non_empty_string/
lib.rs

1#![doc(issue_tracker_base_url = "https://github.com/MidasLamb/non-empty-string/issues/")]
2
3//! # NonEmptyString
4//! A simple wrapper type for `String`s that ensures that the string inside is not `.empty()`, meaning that the length > 0.
5
6// Test the items in the readme file.
7#[cfg(doctest)]
8mod test_readme {
9    #[doc = include_str!("../README.md")]
10    mod something {}
11}
12
13use std::str::FromStr;
14
15use delegate::delegate;
16
17#[cfg(feature = "serde")]
18mod serde_support;
19
20mod trait_impls;
21
22/// A simple String wrapper type, similar to NonZeroUsize and friends.
23/// Guarantees that the String contained inside is not of length 0.
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
25#[repr(transparent)]
26pub struct NonEmptyString(String);
27
28#[allow(clippy::len_without_is_empty, reason = "is_empty would always returns false so it seems a bit silly to have it.")]
29impl NonEmptyString {
30    /// Attempts to create a new `NonEmptyString`.
31    /// If the given `string` is empty, `Err` is returned, containing the original `String`, `Ok` otherwise.
32    pub fn new(string: String) -> Result<Self, String> {
33        if string.is_empty() {
34            Err(string)
35        } else {
36            Ok(NonEmptyString(string))
37        }
38    }
39    /// Creates a new `NonEmptyString`, assuming `string` is not empty.
40    ///
41    /// # Safety
42    /// If the given `string` is empty, it'll be undefined behavior.
43    pub unsafe fn new_unchecked(string: String) -> Self {
44        Self::new(string).unwrap_unchecked()
45    }
46
47    /// Returns a reference to the contained value.
48    pub fn get(&self) -> &str {
49        &self.0
50    }
51
52    /// Consume the `NonEmptyString` to get the internal `String` out.
53    pub fn into_inner(self) -> String {
54        self.0
55    }
56
57    // These are safe methods that can simply be forwarded.
58    delegate! {
59        to self.0 {
60            /// Is forwarded to the inner String.
61            /// See [`String::into_bytes`]
62            pub fn into_bytes(self) -> Vec<u8>;
63
64            /// Is forwarded to the inner String.
65            /// See [`String::as_str`]
66            pub fn as_str(&self) -> &str;
67
68            /// Is forwarded to the inner String.
69            /// See [`String::push_str`]
70            pub fn push_str(&mut self, string: &str);
71
72            /// Is forwarded to the inner String.
73            /// See [`String::capacity`]
74            pub fn capacity(&self) -> usize;
75
76            /// Is forwarded to the inner String.
77            /// See [`String::reserve`]
78            pub fn reserve(&mut self, additional: usize);
79
80            /// Is forwarded to the inner String.
81            /// See [`String::reserve_exact`]
82            pub fn reserve_exact(&mut self, additional: usize);
83
84            // For some reason we cannot delegate the following:
85            // pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError>
86
87            /// Is forwarded to the inner String.
88            /// See [`String::try_reserve_exact`]
89            pub fn try_reserve_exact(
90                &mut self,
91                additional: usize
92            ) -> Result<(), std::collections::TryReserveError>;
93
94            /// Is forwarded to the inner String.
95            /// See std::string::String::[`(&`]
96            pub fn shrink_to_fit(&mut self);
97
98            /// Is forwarded to the inner String.
99            /// See [`String::shrink_to`]
100            pub fn shrink_to(&mut self, min_capacity: usize);
101
102            /// Is forwarded to the inner String.
103            /// See [`String::push`]
104            pub fn push(&mut self, ch: char);
105
106            /// Is forwarded to the inner String.
107            /// See [`String::as_bytes`]
108            pub fn as_bytes(&self) -> &[u8];
109
110            /// Is forwarded to the inner String.
111            /// See [`String::insert`]
112            pub fn insert(&mut self, idx: usize, ch: char);
113
114            /// Is forwarded to the inner String.
115            /// See [`String::insert_str`]
116            pub fn insert_str(&mut self, idx: usize, string: &str);
117
118            /// Is forwarded to the inner String.
119            /// See [`String::len`]
120            pub fn len(&self) -> usize;
121
122            /// Is forwarded to the inner String.
123            /// See [`String::into_boxed_str`]
124            pub fn into_boxed_str(self) -> Box<str>;
125        }
126    }
127}
128
129impl AsRef<str> for NonEmptyString {
130    fn as_ref(&self) -> &str {
131        &self.0
132    }
133}
134
135impl AsRef<String> for NonEmptyString {
136    fn as_ref(&self) -> &String {
137        &self.0
138    }
139}
140
141impl<'s> TryFrom<&'s str> for NonEmptyString {
142    type Error = &'s str;
143
144    fn try_from(value: &'s str) -> Result<Self, Self::Error> {
145        if value.is_empty() {
146            return Err(value);
147        }
148
149        Ok(NonEmptyString(value.to_owned()))
150    }
151}
152
153impl TryFrom<String> for NonEmptyString {
154    type Error = String;
155
156    fn try_from(value: String) -> Result<Self, Self::Error> {
157        NonEmptyString::new(value)
158    }
159}
160
161impl FromStr for NonEmptyString {
162    type Err = <NonEmptyString as TryFrom<String>>::Error;
163
164    fn from_str(s: &str) -> Result<Self, Self::Err> {
165        <Self as TryFrom<String>>::try_from(s.to_string())
166    }
167}
168
169impl From<NonEmptyString> for String {
170    fn from(value: NonEmptyString) -> Self {
171        value.0
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use std::hash::Hash;
178
179    use super::*;
180
181    #[test]
182    fn empty_string_returns_err() {
183        assert_eq!(NonEmptyString::new("".to_owned()), Err("".to_owned()));
184    }
185
186    #[test]
187    fn non_empty_string_returns_ok() {
188        assert!(NonEmptyString::new("string".to_owned()).is_ok())
189    }
190
191    #[test]
192    fn what_goes_in_comes_out() {
193        assert_eq!(
194            NonEmptyString::new("string".to_owned())
195                .unwrap()
196                .into_inner(),
197            "string".to_owned()
198        );
199    }
200
201    #[test]
202    fn as_ref_str_works() {
203        let nes = NonEmptyString::new("string".to_owned()).unwrap();
204        let val: &str = nes.as_ref();
205        assert_eq!(val, "string");
206    }
207
208    #[test]
209    fn as_ref_string_works() {
210        let nes = NonEmptyString::new("string".to_owned()).unwrap();
211        let val: &String = nes.as_ref();
212        assert_eq!(val, "string");
213    }
214
215    #[test]
216    fn calling_string_methods_works() {
217        let nes = NonEmptyString::new("string".to_owned()).unwrap();
218        // `len` is a `String` method.
219        assert!(nes.len() > 0);
220    }
221
222    #[test]
223    fn format_test() {
224        let str = NonEmptyString::new("string".to_owned()).unwrap();
225        println!("{}", &str);
226        assert_eq!(String::from("string"), str.to_string())
227    }
228
229    #[test]
230    fn from_str_works() {
231        let valid_str = "string";
232        let non_empty_string = NonEmptyString::from_str(valid_str).unwrap();
233        let parsed: NonEmptyString = valid_str.parse().unwrap();
234        assert_eq!(non_empty_string, parsed);
235    }
236
237    #[test]
238    fn into_works() {
239        let non_empty_string = NonEmptyString::new("string".to_string()).unwrap();
240        let _string: String = non_empty_string.into();
241
242        let non_empty_string = NonEmptyString::new("string".to_string()).unwrap();
243        let _string = String::from(non_empty_string);
244    }
245
246    #[test]
247    fn hash_works() {
248        fn is_hash<T: Hash>() {}
249        is_hash::<NonEmptyString>();
250    }
251}