rama_utils/
str.rs

1//! String utility types.
2
3use std::{borrow::Cow, fmt};
4
5#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
6/// A string which can never be empty.
7pub struct NonEmptyString(Cow<'static, str>);
8
9crate::macros::error::static_str_error! {
10    #[doc = "empty string"]
11    pub struct EmptyStringErr;
12}
13
14impl NonEmptyString {
15    /// Creates a non-empty string a compile time.
16    ///
17    /// This function requires the static string be non-empty.
18    ///
19    /// # Panics
20    ///
21    /// This function panics at **compile time** when the static string is empty.
22    pub const fn from_static(src: &'static str) -> NonEmptyString {
23        if src.is_empty() {
24            panic!("empty static string");
25        }
26
27        NonEmptyString(Cow::Borrowed(src))
28    }
29
30    /// Views this [`NonEmptyString`] as a string slice.
31    pub fn as_str(&self) -> &str {
32        self.0.as_ref()
33    }
34
35    /// Views this [`NonEmptyString`] as a bytes slice.
36    pub fn as_bytes(&self) -> &[u8] {
37        self.0.as_ref().as_bytes()
38    }
39}
40
41impl fmt::Display for NonEmptyString {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        self.0.fmt(f)
44    }
45}
46
47impl From<NonEmptyString> for String {
48    fn from(value: NonEmptyString) -> Self {
49        value.0.to_string()
50    }
51}
52
53impl TryFrom<String> for NonEmptyString {
54    type Error = EmptyStringErr;
55
56    fn try_from(value: String) -> Result<Self, Self::Error> {
57        if value.is_empty() {
58            Err(Self::Error::default())
59        } else {
60            Ok(Self(Cow::Owned(value)))
61        }
62    }
63}
64
65impl TryFrom<&String> for NonEmptyString {
66    type Error = EmptyStringErr;
67
68    fn try_from(value: &String) -> Result<Self, Self::Error> {
69        if value.is_empty() {
70            Err(Self::Error::default())
71        } else {
72            Ok(Self(Cow::Owned(value.clone())))
73        }
74    }
75}
76
77impl TryFrom<&str> for NonEmptyString {
78    type Error = EmptyStringErr;
79
80    fn try_from(value: &str) -> Result<Self, Self::Error> {
81        if value.is_empty() {
82            Err(Self::Error::default())
83        } else {
84            Ok(Self(Cow::Owned(value.to_owned())))
85        }
86    }
87}
88
89impl std::str::FromStr for NonEmptyString {
90    type Err = EmptyStringErr;
91
92    #[inline]
93    fn from_str(s: &str) -> Result<Self, Self::Err> {
94        s.try_into()
95    }
96}
97
98impl AsRef<str> for NonEmptyString {
99    fn as_ref(&self) -> &str {
100        self.0.as_ref()
101    }
102}
103
104impl PartialEq<str> for NonEmptyString {
105    fn eq(&self, other: &str) -> bool {
106        self.0 == other
107    }
108}
109
110impl PartialEq<String> for NonEmptyString {
111    fn eq(&self, other: &String) -> bool {
112        self.0.as_ref() == other
113    }
114}
115
116impl PartialEq<&String> for NonEmptyString {
117    fn eq(&self, other: &&String) -> bool {
118        self.0.as_ref() == *other
119    }
120}
121
122impl PartialEq<&str> for NonEmptyString {
123    fn eq(&self, other: &&str) -> bool {
124        self.0 == *other
125    }
126}
127
128impl PartialEq<NonEmptyString> for str {
129    fn eq(&self, other: &NonEmptyString) -> bool {
130        other == self
131    }
132}
133
134impl PartialEq<NonEmptyString> for String {
135    fn eq(&self, other: &NonEmptyString) -> bool {
136        other == self
137    }
138}
139
140impl PartialEq<NonEmptyString> for &String {
141    fn eq(&self, other: &NonEmptyString) -> bool {
142        other == *self
143    }
144}
145
146impl PartialEq<NonEmptyString> for &str {
147    fn eq(&self, other: &NonEmptyString) -> bool {
148        other == *self
149    }
150}
151
152impl serde::Serialize for NonEmptyString {
153    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154    where
155        S: serde::Serializer,
156    {
157        self.0.serialize(serializer)
158    }
159}
160
161impl<'de> serde::Deserialize<'de> for NonEmptyString {
162    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163    where
164        D: serde::Deserializer<'de>,
165    {
166        let s = <Cow<'de, str>>::deserialize(deserializer)?;
167        s.parse().map_err(serde::de::Error::custom)
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    fn assert_try_into_err(src: impl TryInto<NonEmptyString>) {
176        assert!(src.try_into().is_err());
177    }
178
179    fn assert_try_into_ok<S>(src: S)
180    where
181        S: TryInto<NonEmptyString, Error: std::error::Error>
182            + fmt::Debug
183            + Clone
184            + PartialEq<NonEmptyString>,
185    {
186        let expected = src.clone();
187        let value: NonEmptyString = src.try_into().unwrap();
188        assert_eq!(expected, value);
189    }
190
191    #[test]
192    fn test_non_empty_string_construction_failure() {
193        assert_try_into_err("");
194        assert_try_into_err(String::from(""));
195        #[allow(clippy::needless_borrows_for_generic_args)]
196        assert_try_into_err(&String::from(""));
197    }
198
199    #[test]
200    fn test_non_empty_string_construction_success() {
201        assert_try_into_ok("a");
202        assert_try_into_ok(String::from("b"));
203        #[allow(clippy::needless_borrows_for_generic_args)]
204        assert_try_into_ok(&String::from("c"));
205    }
206
207    #[test]
208    fn test_serde_json_compat() {
209        let source = r##"{"greeting": "Hello", "language": "en"}"##.to_owned();
210
211        #[derive(Debug, serde::Deserialize)]
212        struct Test {
213            greeting: NonEmptyString,
214            language: NonEmptyString,
215        }
216
217        let test: Test = serde_json::from_str(&source).unwrap();
218        assert_eq!("Hello", test.greeting);
219        assert_eq!("en", test.language);
220    }
221}