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