1#[cfg_attr(feature = "serde", derive(serde::Serialize))]
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub struct NonEmptyString(String);
28
29#[cfg(feature = "serde")]
30impl<'de> serde::Deserialize<'de> for NonEmptyString {
31 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
32 where
33 D: serde::Deserializer<'de>,
34 {
35 let string = String::deserialize(deserializer)?;
36 NonEmptyString::new(string).ok_or_else(|| {
37 serde::de::Error::custom("cannot deserialize empty string as NonEmptyString")
38 })
39 }
40}
41
42impl NonEmptyString {
43 #[must_use]
46 pub fn new(string: String) -> Option<Self> {
47 if string.is_empty() {
48 None
49 } else {
50 Some(Self(string))
51 }
52 }
53
54 #[must_use]
56 pub fn as_string(&self) -> &String {
57 &self.0
58 }
59
60 #[must_use]
62 pub fn into_string(self) -> String {
63 self.0
64 }
65
66 #[must_use]
68 pub fn as_str(&self) -> &str {
69 self.0.as_str()
70 }
71
72 pub fn push(&mut self, ch: char) {
74 self.0.push(ch);
75 }
76
77 pub fn push_str(&mut self, string: &str) {
79 self.0.push_str(string);
80 }
81
82 pub fn insert(&mut self, idx: usize, ch: char) {
84 self.0.insert(idx, ch);
85 }
86
87 pub fn insert_str(&mut self, idx: usize, string: &str) {
89 self.0.insert_str(idx, string);
90 }
91
92 pub fn remove(&mut self, idx: usize) -> Option<char> {
96 if self.0.len() == 1 {
97 None
98 } else {
99 Some(self.0.remove(idx))
100 }
101 }
102
103 pub fn pop(&mut self) -> Option<char> {
107 if self.0.len() == 1 {
108 None
109 } else {
110 self.0.pop()
111 }
112 }
113
114 #[expect(clippy::len_without_is_empty, reason = "NonEmptyString is never empty")] #[must_use]
117 pub fn len(&self) -> usize {
118 self.0.len()
119 }
120
121 pub fn clear_except_first(&mut self) {
123 let first = self.0.remove(0);
124 self.0.clear();
125 self.0.push(first);
126 }
127
128 pub fn chars(&self) -> std::str::Chars<'_> {
130 self.0.chars()
131 }
132
133 pub fn char_indices(&self) -> std::str::CharIndices<'_> {
135 self.0.char_indices()
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn new_with_empty_string() {
145 let empty = String::new();
146 assert!(NonEmptyString::new(empty).is_none());
147 }
148
149 #[test]
150 fn new_with_non_empty_string() {
151 let non_empty = String::from("hello");
152 let non_empty_string = NonEmptyString::new(non_empty.clone()).unwrap();
153 assert_eq!(non_empty_string.as_string(), &non_empty);
154 }
155
156 #[test]
157 fn push_and_pop() {
158 let mut string = NonEmptyString::new(String::from("a")).unwrap();
159 string.push('b');
160 string.push('c');
161 assert_eq!(string.len(), 3);
162 assert_eq!(string.pop(), Some('c'));
163 assert_eq!(string.pop(), Some('b'));
164 assert_eq!(string.pop(), None);
165 }
166
167 #[test]
168 fn remove() {
169 let mut string = NonEmptyString::new(String::from("abc")).unwrap();
170 assert_eq!(string.remove(1), Some('b'));
171 assert_eq!(string.as_str(), "ac");
172 assert_eq!(string.remove(0), Some('a'));
173 assert_eq!(string.remove(0), None);
174 }
175
176 #[test]
177 fn clear_except_first() {
178 let mut string = NonEmptyString::new(String::from("abcd")).unwrap();
179 string.clear_except_first();
180 assert_eq!(string.as_str(), "a");
181 }
182
183 #[test]
184 fn push_str() {
185 let mut string = NonEmptyString::new(String::from("hello")).unwrap();
186 string.push_str(" world");
187 assert_eq!(string.as_str(), "hello world");
188 }
189}