1use std::{
2 borrow::Cow,
3 fmt::{self, Formatter},
4 ops::Deref,
5};
6
7use hstr::wtf8::{Wtf8, Wtf8Buf};
8use serde::Serializer;
9
10use crate::Atom;
11
12#[derive(Clone, Default, PartialEq, Eq, Hash)]
17#[repr(transparent)]
18pub struct Wtf8Atom(pub(super) hstr::Wtf8Atom);
19
20#[cfg(feature = "encoding-impl")]
21impl cbor4ii::core::enc::Encode for Wtf8Atom {
22 #[inline]
23 fn encode<W: cbor4ii::core::enc::Write>(
24 &self,
25 writer: &mut W,
26 ) -> Result<(), cbor4ii::core::enc::Error<W::Error>> {
27 cbor4ii::core::types::Bytes(self.as_bytes()).encode(writer)
28 }
29}
30
31#[cfg(feature = "encoding-impl")]
32impl<'de> cbor4ii::core::dec::Decode<'de> for Wtf8Atom {
33 #[inline]
34 fn decode<R: cbor4ii::core::dec::Read<'de>>(
35 reader: &mut R,
36 ) -> Result<Self, cbor4ii::core::dec::Error<R::Error>> {
37 let s = <cbor4ii::core::types::Bytes<&[u8]>>::decode(reader)?;
38
39 Ok(Self(hstr::Wtf8Atom::from(unsafe {
41 Wtf8Buf::from_bytes_unchecked(s.0.into())
42 })))
43 }
44}
45
46#[cfg(feature = "arbitrary")]
47#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
48impl<'a> arbitrary::Arbitrary<'a> for Wtf8Atom {
49 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
50 let sym = u.arbitrary::<Vec<u8>>()?;
51 if sym.is_empty() {
52 return Err(arbitrary::Error::NotEnoughData);
53 }
54 Ok(Self(hstr::Wtf8Atom::from(unsafe {
55 Wtf8Buf::from_bytes_unchecked(sym)
56 })))
57 }
58}
59
60fn _asserts() {
61 fn _assert_send<T: Send>() {}
64 fn _assert_sync<T: Sync>() {}
65
66 _assert_sync::<Wtf8Atom>();
67 _assert_send::<Wtf8Atom>();
68}
69
70impl Wtf8Atom {
71 #[inline(always)]
73 pub fn new<S>(s: S) -> Self
74 where
75 hstr::Wtf8Atom: From<S>,
76 {
77 Wtf8Atom(hstr::Wtf8Atom::from(s))
78 }
79
80 pub fn as_wtf8(&self) -> &Wtf8 {
81 &self.0
82 }
83
84 pub fn as_atom(&self) -> Option<&Atom> {
85 if self.as_str().is_some() {
86 Some(unsafe { &*(self as *const Wtf8Atom as *const Atom) })
87 } else {
88 None
89 }
90 }
91
92 pub fn to_atom_lossy(&self) -> Cow<'_, Atom> {
94 if let Some(atom) = self.as_atom() {
95 return Cow::Borrowed(atom);
96 }
97 Cow::Owned(Atom::new(self.to_string_lossy()))
98 }
99
100 pub fn try_into_atom(self) -> Result<Atom, Wtf8Atom> {
105 self.0.try_into_atom().map(Atom).map_err(Wtf8Atom)
106 }
107
108 pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self {
117 Wtf8Atom(hstr::Wtf8Atom::from(
118 hstr::wtf8::Wtf8::from_bytes_unchecked(bytes),
119 ))
120 }
121}
122
123impl Deref for Wtf8Atom {
124 type Target = Wtf8;
125
126 #[inline]
127 fn deref(&self) -> &Self::Target {
128 &self.0
129 }
130}
131
132impl fmt::Debug for Wtf8Atom {
133 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
134 fmt::Debug::fmt(&**self, f)
135 }
136}
137
138impl PartialOrd for Wtf8Atom {
139 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
140 Some(self.cmp(other))
141 }
142}
143
144impl Ord for Wtf8Atom {
145 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
146 self.as_wtf8().cmp(other.as_wtf8())
147 }
148}
149
150impl<T> PartialEq<T> for Wtf8Atom
151where
152 hstr::Wtf8Atom: PartialEq<T>,
153 T: ?Sized,
154{
155 fn eq(&self, other: &T) -> bool {
156 self.0.eq(other)
157 }
158}
159
160impl<T> From<T> for Wtf8Atom
161where
162 hstr::Wtf8Atom: From<T>,
163{
164 fn from(s: T) -> Self {
165 Wtf8Atom::new(s)
166 }
167}
168
169impl From<&Wtf8Atom> for Wtf8Buf {
170 fn from(s: &Wtf8Atom) -> Self {
171 unsafe { Wtf8Buf::from_bytes_unchecked(s.as_bytes().to_vec()) }
173 }
174}
175
176impl serde::ser::Serialize for Wtf8Atom {
177 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
178 where
179 S: Serializer,
180 {
181 self.0.serialize(serializer)
182 }
183}
184
185impl<'de> serde::de::Deserialize<'de> for Wtf8Atom {
186 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
187 where
188 D: serde::Deserializer<'de>,
189 {
190 hstr::Wtf8Atom::deserialize(deserializer).map(Wtf8Atom)
191 }
192}
193
194#[cfg(feature = "shrink-to-fit")]
196impl shrink_to_fit::ShrinkToFit for Wtf8Atom {
197 #[inline(always)]
198 fn shrink_to_fit(&mut self) {}
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn to_atom_lossy_returns_borrowed_for_utf8() {
207 let atom = Atom::from("swc");
208 let wtf = Wtf8Atom::from(atom.clone());
209
210 match wtf.to_atom_lossy() {
211 Cow::Borrowed(borrowed) => assert_eq!(borrowed, &atom),
212 Cow::Owned(_) => panic!("expected a borrowed Atom for valid UTF-8 input"),
213 }
214 }
215
216 #[test]
217 fn to_atom_lossy_returns_owned_for_invalid_utf8() {
218 let invalid_bytes = vec![0xed, 0xa0, 0x80];
219 let invalid = unsafe { Wtf8Buf::from_bytes_unchecked(invalid_bytes.clone()) };
220 let wtf = Wtf8Atom::new(invalid);
221
222 let lossy = wtf.to_string_lossy();
223
224 match wtf.to_atom_lossy() {
225 Cow::Borrowed(_) => panic!("expected an owned Atom for invalid UTF-8 input"),
226 Cow::Owned(atom) => {
227 assert_eq!(atom.as_ref(), lossy);
228 }
229 }
230 }
231}