reliakit_primitives/
non_empty.rs1use crate::{PrimitiveError, PrimitiveResult};
2use alloc::string::String;
3use core::{fmt, hash::Hash, ops::Deref, str::FromStr};
4
5#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct NonEmptyStr(String);
8
9impl NonEmptyStr {
10 pub fn new(value: impl Into<String>) -> PrimitiveResult<Self> {
15 let value = value.into();
16 if value.trim().is_empty() {
17 return Err(PrimitiveError::Empty);
18 }
19 Ok(Self(value))
20 }
21
22 pub fn as_str(&self) -> &str {
24 &self.0
25 }
26
27 pub fn into_inner(self) -> String {
29 self.0
30 }
31
32 pub fn len(&self) -> usize {
34 self.0.chars().count()
35 }
36
37 pub fn is_empty(&self) -> bool {
41 false
42 }
43}
44
45impl fmt::Display for NonEmptyStr {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.write_str(&self.0)
48 }
49}
50
51impl AsRef<str> for NonEmptyStr {
52 fn as_ref(&self) -> &str {
53 self.as_str()
54 }
55}
56
57impl Deref for NonEmptyStr {
58 type Target = str;
59
60 fn deref(&self) -> &Self::Target {
61 self.as_str()
62 }
63}
64
65impl TryFrom<String> for NonEmptyStr {
66 type Error = PrimitiveError;
67
68 fn try_from(value: String) -> Result<Self, Self::Error> {
69 Self::new(value)
70 }
71}
72
73impl TryFrom<&str> for NonEmptyStr {
74 type Error = PrimitiveError;
75
76 fn try_from(value: &str) -> Result<Self, Self::Error> {
77 Self::new(value)
78 }
79}
80
81impl FromStr for NonEmptyStr {
82 type Err = PrimitiveError;
83
84 fn from_str(s: &str) -> Result<Self, Self::Err> {
85 Self::new(s)
86 }
87}
88
89impl From<NonEmptyStr> for String {
90 fn from(value: NonEmptyStr) -> Self {
91 value.into_inner()
92 }
93}
94
95impl PartialEq<str> for NonEmptyStr {
96 fn eq(&self, other: &str) -> bool {
97 self.as_str() == other
98 }
99}
100
101impl PartialEq<&str> for NonEmptyStr {
102 fn eq(&self, other: &&str) -> bool {
103 self.as_str() == *other
104 }
105}
106
107impl PartialEq<String> for NonEmptyStr {
108 fn eq(&self, other: &String) -> bool {
109 self.as_str() == other.as_str()
110 }
111}
112
113impl PartialEq<&String> for NonEmptyStr {
114 fn eq(&self, other: &&String) -> bool {
115 self.as_str() == other.as_str()
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::NonEmptyStr;
122 use crate::PrimitiveError;
123 use alloc::string::{String, ToString};
124
125 #[test]
126 fn accepts_valid_strings() {
127 let value = NonEmptyStr::new("service-api").unwrap();
128 assert_eq!(value.as_str(), "service-api");
129 assert!(!value.is_empty());
130 }
131
132 #[test]
133 fn rejects_empty_string() {
134 assert_eq!(NonEmptyStr::new("").unwrap_err(), PrimitiveError::Empty);
135 }
136
137 #[test]
138 fn rejects_whitespace_only_string() {
139 assert_eq!(NonEmptyStr::new(" ").unwrap_err(), PrimitiveError::Empty);
140 }
141
142 #[test]
143 fn preserves_original_string() {
144 let value = NonEmptyStr::new(" api ").unwrap();
145 assert_eq!(value.as_str(), " api ");
146 }
147
148 #[test]
149 fn into_inner_returns_string() {
150 let value = NonEmptyStr::new("hello").unwrap();
151 assert_eq!(value.into_inner(), "hello");
152 }
153
154 #[test]
155 fn len_returns_char_count() {
156 let value = NonEmptyStr::new("hello").unwrap();
157 assert_eq!(value.len(), 5);
158 let unicode = NonEmptyStr::new("éàü").unwrap();
159 assert_eq!(unicode.len(), 3);
160 }
161
162 #[test]
163 fn display_formats_inner_string() {
164 let value = NonEmptyStr::new("hello").unwrap();
165 assert_eq!(value.to_string(), "hello");
166 }
167
168 #[test]
169 fn as_ref_returns_str() {
170 let value = NonEmptyStr::new("hello").unwrap();
171 let s: &str = value.as_ref();
172 assert_eq!(s, "hello");
173 }
174
175 #[test]
176 fn deref_to_str() {
177 let value = NonEmptyStr::new("hello").unwrap();
178 assert_eq!(&*value, "hello");
179 }
180
181 #[test]
182 fn try_from_string() {
183 let value = NonEmptyStr::try_from(String::from("hello")).unwrap();
184 assert_eq!(value.as_str(), "hello");
185 }
186
187 #[test]
188 fn try_from_str_ref() {
189 let value = NonEmptyStr::try_from("hello").unwrap();
190 assert_eq!(value.as_str(), "hello");
191 }
192
193 #[test]
194 fn from_non_empty_str_into_string() {
195 let value = NonEmptyStr::new("hello").unwrap();
196 let s = String::from(value);
197 assert_eq!(s, "hello");
198 }
199
200 #[test]
201 fn from_str_and_string_comparisons() {
202 let value = "hello".parse::<NonEmptyStr>().unwrap();
203 let owned = String::from("hello");
204 assert_eq!(value, "hello");
205 assert_eq!(value, owned);
206 assert!(NonEmptyStr::try_from(" ").is_err());
207 }
208}