Files
mnemosyne/src/users/handle.rs

90 lines
2.4 KiB
Rust

use std::{fmt::Display, hash::Hash, ops::Deref, str::FromStr};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
#[sqlx(transparent)]
#[serde(into = "String")]
#[serde(try_from = "String")]
pub struct UserHandle(String);
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq, Serialize)]
pub enum UserHandleError {
#[error("Handle is too short - must be 3 or more characters.")]
TooShort,
#[error("Handle is too long - must be 16 or less characters.")]
TooLong,
#[error("Handle must consist of ASCII alphanumeric characters only.")]
NonAsciiAlphanumeric,
}
impl UserHandle {
pub fn new(input: impl AsRef<str>) -> Result<Self, UserHandleError> {
let s = input.as_ref();
UserHandle::validate_str(s)?;
Ok(UserHandle(s.to_string()))
}
pub fn validate_str(str: &str) -> Result<(), UserHandleError> {
match str.len() {
..=2 => return Err(UserHandleError::TooShort),
17.. => return Err(UserHandleError::TooLong),
_ => (),
};
if str.bytes().any(|c| !c.is_ascii_alphanumeric()) {
return Err(UserHandleError::NonAsciiAlphanumeric);
}
Ok(())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl PartialEq for UserHandle {
fn eq(&self, other: &Self) -> bool {
self.0.eq_ignore_ascii_case(&other.0)
}
}
impl Eq for UserHandle {}
impl Hash for UserHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.to_ascii_lowercase().hash(state);
}
}
impl AsRef<str> for UserHandle {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Deref for UserHandle {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for UserHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for UserHandle {
type Err = UserHandleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::validate_str(s)?;
Ok(UserHandle(s.to_string()))
}
}
impl TryFrom<String> for UserHandle {
type Error = UserHandleError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::validate_str(&value)?;
Ok(UserHandle(value))
}
}
impl From<UserHandle> for String {
fn from(value: UserHandle) -> Self {
value.0
}
}