reliakit_primitives/
non_empty.rs1use crate::{PrimitiveError, PrimitiveResult};
2use alloc::string::String;
3use core::{fmt, hash::Hash, ops::Deref};
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 From<NonEmptyStr> for String {
82 fn from(value: NonEmptyStr) -> Self {
83 value.into_inner()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::NonEmptyStr;
90 use crate::PrimitiveError;
91 use alloc::string::{String, ToString};
92
93 #[test]
94 fn accepts_valid_strings() {
95 let value = NonEmptyStr::new("service-api").unwrap();
96 assert_eq!(value.as_str(), "service-api");
97 assert!(!value.is_empty());
98 }
99
100 #[test]
101 fn rejects_empty_string() {
102 assert_eq!(NonEmptyStr::new("").unwrap_err(), PrimitiveError::Empty);
103 }
104
105 #[test]
106 fn rejects_whitespace_only_string() {
107 assert_eq!(NonEmptyStr::new(" ").unwrap_err(), PrimitiveError::Empty);
108 }
109
110 #[test]
111 fn preserves_original_string() {
112 let value = NonEmptyStr::new(" api ").unwrap();
113 assert_eq!(value.as_str(), " api ");
114 }
115
116 #[test]
117 fn into_inner_returns_string() {
118 let value = NonEmptyStr::new("hello").unwrap();
119 assert_eq!(value.into_inner(), "hello");
120 }
121
122 #[test]
123 fn len_returns_char_count() {
124 let value = NonEmptyStr::new("hello").unwrap();
125 assert_eq!(value.len(), 5);
126 let unicode = NonEmptyStr::new("éàü").unwrap();
127 assert_eq!(unicode.len(), 3);
128 }
129
130 #[test]
131 fn display_formats_inner_string() {
132 let value = NonEmptyStr::new("hello").unwrap();
133 assert_eq!(value.to_string(), "hello");
134 }
135
136 #[test]
137 fn as_ref_returns_str() {
138 let value = NonEmptyStr::new("hello").unwrap();
139 let s: &str = value.as_ref();
140 assert_eq!(s, "hello");
141 }
142
143 #[test]
144 fn deref_to_str() {
145 let value = NonEmptyStr::new("hello").unwrap();
146 assert_eq!(&*value, "hello");
147 }
148
149 #[test]
150 fn try_from_string() {
151 let value = NonEmptyStr::try_from(String::from("hello")).unwrap();
152 assert_eq!(value.as_str(), "hello");
153 }
154
155 #[test]
156 fn try_from_str_ref() {
157 let value = NonEmptyStr::try_from("hello").unwrap();
158 assert_eq!(value.as_str(), "hello");
159 }
160
161 #[test]
162 fn from_non_empty_str_into_string() {
163 let value = NonEmptyStr::new("hello").unwrap();
164 let s = String::from(value);
165 assert_eq!(s, "hello");
166 }
167}