Skip to main content

ephemeris_forge/
name.rs

1/// Define a string newtype that is never empty.
2///
3/// The type decodes from a string and fails on the empty string with the given
4/// error type. It compares and sorts like its string, so it works as a map key
5/// that is looked up by `&str`. An optional last argument rewrites the string
6/// before it is stored.
7///
8/// ```
9/// ephemeris_forge::non_empty_name! {
10///     /// A tag name.
11///     pub struct TagName;
12///     /// A tag name was the empty string.
13///     pub error EmptyTagName("tag name cannot be empty");
14/// }
15///
16/// assert_eq!(TagName::try_from("v1".to_owned()).unwrap().as_str(), "v1");
17/// assert_eq!(TagName::try_from(String::new()), Err(EmptyTagName));
18/// ```
19#[macro_export]
20macro_rules! non_empty_name {
21    (
22        $(#[$meta:meta])*
23        $vis:vis struct $name:ident;
24        $(#[$error_meta:meta])*
25        $error_vis:vis error $error:ident($message:literal);
26    ) => {
27        $crate::non_empty_name! {
28            $(#[$meta])* $vis struct $name;
29            $(#[$error_meta])* $error_vis error $error($message);
30            normalize = |value: ::std::string::String| value;
31        }
32    };
33    (
34        $(#[$meta:meta])*
35        $vis:vis struct $name:ident;
36        $(#[$error_meta:meta])*
37        $error_vis:vis error $error:ident($message:literal);
38        normalize = $normalize:expr;
39    ) => {
40        $(#[$meta])*
41        #[derive(Clone, Debug, Eq, ::facet::Facet, Ord, PartialEq, PartialOrd)]
42        #[facet(proxy = ::std::string::String)]
43        $vis struct $name(::std::string::String);
44
45        $(#[$error_meta])*
46        #[derive(Clone, Copy, Debug, Eq, PartialEq, ::thiserror::Error)]
47        #[error($message)]
48        $error_vis struct $error;
49
50        impl $name {
51            /// Borrow the name.
52            #[must_use]
53            pub fn as_str(&self) -> &str {
54                &self.0
55            }
56        }
57
58        impl ::core::convert::TryFrom<::std::string::String> for $name {
59            type Error = $error;
60
61            fn try_from(value: ::std::string::String) -> Result<Self, Self::Error> {
62                if value.is_empty() {
63                    Err($error)
64                } else {
65                    Ok(Self(($normalize)(value)))
66                }
67            }
68        }
69
70        impl ::core::convert::From<&$name> for ::std::string::String {
71            fn from(value: &$name) -> Self {
72                value.0.clone()
73            }
74        }
75
76        impl ::core::convert::AsRef<str> for $name {
77            fn as_ref(&self) -> &str {
78                &self.0
79            }
80        }
81
82        impl ::core::borrow::Borrow<str> for $name {
83            fn borrow(&self) -> &str {
84                &self.0
85            }
86        }
87
88        impl ::core::fmt::Display for $name {
89            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
90                f.write_str(&self.0)
91            }
92        }
93    };
94}