UserHandle newtype, Users & boilerplate

This commit is contained in:
2026-02-22 14:53:40 +01:00
parent 53f9e40477
commit 7a62819d9c
12 changed files with 129 additions and 3 deletions

3
.gitignore vendored
View File

@@ -1 +1,4 @@
/target /target
*.db
*.db3
*.env

View File

@@ -12,7 +12,7 @@ dotenvy = "0.15.7"
maud = { version = "0.27.0", features = ["axum"] } maud = { version = "0.27.0", features = ["axum"] }
rand = "0.10.0" rand = "0.10.0"
rusqlite = { version = "0.38.0", features = ["bundled", "chrono", "uuid"] } rusqlite = { version = "0.38.0", features = ["bundled", "chrono", "uuid"] }
serde = "1.0.228" serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149" serde_json = "1.0.149"
thiserror = "2.0.18" thiserror = "2.0.18"
tokio = { version = "1.49.0", features = ["full"] } tokio = { version = "1.49.0", features = ["full"] }

View File

@@ -1,5 +1,13 @@
mod database; use std::error::Error;
fn main() { mod database;
mod persons;
mod quotes;
mod tags;
mod users;
fn main() -> Result<(), Box<dyn Error>> {
println!("Hello, world!"); println!("Hello, world!");
Ok(())
} }

3
src/persons/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod names;
pub struct Person;

1
src/persons/names.rs Normal file
View File

@@ -0,0 +1 @@
pub struct Name;

1
src/quotes/lines.rs Normal file
View File

@@ -0,0 +1 @@
pub struct QuoteLine;

3
src/quotes/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod lines;
pub struct Quote;

1
src/tags.rs Normal file
View File

@@ -0,0 +1 @@
pub struct Tag;

0
src/users/auth.rs Normal file
View File

91
src/users/handle.rs Normal file
View File

@@ -0,0 +1,91 @@
use std::{fmt::Display, ops::Deref, str::FromStr};
use rusqlite::{
Result as RusqliteResult,
types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[serde(into = "String")]
#[serde(try_from = "String")]
pub struct UserHandle(String);
#[derive(Debug, thiserror::Error, Clone, PartialEq)]
pub enum UserHandleError {
#[error("Handle is too short - must be 3 or more characters.")]
HandleTooShort,
#[error("Handle is too long - must be 16 or less characters.")]
HandleTooLong,
#[error("Handle must consist of ASCII alphanumeric characters only.")]
HandleNonAsciiAlphanumeric,
}
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::HandleTooShort),
17.. => return Err(UserHandleError::HandleTooLong),
_ => (),
};
if str.bytes().any(|c| !c.is_ascii_alphanumeric()) {
return Err(UserHandleError::HandleNonAsciiAlphanumeric);
}
Ok(())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
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
}
}
impl ToSql for UserHandle {
fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
Ok(self.0.to_sql()?)
}
}
impl FromSql for UserHandle {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
UserHandle::from_str(value.as_str()?).map_err(|e| FromSqlError::Other(Box::new(e)))
}
}

14
src/users/mod.rs Normal file
View File

@@ -0,0 +1,14 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::users::handle::UserHandle;
pub mod auth;
pub mod handle;
pub mod sessions;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct User {
pub id: Uuid,
pub handle: UserHandle,
}

1
src/users/sessions.rs Normal file
View File

@@ -0,0 +1 @@
pub struct Session;