use std::fmt; use rand::{seq::SliceRandom, thread_rng}; use serde::{Deserializer, Serializer}; use sqlx::encode::IsNull; use sqlx::{Database, Decode, Encode, Type}; // Make IDs that: // // * Do not require escaping in URLs // * Do not require escaping in hostnames // * Are unique up to case conversion // * Are relatively unlikely to contain cursewords // * Are relatively unlikely to contain visually similar characters in most // typefaces // * Are not sequential // // This leaves 23 ASCII characters, or about 4.52 bits of entropy per character // if generated with uniform probability. const ALPHABET: [char; 23] = [ '1', '2', '3', '4', '6', '7', '8', '9', 'b', 'c', 'd', 'f', 'h', 'j', 'k', 'n', 'p', 'r', 's', 't', 'w', 'x', 'y', ]; // Pick enough characters per ID to make accidental collisions "acceptably" // unlikely without also making them _too_ unwieldy. This gives a fraction under // 68 bits per ID. const ID_SIZE: usize = 15; // 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. // // 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; // ``` // // 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(String, T); // Implementations of prefix provide type tokens for IDs where a new value can be generated, with a // known prefix. Most implementations should also implement Default and be zero-sized types without // state, as many of the operations on Ids will discard or re-create type tokens (using // `Default::default()`) when converting between an ID type and some external form. pub trait Prefix { fn prefix(&self) -> &str; } impl Id 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 Id { pub fn as_str(&self) -> &str { let Self(value, _) = self; value } fn inner(&self) -> &String { let Self(value, _) = self; value } } // Any ID can be converted to a string. Its type token is discarded during the conversion. impl From> for String { fn from(value: Id) -> Self { let Id(value, _) = value; value } } // If the type token implements `Default`, then the corresponding ID type can be converted from a // string. A new type token will be generated using `T::default()`. impl From for Id where T: Default, { fn from(value: String) -> Self { Self(value, T::default()) } } // IDs are printable, exactly as their contained string is printable. impl fmt::Display for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner().fmt(f) } } // IDs are serializable, exactly as their contained string is serializable. The type token is // discarded during serialization. impl serde::ser::Serialize for Id { fn serialize(&self, serializer: S) -> Result where S: Serializer, { self.inner().serialize(serializer) } } // If the type token implements `Default`, then the corresponding ID type is deserializable as a // String. A new type token will be generated using `T::default()` during deserialization. impl<'de, T> serde::de::Deserialize<'de> for Id where T: Default, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let id = String::deserialize(deserializer)?; Ok(id.into()) } } // An ID is an sqlx database type for any database where String is a database type. In practice, // that's all of them. // // This is largely cribbed from the implementation generated by `#[sqlx::transparent]`, but we // implement it by hand here in order to handle the fact that an ID has two contained values, // rather than one. impl Type for Id where DB: Database, String: Type, { fn type_info() -> ::TypeInfo { >::type_info() } fn compatible(ty: &::TypeInfo) -> bool { >::compatible(ty) } } // If the type token implements Default, then the corresponding ID type can be decoded as a String // from a database record. A new type token will be generated using `T::default()` during decoding. impl<'r, DB, T> Decode<'r, DB> for Id where DB: Database, String: Decode<'r, DB>, T: Default, { fn decode(value: ::ValueRef<'r>) -> Result { let id = String::decode(value)?; Ok(id.into()) } } // An ID is encodeable to a database value whenever String is encodeable. The type token is // discarded during encoding. impl<'q, DB, T> Encode<'q, DB> for Id where DB: Database, String: Encode<'q, DB>, { fn encode( self, buf: &mut ::ArgumentBuffer<'q>, ) -> Result { self.inner().encode(buf) } fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer<'q>, ) -> Result { self.inner().encode_by_ref(buf) } fn produces(&self) -> Option<::TypeInfo> { self.inner().produces() } fn size_hint(&self) -> usize { self.inner().size_hint() } }