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(display: D, canonical: C) -> Result where D: AsRef, C: AsRef, { 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(display: Option, canonical: Option) -> Result, Error> where D: AsRef, C: AsRef, { 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 From for Name where S: AsRef, { fn from(name: S) -> Self { let display = nfc::String::from(&name); let canonical = ident::String::from(&name); Self { display, canonical } } } impl From for String { fn from(name: Name) -> Self { name.display.into() } }