1use std::{borrow::Cow, fmt};
4
5#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub 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 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 pub fn as_str(&self) -> &str {
32 self.0.as_ref()
33 }
34
35 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}