1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
45#[cfg(feature = "alloc")]
6use alloc::boxed::Box;
7use core::cmp::Ordering;
8use core::fmt;
9use core::ops::Deref;
1011/// A byte slice that is expected to be a UTF-8 string but does not enforce that invariant.
12///
13/// Use this type instead of `str` if you don't need to enforce UTF-8 during deserialization. For
14/// example, strings that are keys of a map don't need to ever be reified as `str`s.
15///
16/// [`PotentialUtf8`] derefs to `[u8]`. To obtain a `str`, use [`Self::try_as_str()`].
17///
18/// The main advantage of this type over `[u8]` is that it serializes as a string in
19/// human-readable formats like JSON.
20///
21/// # Examples
22///
23/// Using an [`PotentialUtf8`] as the key of a [`ZeroMap`]:
24///
25/// ```
26/// use potential_utf::PotentialUtf8;
27/// use zerovec::ZeroMap;
28///
29/// // This map is cheap to deserialize, as we don't need to perform UTF-8 validation.
30/// let map: ZeroMap<PotentialUtf8, u8> = [
31/// (PotentialUtf8::from_bytes(b"abc"), 11),
32/// (PotentialUtf8::from_bytes(b"def"), 22),
33/// (PotentialUtf8::from_bytes(b"ghi"), 33),
34/// ]
35/// .into_iter()
36/// .collect();
37///
38/// let key = "abc";
39/// let value = map.get_copied(PotentialUtf8::from_str(key));
40/// assert_eq!(Some(11), value);
41/// ```
42///
43/// [`ZeroMap`]: zerovec::ZeroMap
44#[repr(transparent)]
45#[derive(#[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::PartialEq for PotentialUtf8 {
#[inline]
fn eq(&self, other: &PotentialUtf8) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::Eq for PotentialUtf8 {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<[u8]>;
}
}Eq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::PartialOrd for PotentialUtf8 {
#[inline]
fn partial_cmp(&self, other: &PotentialUtf8)
-> ::core::option::Option<::core::cmp::Ordering> {
::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
}
}PartialOrd, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::Ord for PotentialUtf8 {
#[inline]
fn cmp(&self, other: &PotentialUtf8) -> ::core::cmp::Ordering {
::core::cmp::Ord::cmp(&self.0, &other.0)
}
}Ord)]
46#[allow(clippy::exhaustive_structs)] // transparent newtype
47pub struct PotentialUtf8(pub [u8]);
4849impl fmt::Debugfor PotentialUtf8 {
50fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51// Debug as a string if possible
52match self.try_as_str() {
53Ok(s) => fmt::Debug::fmt(s, f),
54Err(_) => fmt::Debug::fmt(&self.0, f),
55 }
56 }
57}
5859impl PotentialUtf8 {
60/// Create a [`PotentialUtf8`] from a byte slice.
61#[inline]
62pub const fn from_bytes(other: &[u8]) -> &Self {
63// Safety: PotentialUtf8 is transparent over [u8]
64unsafe { &*(otheras *const [u8] as *const Self) }
65 }
6667/// Create a [`PotentialUtf8`] from a string slice.
68#[inline]
69pub const fn from_str(s: &str) -> &Self {
70Self::from_bytes(s.as_bytes())
71 }
7273/// Create a [`PotentialUtf8`] from boxed bytes.
74 ///
75 /// ✨ *Enabled with the `alloc` Cargo feature.*
76#[inline]
77 #[cfg(feature = "alloc")]
78pub fn from_boxed_bytes(other: Box<[u8]>) -> Box<Self> {
79// Safety: PotentialUtf8 is transparent over [u8] therefore
80 // the cast between [u8] and Self is sound.
81 // Box::into_raw returns a well aligned pointer that is not
82 // null. The cast then changes the type, but the pointer
83 // is still well aligned, not null and created by the
84 // same global allocator, so it's safe to use Box::from_raw
85 // to create a new Box instance out of it.
86 //
87 // We cannot directly transmute the `Box` here as
88 // there is no gurantee about the layout of `Box` with
89 // unsized types.
90unsafe { Box::from_raw(Box::into_raw(other) as *mut Self) }
91 }
9293/// Create a [`PotentialUtf8`] from a boxed `str`.
94 ///
95 /// ✨ *Enabled with the `alloc` Cargo feature.*
96#[inline]
97 #[cfg(feature = "alloc")]
98pub fn from_boxed_str(other: Box<str>) -> Box<Self> {
99Self::from_boxed_bytes(other.into_boxed_bytes())
100 }
101102/// Get the bytes from a [`PotentialUtf8`].
103#[inline]
104pub const fn as_bytes(&self) -> &[u8] {
105&self.0
106}
107108/// Attempt to convert a [`PotentialUtf8`] to a `str`.
109 ///
110 /// # Examples
111 ///
112 /// ```
113 /// use potential_utf::PotentialUtf8;
114 ///
115 /// static A: &PotentialUtf8 = PotentialUtf8::from_bytes(b"abc");
116 ///
117 /// let b = A.try_as_str().unwrap();
118 /// assert_eq!(b, "abc");
119 /// ```
120// Note: this is const starting in 1.63
121#[inline]
122pub fn try_as_str(&self) -> Result<&str, core::str::Utf8Error> {
123 core::str::from_utf8(&self.0)
124 }
125}
126127impl<'a> From<&'a str> for &'a PotentialUtf8 {
128#[inline]
129fn from(other: &'a str) -> Self {
130PotentialUtf8::from_str(other)
131 }
132}
133134impl PartialEq<str> for PotentialUtf8 {
135fn eq(&self, other: &str) -> bool {
136self.eq(Self::from_str(other))
137 }
138}
139140impl PartialOrd<str> for PotentialUtf8 {
141fn partial_cmp(&self, other: &str) -> Option<Ordering> {
142self.partial_cmp(Self::from_str(other))
143 }
144}
145146impl PartialEq<PotentialUtf8> for str {
147fn eq(&self, other: &PotentialUtf8) -> bool {
148PotentialUtf8::from_str(self).eq(other)
149 }
150}
151152impl PartialOrd<PotentialUtf8> for str {
153fn partial_cmp(&self, other: &PotentialUtf8) -> Option<Ordering> {
154PotentialUtf8::from_str(self).partial_cmp(other)
155 }
156}
157158#[cfg(feature = "alloc")]
159impl From<Box<str>> for Box<PotentialUtf8> {
160#[inline]
161fn from(other: Box<str>) -> Self {
162 PotentialUtf8::from_boxed_str(other)
163 }
164}
165166impl Dereffor PotentialUtf8 {
167type Target = [u8];
168fn deref(&self) -> &Self::Target {
169&self.0
170}
171}
172173/// This impl requires enabling the optional `zerovec` Cargo feature
174#[cfg(all(feature = "zerovec", feature = "alloc"))]
175impl<'a> zerovec::maps::ZeroMapKV<'a> for PotentialUtf8 {
176type Container = zerovec::VarZeroVec<'a, PotentialUtf8>;
177type Slice = zerovec::VarZeroSlice<PotentialUtf8>;
178type GetType = PotentialUtf8;
179type OwnedType = Box<PotentialUtf8>;
180}
181182// Safety (based on the safety checklist on the VarULE trait):
183// 1. PotentialUtf8 does not include any uninitialized or padding bytes (transparent over a ULE)
184// 2. PotentialUtf8 is aligned to 1 byte (transparent over a ULE)
185// 3. The impl of `validate_bytes()` returns an error if any byte is not valid (impossible)
186// 4. The impl of `validate_bytes()` returns an error if the slice cannot be used in its entirety (impossible)
187// 5. The impl of `from_bytes_unchecked()` returns a reference to the same data (returns the argument directly)
188// 6. All other methods are defaulted
189// 7. `[T]` byte equality is semantic equality (transparent over a ULE)
190/// This impl requires enabling the optional `zerovec` Cargo feature
191#[cfg(feature = "zerovec")]
192unsafe impl zerovec::ule::VarULEfor PotentialUtf8 {
193#[inline]
194fn validate_bytes(_: &[u8]) -> Result<(), zerovec::ule::UleError> {
195Ok(())
196 }
197#[inline]
198unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self {
199PotentialUtf8::from_bytes(bytes)
200 }
201}
202203/// This impl requires enabling the optional `serde` Cargo feature
204#[cfg(feature = "serde")]
205impl serde_core::Serialize for PotentialUtf8 {
206fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
207where
208S: serde_core::Serializer,
209 {
210use serde_core::ser::Error;
211let s = self
212.try_as_str()
213 .map_err(|_| S::Error::custom("invalid UTF-8 in PotentialUtf8"))?;
214if serializer.is_human_readable() {
215 serializer.serialize_str(s)
216 } else {
217 serializer.serialize_bytes(s.as_bytes())
218 }
219 }
220}
221222/// This impl requires enabling the optional `serde` Cargo feature
223#[cfg(all(feature = "serde", feature = "alloc"))]
224impl<'de> serde_core::Deserialize<'de> for Box<PotentialUtf8> {
225fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
226where
227D: serde_core::Deserializer<'de>,
228 {
229if deserializer.is_human_readable() {
230let boxed_str = Box::<str>::deserialize(deserializer)?;
231Ok(PotentialUtf8::from_boxed_str(boxed_str))
232 } else {
233let boxed_bytes = Box::<[u8]>::deserialize(deserializer)?;
234Ok(PotentialUtf8::from_boxed_bytes(boxed_bytes))
235 }
236 }
237}
238239/// This impl requires enabling the optional `serde` Cargo feature
240#[cfg(feature = "serde")]
241impl<'de, 'a> serde_core::Deserialize<'de> for &'a PotentialUtf8
242where
243'de: 'a,
244{
245fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
246where
247D: serde_core::Deserializer<'de>,
248 {
249if deserializer.is_human_readable() {
250let s = <&str>::deserialize(deserializer)?;
251Ok(PotentialUtf8::from_str(s))
252 } else {
253let bytes = <&[u8]>::deserialize(deserializer)?;
254Ok(PotentialUtf8::from_bytes(bytes))
255 }
256 }
257}
258259/// A `u16` slice that is expected to be a UTF-16 string but does not enforce that invariant.
260///
261/// See [`PotentialUtf8`] for more info.
262#[repr(transparent)]
263#[derive(#[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::PartialEq for PotentialUtf16 {
#[inline]
fn eq(&self, other: &PotentialUtf16) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::Eq for PotentialUtf16 {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<[u16]>;
}
}Eq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::PartialOrd for PotentialUtf16 {
#[inline]
fn partial_cmp(&self, other: &PotentialUtf16)
-> ::core::option::Option<::core::cmp::Ordering> {
::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
}
}PartialOrd, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::Ord for PotentialUtf16 {
#[inline]
fn cmp(&self, other: &PotentialUtf16) -> ::core::cmp::Ordering {
::core::cmp::Ord::cmp(&self.0, &other.0)
}
}Ord)]
264#[allow(clippy::exhaustive_structs)] // transparent newtype
265pub struct PotentialUtf16(pub [u16]);
266267impl fmt::Debugfor PotentialUtf16 {
268fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269// Debug as a string if possible
270for c in char::decode_utf16(self.0.iter().copied()) {
271match c {
272Ok(c) => f.write_fmt(format_args!("{0}", c))write!(f, "{c}")?,
273Err(e) => f.write_fmt(format_args!("\\0x{0:x}", e.unpaired_surrogate()))write!(f, "\\0x{:x}", e.unpaired_surrogate())?,
274 }
275 }
276Ok(())
277 }
278}
279280impl PotentialUtf16 {
281/// Create a [`PotentialUtf16`] from a u16 slice.
282#[inline]
283pub const fn from_slice(other: &[u16]) -> &Self {
284// Safety: PotentialUtf16 is transparent over [u16]
285unsafe { &*(otheras *const [u16] as *const Self) }
286 }
287288/// Iterates the characters of the string.
289 ///
290 /// Returns [`char::REPLACEMENT_CHARACTER`] if invalid surrogates are encountered.
291pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
292char::decode_utf16(self.0.iter().copied()).map(|c| c.unwrap_or(char::REPLACEMENT_CHARACTER))
293 }
294}