diff options
Diffstat (limited to 'src/name.rs')
| -rw-r--r-- | src/name.rs | 85 |
1 files changed, 85 insertions, 0 deletions
diff --git a/src/name.rs b/src/name.rs new file mode 100644 index 0000000..9187d33 --- /dev/null +++ b/src/name.rs @@ -0,0 +1,85 @@ +use std::fmt; + +use crate::normalize::{ident, nfc}; + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, sqlx::Type)] +#[serde(from = "String", into = "String")] +pub struct Name { + display: nfc::String, + canonical: ident::String, +} + +impl Name { + pub fn new<D, C>(display: D, canonical: C) -> Result<Self, Error> + where + D: AsRef<str>, + C: AsRef<str>, + { + let name = Self::from(display); + + if name.canonical.as_str() == canonical.as_ref() { + Ok(name) + } else { + Err(Error::CanonicalMismatch( + canonical.as_ref().into(), + name.canonical, + name.display, + )) + } + } + + pub fn optional<D, C>(display: Option<D>, canonical: Option<C>) -> Result<Option<Self>, Error> + where + D: AsRef<str>, + C: AsRef<str>, + { + display + .zip(canonical) + .map(|(display, canonical)| Self::new(display, canonical)) + .transpose() + } + + pub fn display(&self) -> &nfc::String { + &self.display + } + + pub fn canonical(&self) -> &ident::String { + &self.canonical + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("stored canonical form {0:#?} does not match computed canonical form {:#?} for name {:#?}", .1.as_str(), .2.as_str())] + CanonicalMismatch(String, ident::String, nfc::String), +} + +impl Default for Name { + fn default() -> Self { + Self::from(String::default()) + } +} + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display.fmt(f) + } +} + +impl<S> From<S> for Name +where + S: AsRef<str>, +{ + fn from(name: S) -> Self { + let display = nfc::String::from(&name); + let canonical = ident::String::from(&name); + + Self { display, canonical } + } +} + +impl From<Name> for String { + fn from(name: Name) -> Self { + name.display.into() + } +} |
