non_empty_str/
owned.rs

1//! Non-empty [`String`].
2
3#[cfg(not(any(feature = "std", feature = "alloc")))]
4compile_error!("expected either `std` or `alloc` to be enabled");
5
6#[cfg(all(not(feature = "std"), feature = "alloc"))]
7use alloc::{borrow::ToOwned, string::String};
8
9use core::{borrow::Borrow, fmt, ops::Deref, str::FromStr};
10
11use thiserror::Error;
12
13use crate::str::{Empty, Str};
14
15/// The error message used when the owned string is empty.
16pub const EMPTY_OWNED: &str = "the owned string is empty";
17
18/// Similar to [`Empty`], but holds the empty string provided.
19///
20/// [`Empty`]: crate::str::Empty
21#[derive(Debug, Error)]
22#[error("{EMPTY_OWNED}")]
23#[cfg_attr(
24    feature = "diagnostics",
25    derive(miette::Diagnostic),
26    diagnostic(
27        code(non_empty_str::owned),
28        help("make sure the owned string is non-empty")
29    )
30)]
31pub struct EmptyOwned {
32    string: String,
33}
34
35impl EmptyOwned {
36    // NOTE: this is private to prevent creating this error with non-empty strings
37    const fn new(string: String) -> Self {
38        Self { string }
39    }
40
41    /// Returns the contained empty string.
42    #[must_use]
43    pub fn get(self) -> String {
44        self.string
45    }
46}
47
48/// Represents non-empty [`String`] values.
49#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
50#[repr(transparent)]
51pub struct OwnedStr {
52    inner: String,
53}
54
55impl Borrow<Str> for OwnedStr {
56    fn borrow(&self) -> &Str {
57        self.as_str()
58    }
59}
60
61impl fmt::Display for OwnedStr {
62    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
63        self.as_str().fmt(formatter)
64    }
65}
66
67impl TryFrom<String> for OwnedStr {
68    type Error = EmptyOwned;
69
70    fn try_from(value: String) -> Result<Self, Self::Error> {
71        Self::new(value)
72    }
73}
74
75impl From<OwnedStr> for String {
76    fn from(string: OwnedStr) -> Self {
77        string.get()
78    }
79}
80
81impl From<&Str> for OwnedStr {
82    fn from(string: &Str) -> Self {
83        Self::from_str(string)
84    }
85}
86
87impl AsRef<Str> for OwnedStr {
88    fn as_ref(&self) -> &Str {
89        self.as_str()
90    }
91}
92
93impl AsRef<str> for OwnedStr {
94    fn as_ref(&self) -> &str {
95        self.as_str().get()
96    }
97}
98
99impl FromStr for OwnedStr {
100    type Err = Empty;
101
102    fn from_str(string: &str) -> Result<Self, Self::Err> {
103        let non_empty = Str::try_from_str(string)?;
104
105        let owned = Self::from_str(non_empty);
106
107        Ok(owned)
108    }
109}
110
111impl Deref for OwnedStr {
112    type Target = Str;
113
114    fn deref(&self) -> &Self::Target {
115        self.as_str()
116    }
117}
118
119impl OwnedStr {
120    /// Constructs [`Self`], provided that the [`String`] is non-empty.
121    ///
122    /// # Errors
123    ///
124    /// Returns [`EmptyOwned`] if the string is empty.
125    ///
126    /// # Examples
127    ///
128    /// Basic snippet:
129    ///
130    /// ```
131    /// use non_empty_str::OwnedStr;
132    ///
133    /// let message = OwnedStr::new("Hello, world!".to_owned()).unwrap();
134    /// ```
135    ///
136    /// Handling possible errors and recovering empty strings:
137    ///
138    /// ```
139    /// use non_empty_str::OwnedStr;
140    ///
141    /// let empty_owned = OwnedStr::new(String::new()).unwrap_err();
142    ///
143    /// let empty = empty_owned.get();
144    /// ```
145    pub const fn new(string: String) -> Result<Self, EmptyOwned> {
146        if string.is_empty() {
147            return Err(EmptyOwned::new(string));
148        }
149
150        // SAFETY: the string is non-empty at this point
151        Ok(unsafe { Self::new_unchecked(string) })
152    }
153
154    /// Constructs [`Self`] without checking if the value is non-empty.
155    ///
156    /// # Safety
157    ///
158    /// The caller must ensure that the value is non-empty.
159    #[must_use]
160    pub const unsafe fn new_unchecked(inner: String) -> Self {
161        debug_assert!(!inner.is_empty());
162
163        Self { inner }
164    }
165
166    #[cfg(feature = "unsafe-assert")]
167    const fn assert_non_empty(&self) {
168        use core::hint::assert_unchecked;
169
170        // SAFETY: the string is non-empty by construction
171        unsafe {
172            assert_unchecked(!self.inner.is_empty());
173        }
174    }
175
176    /// Constructs [`Self`] from [`Str`] via cloning.
177    ///
178    /// # Examples
179    ///
180    /// Basic snippet:
181    ///
182    /// ```
183    /// use non_empty_str::{OwnedStr, Str};
184    ///
185    /// let nekit = Str::from_str("nekit").unwrap();
186    ///
187    /// let owned = OwnedStr::from_str(nekit);
188    /// ```
189    #[allow(clippy::should_implement_trait)]
190    #[must_use]
191    pub fn from_str(string: &Str) -> Self {
192        // SAFETY: the string is non-empty by construction
193        unsafe { Self::new_unchecked(string.get().to_owned()) }
194    }
195
196    /// Returns contained string reference as [`Str`].
197    #[must_use]
198    pub const fn as_str(&self) -> &Str {
199        // SAFETY: the string is non-empty by construction
200        unsafe { Str::from_str_unchecked(self.inner.as_str()) }
201    }
202
203    /// Returns the contained [`String`].
204    #[must_use]
205    pub fn get(self) -> String {
206        #[cfg(feature = "unsafe-assert")]
207        self.assert_non_empty();
208
209        self.inner
210    }
211}
212
213#[cfg(feature = "serde")]
214mod serde {
215    #[cfg(all(not(feature = "std"), feature = "alloc"))]
216    use alloc::string::String;
217
218    use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
219
220    use super::OwnedStr;
221
222    impl Serialize for OwnedStr {
223        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
224            self.as_str().serialize(serializer)
225        }
226    }
227
228    impl<'de> Deserialize<'de> for OwnedStr {
229        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
230            let string = String::deserialize(deserializer)?;
231
232            let non_empty = string.try_into().map_err(D::Error::custom)?;
233
234            Ok(non_empty)
235        }
236    }
237}