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}