musli_utils/
macros.rs

1/// Generate extensions assuming an encoding has implemented encode_with.
2#[doc(hidden)]
3#[macro_export]
4macro_rules! encode_with_extensions {
5    ($mode:ident) => {
6        /// Encode the given value to the given [`Writer`] using the current
7        /// configuration.
8        #[inline]
9        pub fn encode<W, T>(self, writer: W, value: &T) -> Result<(), Error>
10        where
11            W: Writer,
12            T: ?Sized + Encode<$mode>,
13        {
14            $crate::allocator::with(|alloc| {
15                let cx = $crate::context::Same::new(alloc);
16                self.encode_with(&cx, writer, value)
17            })
18        }
19
20        /// Encode the given value to the given [Write][io::Write] using the current
21        /// configuration.
22        #[cfg(feature = "std")]
23        #[inline]
24        pub fn to_writer<W, T>(self, write: W, value: &T) -> Result<(), Error>
25        where
26            W: io::Write,
27            T: ?Sized + Encode<$mode>,
28        {
29            let writer = $crate::wrap::wrap(write);
30            self.encode(writer, value)
31        }
32
33        /// Encode the given value to the given [Write][io::Write] using the current
34        /// configuration and context `C`.
35        #[cfg(feature = "std")]
36        #[inline]
37        pub fn to_writer_with<C, W, T>(self, cx: &C, write: W, value: &T) -> Result<(), C::Error>
38        where
39            C: ?Sized + Context<Mode = $mode>,
40            W: io::Write,
41            T: ?Sized + Encode<$mode>,
42        {
43            let writer = $crate::wrap::wrap(write);
44            self.encode_with(cx, writer, value)
45        }
46
47        /// Encode the given value to a [`Vec`] using the current configuration.
48        #[cfg(feature = "alloc")]
49        #[inline]
50        pub fn to_vec<T>(self, value: &T) -> Result<Vec<u8>, Error>
51        where
52            T: ?Sized + Encode<$mode>,
53        {
54            let mut vec = Vec::new();
55            self.encode(&mut vec, value)?;
56            Ok(vec)
57        }
58
59        /// Encode the given value to a [`Vec`] using the current configuration.
60        ///
61        /// This is the same as [`Encoding::to_vec`], but allows for using a
62        /// configurable [`Context`].
63        #[cfg(feature = "alloc")]
64        #[inline]
65        pub fn to_vec_with<C, T>(self, cx: &C, value: &T) -> Result<Vec<u8>, C::Error>
66        where
67            C: ?Sized + Context<Mode = $mode>,
68            T: ?Sized + Encode<$mode>,
69        {
70            let mut vec = Vec::new();
71            self.encode_with(cx, &mut vec, value)?;
72            Ok(vec)
73        }
74
75        /// Encode the given value to a fixed-size bytes using the current
76        /// configuration.
77        #[inline]
78        pub fn to_fixed_bytes<const N: usize, T>(self, value: &T) -> Result<FixedBytes<N>, Error>
79        where
80            T: ?Sized + Encode<$mode>,
81        {
82            $crate::allocator::with(|alloc| {
83                let cx = $crate::context::Same::new(alloc);
84                self.to_fixed_bytes_with(&cx, value)
85            })
86        }
87
88        /// Encode the given value to a fixed-size bytes using the current
89        /// configuration.
90        #[inline]
91        pub fn to_fixed_bytes_with<C, const N: usize, T>(
92            self,
93            cx: &C,
94            value: &T,
95        ) -> Result<FixedBytes<N>, C::Error>
96        where
97            C: ?Sized + Context<Mode = $mode>,
98            T: ?Sized + Encode<$mode>,
99        {
100            let mut bytes = FixedBytes::new();
101            self.encode_with(cx, &mut bytes, value)?;
102            Ok(bytes)
103        }
104    };
105}
106
107/// Generate all public encoding helpers.
108#[doc(hidden)]
109#[macro_export]
110macro_rules! encoding_from_slice_impls {
111    ($mode:ident) => {
112        /// Decode the given type `T` from the given slice using the current
113        /// configuration.
114        #[inline]
115        pub fn from_slice<'de, T>(self, bytes: &'de [u8]) -> Result<T, Error>
116        where
117            T: Decode<'de, $mode>,
118        {
119            $crate::allocator::with(|alloc| {
120                let cx = $crate::context::Same::new(alloc);
121                self.from_slice_with(&cx, bytes)
122            })
123        }
124
125        /// Decode the given type `T` from the given slice using the current
126        /// configuration.
127        ///
128        /// This is the same as [`Encoding::from_slice`], but allows for using a
129        /// configurable [`Context`].
130        #[inline]
131        pub fn from_slice_with<'de, C, T>(self, cx: &C, bytes: &'de [u8]) -> Result<T, C::Error>
132        where
133            C: ?Sized + Context<Mode = $mode>,
134            T: Decode<'de, $mode>,
135        {
136            let reader = $crate::reader::SliceReader::new(bytes);
137            self.decode_with(cx, reader)
138        }
139    };
140}
141
142/// Generate all public encoding helpers.
143#[doc(hidden)]
144#[macro_export]
145macro_rules! encoding_impls {
146    ($mode:ident, $encoder_new:path, $decoder_new:path) => {
147        /// Encode the given value to the given [`Writer`] using the current
148        /// configuration.
149        ///
150        /// This is the same as [`Encoding::encode`] but allows for using a
151        /// configurable [`Context`].
152        #[inline]
153        pub fn encode_with<C, W, T>(self, cx: &C, writer: W, value: &T) -> Result<(), C::Error>
154        where
155            C: ?Sized + Context<Mode = $mode>,
156            W: Writer,
157            T: ?Sized + Encode<$mode>,
158        {
159            cx.clear();
160            T::encode(value, cx, $encoder_new(cx, writer))
161        }
162
163        /// Decode the given type `T` from the given [`Reader`] using the
164        /// current configuration.
165        ///
166        /// This is the same as [`Encoding::decode`] but allows for using a
167        /// configurable [`Context`].
168        #[inline]
169        pub fn decode_with<'de, C, R, T>(self, cx: &C, reader: R) -> Result<T, C::Error>
170        where
171            C: ?Sized + Context<Mode = $mode>,
172            R: Reader<'de>,
173            T: Decode<'de, $mode>,
174        {
175            cx.clear();
176            T::decode(cx, $decoder_new(cx, reader))
177        }
178
179        /// Decode the given type `T` from the given [`Reader`] using the
180        /// current configuration.
181        #[inline]
182        pub fn decode<'de, R, T>(self, reader: R) -> Result<T, Error>
183        where
184            R: Reader<'de>,
185            T: Decode<'de, $mode>,
186        {
187            $crate::allocator::with(|alloc| {
188                let cx = $crate::context::Same::new(alloc);
189                self.decode_with(&cx, reader)
190            })
191        }
192
193        $crate::encode_with_extensions!($mode);
194    };
195}
196
197#[doc(hidden)]
198#[macro_export]
199macro_rules! test_include_if {
200    (#[musli_value] => $($rest:tt)*) => { $($rest)* };
201    (=> $($_:tt)*) => {};
202}
203
204/// Generate test functions which provides rich diagnostics when they fail.
205#[doc(hidden)]
206#[macro_export]
207#[allow(clippy::crate_in_macro_def)]
208macro_rules! test_fns {
209    ($what:expr, $mode:ty $(, $(#[$option:ident])*)?) => {
210        /// Roundtrip encode the given value.
211        #[doc(hidden)]
212        #[track_caller]
213        #[cfg(feature = "test")]
214        pub fn rt<T>(value: T) -> T
215        where
216            T: ::musli::en::Encode<$mode> + ::musli::de::DecodeOwned<$mode>,
217            T: ::core::fmt::Debug + ::core::cmp::PartialEq,
218        {
219            const WHAT: &str = $what;
220            const ENCODING: crate::Encoding = crate::Encoding::new();
221
222            use ::core::any::type_name;
223            use ::alloc::string::ToString;
224
225            struct FormatBytes<'a>(&'a [u8]);
226
227            impl ::core::fmt::Display for FormatBytes<'_> {
228                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
229                    write!(f, "b\"")?;
230
231                    for b in self.0 {
232                        if b.is_ascii_graphic() {
233                            write!(f, "{}", *b as char)?;
234                        } else {
235                            write!(f, "\\x{b:02x}")?;
236                        }
237                    }
238
239                    write!(f, "\" (0-{})", self.0.len())?;
240                    Ok(())
241                }
242            }
243
244            $crate::allocator::with(|alloc| {
245                let mut cx = $crate::context::SystemContext::new(alloc);
246                cx.include_type();
247
248                let out = match ENCODING.to_vec_with(&cx, &value) {
249                    Ok(out) => out,
250                    Err(..) => {
251                        let error = cx.report();
252                        panic!("{WHAT}: {}: failed to encode:\n{error}", type_name::<T>())
253                    }
254                };
255
256                let decoded: T = match ENCODING.from_slice_with(&cx, out.as_slice()) {
257                    Ok(decoded) => decoded,
258                    Err(..) => {
259                        let out = FormatBytes(&out);
260                        let error = cx.report();
261                        panic!("{WHAT}: {}: failed to decode:\nValue: {value:?}\nBytes: {out}\n{error}", type_name::<T>())
262                    }
263                };
264
265                assert_eq!(decoded, value, "{WHAT}: {}: roundtrip does not match\nValue: {value:?}", type_name::<T>());
266
267                $crate::test_include_if! {
268                    $($(#[$option])*)* =>
269                    let value_decode: ::musli_value::Value = match ENCODING.from_slice_with(&cx, out.as_slice()) {
270                        Ok(decoded) => decoded,
271                        Err(..) => {
272                            let out = FormatBytes(&out);
273                            let error = cx.report();
274                            panic!("{WHAT}: {}: failed to decode to value type:\nValue: {value:?}\nBytes:{out}\n{error}", type_name::<T>())
275                        }
276                    };
277
278                    let value_decoded: T = match ::musli_value::decode_with(&cx, &value_decode) {
279                        Ok(decoded) => decoded,
280                        Err(..) => {
281                            let out = FormatBytes(&out);
282                            let error = cx.report();
283                            panic!("{WHAT}: {}: failed to decode from value type:\nValue: {value:?}\nBytes: {out}\nBuffered value: {value_decode:?}\n{error}", type_name::<T>())
284                        }
285                    };
286
287                    assert_eq!(value_decoded, value, "{WHAT}: {}: musli-value roundtrip does not match\nValue: {value:?}", type_name::<T>());
288                }
289
290                decoded
291            })
292        }
293
294        /// Encode and then decode the given value once.
295        #[doc(hidden)]
296        #[track_caller]
297        #[cfg(feature = "test")]
298        pub fn decode<'de, T, U>(value: T, out: &'de mut ::alloc::vec::Vec<u8>, expected: &U) -> U
299        where
300            T: ::musli::en::Encode<$mode>,
301            T: ::core::fmt::Debug + ::core::cmp::PartialEq,
302            U: ::musli::de::Decode<'de, $mode>,
303            U: ::core::fmt::Debug + ::core::cmp::PartialEq,
304        {
305            const WHAT: &str = $what;
306            const ENCODING: crate::Encoding = crate::Encoding::new();
307
308            use ::core::any::type_name;
309            use ::alloc::string::ToString;
310
311            struct FormatBytes<'a>(&'a [u8]);
312
313            impl ::core::fmt::Display for FormatBytes<'_> {
314                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
315                    write!(f, "b\"")?;
316
317                    for b in self.0 {
318                        if b.is_ascii_graphic() {
319                            write!(f, "{}", *b as char)?;
320                        } else {
321                            write!(f, "\\x{b:02x}")?;
322                        }
323                    }
324
325                    write!(f, "\" (0-{})", self.0.len())?;
326                    Ok(())
327                }
328            }
329
330            $crate::allocator::with(|alloc| {
331                let mut cx = $crate::context::SystemContext::new(alloc);
332                cx.include_type();
333
334                out.clear();
335
336                match ENCODING.to_writer_with(&cx, &mut *out, &value) {
337                    Ok(()) => (),
338                    Err(..) => {
339                        let error = cx.report();
340                        panic!("{WHAT}: {}: failed to encode:\n{error}", type_name::<T>())
341                    }
342                };
343
344                let actual = match ENCODING.from_slice_with(&cx, &*out) {
345                    Ok(decoded) => decoded,
346                    Err(error) => {
347                        let out = FormatBytes(&*out);
348                        let error = cx.report();
349                        panic!("{WHAT}: {}: failed to decode:\nValue: {value:?}\nBytes: {out}\n{error}", type_name::<T>())
350                    }
351                };
352
353                assert_eq!(
354                    actual,
355                    *expected,
356                    "{WHAT}: decoded value does not match expected\nBytes: {}",
357                    FormatBytes(&*out),
358                );
359
360                actual
361            })
362        }
363
364        /// Encode a value to bytes.
365        #[doc(hidden)]
366        #[track_caller]
367        #[cfg(feature = "test")]
368        pub fn to_vec<T>(value: T) -> ::alloc::vec::Vec<u8>
369        where
370            T: ::musli::en::Encode<$mode>,
371        {
372            const WHAT: &str = $what;
373            const ENCODING: crate::Encoding = crate::Encoding::new();
374
375            use ::core::any::type_name;
376            use ::alloc::string::ToString;
377
378            $crate::allocator::with(|alloc| {
379                let mut cx = $crate::context::SystemContext::new(alloc);
380                cx.include_type();
381
382                match ENCODING.to_vec_with(&cx, &value) {
383                    Ok(out) => out,
384                    Err(..) => {
385                        let error = cx.report();
386                        panic!("{WHAT}: {}: failed to encode:\n{error}", type_name::<T>())
387                    }
388                }
389            })
390        }
391    }
392}
393
394/// Expands to a `str` module which provides local and lightweight simdutf8
395/// compatibility functions.
396#[doc(hidden)]
397#[macro_export]
398macro_rules! simdutf8 {
399    () => {
400        pub(crate) mod str {
401            //! Functions for working with strings. The exported implementations change
402            //! depending on if the `simdutf8` feature is enabled.
403
404            #[cfg(feature = "alloc")]
405            use alloc::string::String;
406            #[cfg(feature = "alloc")]
407            use alloc::vec::Vec;
408
409            use core::fmt;
410
411            #[cfg(not(feature = "simdutf8"))]
412            #[doc(inline)]
413            pub use core::str::from_utf8;
414            #[cfg(feature = "simdutf8")]
415            #[doc(inline)]
416            pub use simdutf8::basic::from_utf8;
417
418            /// Error raised in case the UTF-8 sequence could not be decoded.
419            #[non_exhaustive]
420            #[derive(Debug)]
421            pub struct Utf8Error;
422
423            #[cfg(feature = "std")]
424            impl std::error::Error for Utf8Error {}
425
426            impl fmt::Display for Utf8Error {
427                #[inline]
428                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429                    write!(f, "invalid or incomplete utf-8 sequence")
430                }
431            }
432
433            /// The same as [`String::from_utf8`], but the implementation can different
434            /// depending on if the `simdutf8` feature is enabled.
435            ///
436            /// [`String::from_utf8`]: alloc::string::String::from_utf8
437            #[inline(always)]
438            #[cfg(all(feature = "alloc", not(feature = "simdutf8")))]
439            pub fn from_utf8_owned(bytes: Vec<u8>) -> Result<String, Utf8Error> {
440                match String::from_utf8(bytes) {
441                    Ok(string) => Ok(string),
442                    Err(..) => Err(Utf8Error),
443                }
444            }
445
446            /// The same as [`String::from_utf8`], but the implementation can different
447            /// depending on if the `simdutf8` feature is enabled.
448            ///
449            /// [`String::from_utf8`]: alloc::string::String::from_utf8
450            #[inline(always)]
451            #[cfg(all(feature = "alloc", feature = "simdutf8"))]
452            pub fn from_utf8_owned(bytes: Vec<u8>) -> Result<String, Utf8Error> {
453                if from_utf8(&bytes).is_err() {
454                    return Err(Utf8Error);
455                }
456
457                // SAFETY: String was checked above.
458                Ok(unsafe { String::from_utf8_unchecked(bytes) })
459            }
460        }
461    };
462}