UserHandle newtype, Users & boilerplate
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
|
*.db
|
||||||
|
*.db3
|
||||||
|
*.env
|
||||||
|
|||||||
@@ -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"] }
|
||||||
|
|||||||
12
src/main.rs
12
src/main.rs
@@ -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
3
src/persons/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod names;
|
||||||
|
|
||||||
|
pub struct Person;
|
||||||
1
src/persons/names.rs
Normal file
1
src/persons/names.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub struct Name;
|
||||||
1
src/quotes/lines.rs
Normal file
1
src/quotes/lines.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub struct QuoteLine;
|
||||||
3
src/quotes/mod.rs
Normal file
3
src/quotes/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod lines;
|
||||||
|
|
||||||
|
pub struct Quote;
|
||||||
1
src/tags.rs
Normal file
1
src/tags.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub struct Tag;
|
||||||
0
src/users/auth.rs
Normal file
0
src/users/auth.rs
Normal file
91
src/users/handle.rs
Normal file
91
src/users/handle.rs
Normal 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
14
src/users/mod.rs
Normal 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
1
src/users/sessions.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub struct Session;
|
||||||
Reference in New Issue
Block a user