rama_utils/str/
non_empty.rs1use core::{fmt, ops::Deref};
2
3use crate::{std::string::String, str::arcstr::ArcStr};
4
5#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub struct NonEmptyStr(ArcStr);
8
9impl NonEmptyStr {
10 #[must_use]
21 pub const unsafe fn new_unchecked(s: ArcStr) -> Self {
22 Self(s)
23 }
24}
25
26impl AsRef<str> for NonEmptyStr {
27 fn as_ref(&self) -> &str {
28 self.0.as_str()
29 }
30}
31
32impl Deref for NonEmptyStr {
33 type Target = str;
34
35 #[inline(always)]
36 fn deref(&self) -> &Self::Target {
37 self.0.as_str()
38 }
39}
40
41crate::macros::error::static_str_error! {
42 #[doc = "empty string"]
43 pub struct EmptyStrErr;
44}
45
46#[macro_export]
52#[doc(hidden)]
53macro_rules! __non_empty_str {
54 ($text:expr $(,)?) => {{
55 if ($text).is_empty() {
56 panic!("literal is empty");
57 }
58 unsafe { $crate::str::NonEmptyStr::new_unchecked($crate::str::arcstr::arcstr!($text)) }
60 }};
61}
62
63impl fmt::Display for NonEmptyStr {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 self.0.fmt(f)
66 }
67}
68
69impl From<NonEmptyStr> for String {
70 fn from(value: NonEmptyStr) -> Self {
71 value.0.to_string()
72 }
73}
74
75impl TryFrom<String> for NonEmptyStr {
76 type Error = EmptyStrErr;
77
78 fn try_from(value: String) -> Result<Self, Self::Error> {
79 if value.is_empty() {
80 Err(Self::Error::default())
81 } else {
82 Ok(Self(value.into()))
83 }
84 }
85}
86
87impl TryFrom<ArcStr> for NonEmptyStr {
88 type Error = EmptyStrErr;
89
90 fn try_from(value: ArcStr) -> Result<Self, Self::Error> {
91 if value.is_empty() {
92 Err(Self::Error::default())
93 } else {
94 Ok(Self(value))
95 }
96 }
97}
98
99impl TryFrom<&String> for NonEmptyStr {
100 type Error = EmptyStrErr;
101
102 fn try_from(value: &String) -> Result<Self, Self::Error> {
103 if value.is_empty() {
104 Err(Self::Error::default())
105 } else {
106 Ok(Self(value.into()))
107 }
108 }
109}
110
111impl TryFrom<&str> for NonEmptyStr {
112 type Error = EmptyStrErr;
113
114 fn try_from(value: &str) -> Result<Self, Self::Error> {
115 if value.is_empty() {
116 Err(Self::Error::default())
117 } else {
118 Ok(Self(value.into()))
119 }
120 }
121}
122
123impl core::str::FromStr for NonEmptyStr {
124 type Err = EmptyStrErr;
125
126 #[inline(always)]
127 fn from_str(s: &str) -> Result<Self, Self::Err> {
128 s.try_into()
129 }
130}
131
132impl PartialEq<str> for NonEmptyStr {
133 fn eq(&self, other: &str) -> bool {
134 self.0 == other
135 }
136}
137
138impl PartialEq<String> for NonEmptyStr {
139 fn eq(&self, other: &String) -> bool {
140 self.0.as_str() == other
141 }
142}
143
144impl PartialEq<&String> for NonEmptyStr {
145 fn eq(&self, other: &&String) -> bool {
146 self.0.as_str() == *other
147 }
148}
149
150impl PartialEq<&str> for NonEmptyStr {
151 fn eq(&self, other: &&str) -> bool {
152 self.0 == *other
153 }
154}
155
156impl PartialEq<NonEmptyStr> for str {
157 fn eq(&self, other: &NonEmptyStr) -> bool {
158 other == self
159 }
160}
161
162impl PartialEq<NonEmptyStr> for String {
163 fn eq(&self, other: &NonEmptyStr) -> bool {
164 other == self
165 }
166}
167
168impl PartialEq<NonEmptyStr> for &String {
169 #[inline(always)]
170 fn eq(&self, other: &NonEmptyStr) -> bool {
171 other == *self
172 }
173}
174
175impl PartialEq<NonEmptyStr> for &str {
176 #[inline(always)]
177 fn eq(&self, other: &NonEmptyStr) -> bool {
178 other == *self
179 }
180}
181
182impl serde::Serialize for NonEmptyStr {
183 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
184 where
185 S: serde::Serializer,
186 {
187 self.0.serialize(serializer)
188 }
189}
190
191impl<'de> serde::Deserialize<'de> for NonEmptyStr {
192 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
193 where
194 D: serde::Deserializer<'de>,
195 {
196 let s = ArcStr::deserialize(deserializer)?;
197 if s.is_empty() {
198 return Err(serde::de::Error::custom(EmptyStrErr::default()));
199 }
200 Ok(Self(s))
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 fn assert_try_into_err(src: impl TryInto<NonEmptyStr>) {
209 _ = src.try_into().unwrap_err();
210 }
211
212 #[cfg(not(loom))]
213 fn assert_try_into_ok<S>(src: S)
214 where
215 S: TryInto<NonEmptyStr, Error: std::error::Error>
216 + fmt::Debug
217 + Clone
218 + PartialEq<NonEmptyStr>,
219 {
220 let expected = src.clone();
221 let value: NonEmptyStr = src.try_into().unwrap();
222 assert_eq!(expected, value);
223 }
224
225 #[test]
226 fn test_non_empty_string_construction_failure() {
227 assert_try_into_err("");
228 assert_try_into_err(String::from(""));
229 #[expect(
230 clippy::needless_borrows_for_generic_args,
231 reason = "exercise &String→TryInto path"
232 )]
233 assert_try_into_err(&String::from(""));
234 }
235
236 #[test]
237 #[cfg(not(loom))]
238 fn test_non_empty_string_construction_success() {
239 assert_try_into_ok("a");
240 assert_try_into_ok(String::from("b"));
241 #[expect(
242 clippy::needless_borrows_for_generic_args,
243 reason = "exercise &String→TryInto path"
244 )]
245 assert_try_into_ok(&String::from("c"));
246 }
247
248 #[test]
249 #[cfg(not(loom))]
250 fn test_serde_json_compat() {
251 let source = r##"{"greeting": "Hello", "language": "en"}"##.to_owned();
252
253 #[derive(Debug, serde::Deserialize)]
254 struct Test {
255 greeting: NonEmptyStr,
256 language: NonEmptyStr,
257 }
258
259 let test: Test = serde_json::from_str(&source).unwrap();
260 assert_eq!("Hello", test.greeting);
261 assert_eq!("en", test.language);
262 }
263}