diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/conversation/id.rs | 40 | ||||
| -rw-r--r-- | src/error.rs | 25 | ||||
| -rw-r--r-- | src/id.rs | 170 | ||||
| -rw-r--r-- | src/invite/id.rs | 26 | ||||
| -rw-r--r-- | src/message/id.rs | 29 | ||||
| -rw-r--r-- | src/token/id.rs | 29 | ||||
| -rw-r--r-- | src/user/id.rs | 25 |
7 files changed, 173 insertions, 171 deletions
diff --git a/src/conversation/id.rs b/src/conversation/id.rs index 5f37a59..1c260c2 100644 --- a/src/conversation/id.rs +++ b/src/conversation/id.rs @@ -1,38 +1,10 @@ -use std::fmt; +pub type Id = crate::id::Id<Conversation>; -use crate::id::Id as BaseId; +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Conversation; -// Stable identifier for a [Conversation]. Prefixed with `C`. -#[derive( - Clone, - Debug, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - sqlx::Type, - serde::Deserialize, - serde::Serialize, -)] -#[sqlx(transparent)] -#[serde(transparent)] -pub struct Id(BaseId); - -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} - -impl Id { - pub fn generate() -> Self { - BaseId::generate("C") - } -} - -impl fmt::Display for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) +impl crate::id::Prefix for Conversation { + fn prefix(&self) -> &'static str { + "C" } } diff --git a/src/error.rs b/src/error.rs index 7483f00..3c46097 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,8 +5,6 @@ use axum::{ response::{IntoResponse, Response}, }; -use crate::id::Id as BaseId; - // I'm making an effort to avoid `anyhow` here, as that crate is _enormously_ // complex (though very usable). We don't need to be overly careful about // allocations on errors in this app, so this is fine for most "general @@ -45,25 +43,14 @@ impl IntoResponse for Internal { } } -// Transient identifier for an InternalError. Prefixed with `E`. -#[derive(Debug)] -pub struct Id(BaseId); - -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} +pub type Id = crate::id::Id<InternalError>; -impl Id { - pub fn generate() -> Self { - BaseId::generate("E") - } -} +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct InternalError; -impl fmt::Display for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) +impl crate::id::Prefix for InternalError { + fn prefix(&self) -> &'static str { + "E" } } @@ -1,4 +1,7 @@ use rand::{seq::SliceRandom, thread_rng}; +use serde::{Deserializer, Serializer}; +use sqlx::encode::IsNull; +use sqlx::{Database, Decode, Encode, Type}; use std::fmt; // Make IDs that: @@ -23,48 +26,149 @@ const ALPHABET: [char; 23] = [ // 68 bits per ID. const ID_SIZE: usize = 15; -// Intended to be wrapped in a newtype that provides both type-based separation -// from other identifier types, and a unique prefix to allow the intended type -// of an ID to be determined by eyeball when debugging. +// Intended to be wrapped in a type aliases that specalizes Id on a type that +// implements Prefix, both so that IDs are type-wise distinct within the server +// and so that IDs are readily distinguishable from one another outside of it. // // By convention, the prefix should be UPPERCASE - note that the alphabet for // this is entirely lowercase. -#[derive( - Clone, - Debug, - Hash, - Eq, - Ord, - PartialEq, - PartialOrd, - sqlx::Type, - serde::Deserialize, - serde::Serialize, -)] -#[sqlx(transparent)] -#[serde(transparent)] -pub struct Id(String); - -impl fmt::Display for Id { +// +// To build a new ID type, create a "marker" type that implements this trait, plus the following list of derives: +// +// ``` +// #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +// pub struct Person; +// +// impl crate::id::Prefix for Person { +// fn prefix(&self) -> &str { +// "P" // try not to conflict with other prefixes +// } +// } +// ``` +// +// Then provide a type alias of the form +// +// ``` +// pub type Id = crate::id::Id<Person>; +// ``` +// +// The `Id` type will provide a `generate()` method that can generate new random IDs, and the +// resulting type alias can be serialized to and deserialized from strings, and stored in string +// compatible database columns. +#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub struct Id<T>(String, T); + +impl<T> fmt::Display for Id<T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } -impl Id { - pub fn generate<T>(prefix: &str) -> T +pub trait Prefix { + fn prefix(&self) -> &str; +} + +impl<T> Id<T> +where + T: Prefix + Default, +{ + pub fn generate() -> Self { + let instance = T::default(); + let prefix = instance.prefix(); + + let random_part = (0..ID_SIZE) + .filter_map(|_| ALPHABET.choose(&mut thread_rng())) + .copied(); + + let id = prefix.chars().chain(random_part).collect(); + + Self(id, instance) + } +} + +impl<T> serde::ser::Serialize for Id<T> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let Self(id, _) = self; + id.serialize(serializer) + } +} + +impl<'de, T> serde::de::Deserialize<'de> for Id<T> +where + T: Default, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where - T: From<Self>, + D: Deserializer<'de>, { - let mut rng = thread_rng(); - let id = prefix - .chars() - .chain( - (0..ID_SIZE) - .filter_map(|_| ALPHABET.choose(&mut rng)) /* usize -> &char */ - .copied(), /* &char -> char */ - ) - .collect(); - T::from(Self(id)) + let instance = T::default(); + let id = String::deserialize(deserializer)?; + + Ok(Self(id, instance)) + } +} + +// Type is manually implemented so that we can implement Decode to do +// recover the type token on read. Implementation is otherwise based on +// `#[derive(sqlx::Type)]` with the `#[sqlx(transparent)]` attribute. +impl<DB, T> Type<DB> for Id<T> +where + DB: Database, + String: Type<DB>, +{ + fn type_info() -> <DB as Database>::TypeInfo { + <String as Type<DB>>::type_info() + } + + fn compatible(ty: &<DB as Database>::TypeInfo) -> bool { + <String as Type<DB>>::compatible(ty) + } +} + +impl<'r, DB, T> Decode<'r, DB> for Id<T> +where + DB: Database, + String: Decode<'r, DB>, + T: Default, +{ + fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> { + let instance = T::default(); + let id = String::decode(value)?; + Ok(Self(id, instance)) + } +} + +impl<'q, DB, T> Encode<'q, DB> for Id<T> +where + DB: Database, + String: Encode<'q, DB>, +{ + fn encode( + self, + buf: &mut <DB as Database>::ArgumentBuffer<'q>, + ) -> Result<IsNull, sqlx::error::BoxDynError> { + let Self(id, _) = self; + id.encode(buf) + } + + fn encode_by_ref( + &self, + buf: &mut <DB as Database>::ArgumentBuffer<'q>, + ) -> Result<IsNull, sqlx::error::BoxDynError> { + let Self(id, _) = self; + id.encode_by_ref(buf) + } + + fn produces(&self) -> Option<<DB as Database>::TypeInfo> { + let Self(id, _) = self; + id.produces() + } + + fn size_hint(&self) -> usize { + let Self(id, _) = self; + id.size_hint() } } diff --git a/src/invite/id.rs b/src/invite/id.rs index bd53f1f..c37f3ac 100644 --- a/src/invite/id.rs +++ b/src/invite/id.rs @@ -1,24 +1,10 @@ -use crate::id::Id as BaseId; +pub type Id = crate::id::Id<Invitation>; -// Stable identifier for an invite Prefixed with `I`. -#[derive(Clone, Debug, Eq, PartialEq, sqlx::Type, serde::Deserialize, serde::Serialize)] -#[sqlx(transparent)] -pub struct Id(BaseId); +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Invitation; -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} - -impl Id { - pub fn generate() -> Self { - BaseId::generate("I") - } -} - -impl std::fmt::Display for Id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) +impl crate::id::Prefix for Invitation { + fn prefix(&self) -> &'static str { + "I" } } diff --git a/src/message/id.rs b/src/message/id.rs index 385b103..9161c78 100644 --- a/src/message/id.rs +++ b/src/message/id.rs @@ -1,27 +1,10 @@ -use std::fmt; +pub type Id = crate::id::Id<Message>; -use crate::id::Id as BaseId; +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Message; -// Stable identifier for a [Message]. Prefixed with `M`. -#[derive(Clone, Debug, Eq, Hash, PartialEq, sqlx::Type, serde::Deserialize, serde::Serialize)] -#[sqlx(transparent)] -#[serde(transparent)] -pub struct Id(BaseId); - -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} - -impl Id { - pub fn generate() -> Self { - BaseId::generate("M") - } -} - -impl fmt::Display for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) +impl crate::id::Prefix for Message { + fn prefix(&self) -> &'static str { + "M" } } diff --git a/src/token/id.rs b/src/token/id.rs index 9ef063c..e978d59 100644 --- a/src/token/id.rs +++ b/src/token/id.rs @@ -1,27 +1,10 @@ -use std::fmt; +pub type Id = crate::id::Id<Token>; -use crate::id::Id as BaseId; +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Token; -// Stable identifier for a token. Prefixed with `T`. -#[derive(Clone, Debug, Eq, Hash, PartialEq, sqlx::Type, serde::Deserialize, serde::Serialize)] -#[sqlx(transparent)] -#[serde(transparent)] -pub struct Id(BaseId); - -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} - -impl Id { - pub fn generate() -> Self { - BaseId::generate("T") - } -} - -impl fmt::Display for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) +impl crate::id::Prefix for Token { + fn prefix(&self) -> &'static str { + "T" } } diff --git a/src/user/id.rs b/src/user/id.rs index bc14c1f..3ad8d16 100644 --- a/src/user/id.rs +++ b/src/user/id.rs @@ -1,25 +1,12 @@ -use crate::id::Id as BaseId; - // Stable identifier for a User. Prefixed with `U`. Users created before March, 2025 may have an `L` // prefix, instead. -#[derive(Clone, Debug, Eq, PartialEq, sqlx::Type, serde::Serialize)] -#[sqlx(transparent)] -pub struct Id(BaseId); - -impl From<BaseId> for Id { - fn from(id: BaseId) -> Self { - Self(id) - } -} +pub type Id = crate::id::Id<User>; -impl Id { - pub fn generate() -> Self { - BaseId::generate("U") - } -} +#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct User; -impl std::fmt::Display for Id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) +impl crate::id::Prefix for User { + fn prefix(&self) -> &'static str { + "U" } } |
