strid_examples/
normalized.rs

1//! An example of constructing a strongly-typed wrapper around
2//! a normalized string value.
3//!
4//! The types in this module perform validation and normalization prior
5//! to allowing the type to be instantiated. If the value is already
6//! normalized, then the value is used without modification. Otherwise,
7//! an attempt is made to normalize the value. If the value cannot be
8//! normalized, then an error will be returned rather than allowing the
9//! invalid value to be constructed.
10//!
11//! Refer to the [`Normalizer`][strid::Normalizer] implementation
12//! for a given type for additional information on what is considered
13//! a valid or normalizable value for the type.
14
15use std::{borrow::Cow, convert::Infallible, error, fmt};
16
17use strid::braid;
18
19/// An error indicating that the provided value was invalid
20#[derive(Debug)]
21pub enum InvalidString {
22    EmptyString,
23    InvalidCharacter,
24}
25
26impl fmt::Display for InvalidString {
27    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28        match self {
29            Self::EmptyString => f.write_str("string cannot be empty"),
30            Self::InvalidCharacter => f.write_str("string contains invalid uppercase character"),
31        }
32    }
33}
34
35impl From<Infallible> for InvalidString {
36    #[inline(always)]
37    fn from(x: Infallible) -> Self {
38        match x {}
39    }
40}
41
42impl error::Error for InvalidString {}
43
44/// A non-empty [`String`] normalized to lowercase
45///
46/// This type maintains an invariant that ensures that a
47/// value of this type cannot be constructed that contains
48/// invalid data. Data that _can_ be normalized to a valid
49/// instance of this type will be.
50///
51/// Because this type does normalization, the type explicitly
52/// does _not_ implement [`Borrow<str>`][::std::borrow::Borrow],
53/// as doing so would could violate the contract of that trait,
54/// potentially resulting in lost data. If a user of
55/// the crate would like to override this, then they can
56/// explicitly implement the trait.
57///
58/// This type includes an explicit parameter indicating that
59/// the borrowed form of this type should be named [`LowerStr`].
60#[braid(
61    serde,
62    normalizer,
63    ref_name = "LowerStr",
64    ref_doc = "A borrowed reference to a non-empty, lowercase string"
65)]
66pub struct LowerString(String);
67
68impl strid::Validator for LowerString {
69    type Error = InvalidString;
70
71    fn validate(raw: &str) -> Result<(), Self::Error> {
72        if raw.is_empty() {
73            Err(InvalidString::EmptyString)
74        } else if raw.chars().any(char::is_uppercase) {
75            Err(InvalidString::InvalidCharacter)
76        } else {
77            Ok(())
78        }
79    }
80}
81
82impl strid::Normalizer for LowerString {
83    fn normalize(s: &str) -> Result<Cow<'_, str>, Self::Error> {
84        if s.is_empty() {
85            Err(InvalidString::EmptyString)
86        } else if s.contains(char::is_uppercase) {
87            Ok(Cow::Owned(s.to_lowercase()))
88        } else {
89            Ok(Cow::Borrowed(s))
90        }
91    }
92}
93
94/// A non-empty [`String`] normalized to lowercase
95///
96/// This type maintains an invariant that ensures that a
97/// value of this type cannot be constructed that contains
98/// invalid data. Data that _can_ be normalized to a valid
99/// instance of this type will be.
100///
101/// Because this type does normalization, the type explicitly
102/// does _not_ implement [`Borrow<str>`][::std::borrow::Borrow],
103/// as doing so would could violate the contract of that trait,
104/// potentially resulting in lost data. If a user of
105/// the crate would like to override this, then they can
106/// explicitly implement the trait.
107///
108/// This type includes an explicit parameter indicating that
109/// the borrowed form of this type should be named [`LowerStr`].
110///
111/// Note: This example requires facet support for `compact_str::CompactString`.
112/// See: https://github.com/facet-rs/facet/issues/1282
113#[cfg(feature = "compact_str-facet")]
114#[braid(
115    serde,
116    normalizer,
117    ref_doc = "A borrowed reference to a non-empty, lowercase string"
118)]
119pub struct LowerCompactString(compact_str::CompactString);
120
121#[cfg(feature = "compact_str-facet")]
122impl strid::Validator for LowerCompactString {
123    type Error = InvalidString;
124
125    fn validate(raw: &str) -> Result<(), Self::Error> {
126        if raw.is_empty() {
127            Err(InvalidString::EmptyString)
128        } else if raw.chars().any(char::is_uppercase) {
129            Err(InvalidString::InvalidCharacter)
130        } else {
131            Ok(())
132        }
133    }
134}
135
136#[cfg(feature = "compact_str-facet")]
137impl strid::Normalizer for LowerCompactString {
138    fn normalize(s: &str) -> Result<Cow<str>, Self::Error> {
139        if s.is_empty() {
140            Err(InvalidString::EmptyString)
141        } else if s.contains(char::is_uppercase) {
142            Ok(Cow::Owned(s.to_lowercase()))
143        } else {
144            Ok(Cow::Borrowed(s))
145        }
146    }
147}