use std::{fmt, string::String as StdString}; use sqlx::{ Database, Decode, Type, encode::{Encode, IsNull}, }; pub trait Normalize: Clone + Default { fn normalize(&self, value: &str) -> StdString; } #[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(into = "StdString", from = "StdString")] #[serde(bound = "N: Normalize")] pub struct String(StdString, N); impl fmt::Display for String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self(value, _) = self; value.fmt(f) } } impl From for String where S: AsRef, N: Normalize, { fn from(value: S) -> Self { let normalizer = N::default(); let value = normalizer.normalize(value.as_ref()); Self(value, normalizer) } } impl From> for StdString { fn from(value: String) -> Self { let String(value, _) = value; value } } impl std::ops::Deref for String { type Target = StdString; fn deref(&self) -> &Self::Target { let Self(value, _) = self; value } } // Type is manually implemented so that we can implement Decode to do // normalization on read. Implementation is otherwise based on // `#[derive(sqlx::Type)]` with the `#[sqlx(transparent)]` attribute. impl Type for String where DB: Database, StdString: Type, { fn type_info() -> ::TypeInfo { >::type_info() } fn compatible(ty: &::TypeInfo) -> bool { >::compatible(ty) } } impl<'r, DB, N> Decode<'r, DB> for String where DB: Database, StdString: Decode<'r, DB>, N: Normalize, { fn decode(value: ::ValueRef<'r>) -> Result { let value = StdString::decode(value)?; Ok(Self::from(value)) } } impl<'q, DB, N> Encode<'q, DB> for String where DB: Database, StdString: Encode<'q, DB>, { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer<'q>, ) -> Result { let Self(value, _) = self; value.encode_by_ref(buf) } fn encode( self, buf: &mut ::ArgumentBuffer<'q>, ) -> Result { let Self(value, _) = self; value.encode(buf) } fn produces(&self) -> Option<::TypeInfo> { let Self(value, _) = self; value.produces() } fn size_hint(&self) -> usize { let Self(value, _) = self; value.size_hint() } }