Skip to main content

abin/string/
any_str.rs

1use core::fmt;
2use std::borrow::Borrow;
3use std::error::Error;
4use std::fmt::{Debug, Display, Formatter};
5use std::hash::{Hash, Hasher};
6use std::ops::{Bound, Deref, RangeBounds};
7use std::str::Utf8Error;
8
9use crate::{AnyBin, Bin, IntoSync, IntoUnSync, IntoUnSyncView, SBin};
10
11/// A utf-8 string backed by `AnyBin` (`Bin` or `SBin`), see also `Str` and `SStr`.
12pub struct AnyStr<TBin>(TBin);
13
14impl<TBin> AnyStr<TBin>
15where
16    TBin: AnyBin,
17{
18    /// Converts the given value to a string.
19    ///
20    /// The given value must be valid UTF-8. If the value is not valid UTF-8, this method
21    /// returns an error containing the original binary.
22    ///
23    /// See also: `core::str::from_utf8` / `std::string::String::from_utf8`.
24    ///
25    /// ```rust
26    /// use abin::{NewBin, BinFactory, AnyStr, Bin};
27    /// let bin : Bin = NewBin::from_static(&[65u8, 66u8, 67u8]);
28    /// let str : AnyStr<Bin> = AnyStr::from_utf8(bin).unwrap();
29    /// assert_eq!("ABC", str.as_str());
30    /// ```
31    #[inline]
32    pub fn from_utf8(value: impl Into<TBin>) -> Result<Self, AnyStrUtf8Error<TBin>> {
33        let value = value.into();
34        // check whether it's valid UTF8
35        if let Err(err) = core::str::from_utf8(value.as_slice()) {
36            Err(AnyStrUtf8Error::new(err, value))
37        } else {
38            // ok, valid UTF8
39            Ok(Self(value))
40        }
41    }
42
43    /// Creates a new string from given binary without checking whether the data in the given
44    /// binary is valid UTF-8.
45    ///
46    /// # Safety
47    ///
48    /// This function is unsafe because it does not check that the bytes passed to it are valid
49    /// UTF-8. If this constraint is violated, undefined behavior results, as `AnyStr`
50    /// assumes that it only contains valid UTF-8. See also `core::str::from_utf8_unchecked`.
51    #[inline]
52    pub unsafe fn from_utf8_unchecked(value: TBin) -> Self {
53        Self(value)
54    }
55
56    /// Returns `&str`. It's a cheap operation. See `AnyBin::as_slice`.
57    ///
58    /// ```rust
59    /// use abin::{Str, NewStr, StrFactory};
60    /// let string : Str = NewStr::from_static("Hello");
61    /// assert_eq!("Hello", string.as_str());
62    /// ```
63    #[inline]
64    pub fn as_str(&self) -> &str {
65        // no need to check utf8 again; we know it's valid (it's validated when constructed).
66        unsafe { core::str::from_utf8_unchecked(self.0.as_slice()) }
67    }
68
69    /// Extracts the wrapped binary.
70    ///
71    /// ```rust
72    /// use abin::{Str, NewStr, StrFactory, AnyBin, Bin};
73    /// let string : Str = NewStr::from_static("ABC");
74    /// let bin : Bin = string.into_bin();
75    /// assert_eq!(&[65u8, 66u8, 67u8], bin.as_slice());
76    /// ```
77    #[inline]
78    pub fn into_bin(self) -> TBin {
79        self.0
80    }
81
82    /// Wrapped binary as reference.
83    ///
84    /// ```rust
85    /// use abin::{Str, NewStr, StrFactory, AnyBin, Bin};
86    /// let string : Str = NewStr::from_static("ABC");
87    /// let bin : &Bin = string.as_bin();
88    /// assert_eq!(&[65u8, 66u8, 67u8], bin.as_slice());
89    /// ```
90    #[inline]
91    pub fn as_bin(&self) -> &TBin {
92        &self.0
93    }
94
95    /// `true` if this string is empty (number of utf-8 bytes is 0). See also `AnyBin::is_empty`.
96    ///
97    /// ```rust
98    /// use abin::{Str, NewStr, StrFactory};
99    /// let string : Str = NewStr::from_static("");
100    /// assert_eq!(true, string.is_empty());
101    /// ```
102    #[inline]
103    pub fn is_empty(&self) -> bool {
104        self.0.is_empty()
105    }
106
107    /// The number of utf-8 bytes in this string. See also `AnyBin::len`.
108    ///
109    /// ```rust
110    /// use abin::{Str, NewStr, StrFactory};
111    /// let string : Str = NewStr::from_static("Hello");
112    /// assert_eq!(5, string.len());
113    /// ```
114    #[inline]
115    pub fn len(&self) -> usize {
116        self.0.len()
117    }
118
119    /// Converts this into a `String`; depending on the type, this might allocate or not. See
120    /// also `AnyBin::into_vec`.
121    ///
122    /// ```rust
123    /// use abin::{Str, NewStr, StrFactory};
124    /// let string : Str = NewStr::from_static("Hello");
125    /// let std_string : String = string.into_string();
126    /// assert_eq!("Hello", &std_string);
127    /// ```
128    #[inline]
129    pub fn into_string(self) -> String {
130        let vec = self.0.into_vec();
131        unsafe { String::from_utf8_unchecked(vec) }
132    }
133
134    /// Returns a slice of this string.
135    ///
136    /// Returns `None` if:
137    ///
138    ///   - range is ouf of bounds.
139    ///   - or if the range does not lie on UTF-8 boundaries (see also `str::get`).
140    ///
141    /// See also `AnyBin::slice`.
142    ///
143    /// ```rust
144    /// use abin::{NewStr, StrFactory, Str};
145    /// let str : Str = NewStr::from_static("🗻∈🌏");
146    ///
147    /// // works
148    /// let slice1 : Option<Str> = str.slice(0..4);
149    /// assert_eq!(Some(NewStr::from_static("🗻")), slice1);
150    ///
151    /// // indices not on UTF-8 sequence boundaries
152    /// let slice2 : Option<Str> = str.slice(1..);
153    /// assert!(slice2.is_none());
154    /// let slice3 : Option<Str> = str.slice(..8);
155    /// assert!(slice3.is_none());
156    /// // out of bounds
157    /// let slice4 : Option<Str> = str.slice(..42);
158    /// assert!(slice4.is_none());
159    /// ```
160    #[inline]
161    pub fn slice<TRange>(&self, range: TRange) -> Option<Self>
162    where
163        TRange: RangeBounds<usize>,
164    {
165        let start = match range.start_bound() {
166            Bound::Included(start) => *start,
167            Bound::Excluded(start) => *start + 1,
168            Bound::Unbounded => 0,
169        };
170        let end_excluded = match range.end_bound() {
171            Bound::Included(end) => *end - 1,
172            Bound::Excluded(end) => *end,
173            Bound::Unbounded => self.len(),
174        };
175        // use str::get to check whether we're within range and lie on UTF-8 boundaries
176        if self.as_str().get(start..end_excluded).is_some() {
177            // ok
178            let sliced_bin = self.as_bin().slice(start..end_excluded);
179            if let Some(sliced_bin) = sliced_bin {
180                // we know it's valid UTF-8 (confirmed by `str::get`).
181                Some(unsafe { Self::from_utf8_unchecked(sliced_bin) })
182            } else {
183                None
184            }
185        } else {
186            None
187        }
188    }
189}
190
191/// See `AnyStr::into_string`.
192impl<TBin> Into<String> for AnyStr<TBin>
193where
194    TBin: AnyBin,
195{
196    #[inline]
197    fn into(self) -> String {
198        self.into_string()
199    }
200}
201
202impl<TBin> Hash for AnyStr<TBin>
203where
204    TBin: AnyBin,
205{
206    fn hash<H: Hasher>(&self, state: &mut H) {
207        self.as_str().hash(state)
208    }
209}
210
211impl<TBin> Debug for AnyStr<TBin>
212where
213    TBin: AnyBin,
214{
215    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
216        Debug::fmt(self.as_str(), f)
217    }
218}
219
220impl<TBin> Display for AnyStr<TBin>
221where
222    TBin: AnyBin,
223{
224    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
225        Display::fmt(self.as_str(), f)
226    }
227}
228
229impl<TBin> Clone for AnyStr<TBin>
230where
231    TBin: AnyBin,
232{
233    #[inline]
234    fn clone(&self) -> Self {
235        unsafe { AnyStr::from_utf8_unchecked(self.0.clone()) }
236    }
237}
238
239impl<TBin> Borrow<str> for AnyStr<TBin>
240where
241    TBin: AnyBin,
242{
243    #[inline]
244    fn borrow(&self) -> &str {
245        self.as_str()
246    }
247}
248
249impl<TBin> AsRef<str> for AnyStr<TBin>
250where
251    TBin: AnyBin,
252{
253    #[inline]
254    fn as_ref(&self) -> &str {
255        self.as_str()
256    }
257}
258
259impl<TBin> AsRef<TBin> for AnyStr<TBin>
260where
261    TBin: AnyBin,
262{
263    #[inline]
264    fn as_ref(&self) -> &TBin {
265        self.as_bin()
266    }
267}
268
269impl<TBin> Deref for AnyStr<TBin>
270where
271    TBin: AnyBin,
272{
273    type Target = str;
274
275    #[inline]
276    fn deref(&self) -> &Self::Target {
277        self.as_str()
278    }
279}
280
281impl<TBin> IntoUnSyncView for AnyStr<TBin>
282where
283    TBin: AnyBin,
284{
285    type Target = AnyStr<Bin>;
286
287    fn un_sync(self) -> Self::Target {
288        unsafe { Self::Target::from_utf8_unchecked(self.0.un_sync()) }
289    }
290}
291
292impl<TBin> IntoUnSync for AnyStr<TBin>
293where
294    TBin: AnyBin,
295{
296    type Target = AnyStr<Bin>;
297
298    fn un_sync_convert(self) -> Self::Target {
299        unsafe { Self::Target::from_utf8_unchecked(self.0.un_sync_convert()) }
300    }
301}
302
303impl<TBin> IntoSync for AnyStr<TBin>
304where
305    TBin: AnyBin,
306{
307    type Target = AnyStr<SBin>;
308
309    fn into_sync(self) -> Self::Target {
310        let bin = self.0;
311        let sync_bin = bin.into_sync();
312        unsafe { Self::Target::from_utf8_unchecked(sync_bin) }
313    }
314}
315
316/// Error returned when trying to create a `AnyStr` from a binary that contains invalid utf-8.
317#[derive(Debug, Clone)]
318pub struct AnyStrUtf8Error<TBin> {
319    utf8_error: Utf8Error,
320    binary: TBin,
321}
322
323impl<TBin> AnyStrUtf8Error<TBin> {
324    pub fn new(utf8_error: Utf8Error, binary: TBin) -> Self {
325        Self { utf8_error, binary }
326    }
327
328    pub fn utf8_error(&self) -> &Utf8Error {
329        &self.utf8_error
330    }
331
332    /// This can be used to retrieve the binary that has been used to construct the string.
333    pub fn deconstruct(self) -> (Utf8Error, TBin) {
334        (self.utf8_error, self.binary)
335    }
336}
337
338impl<TBin> Error for AnyStrUtf8Error<TBin> where TBin: AnyBin {}
339
340impl<TBin> Display for AnyStrUtf8Error<TBin>
341where
342    TBin: AnyBin,
343{
344    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
345        Display::fmt(&self.utf8_error, f)
346    }
347}