summaryrefslogtreecommitdiff
path: root/src/name.rs
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-10-22 19:12:34 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-10-22 19:12:34 -0400
commit6430854352745f45281021c305b4e350bc92d535 (patch)
treec6901c22a45e36415f63efe988d4d4f2a309df81 /src/name.rs
parent98af8ff80da919a1126ba7c6afa65e6654b5ecde (diff)
parentdb940bacd096a33a65f29759e70ea1acf6186a67 (diff)
Merge branch 'unicode-normalization'
Diffstat (limited to 'src/name.rs')
-rw-r--r--src/name.rs85
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()
+ }
+}