Skip to main content

musli/
options.rs

1//! Serialization options.
2
3use core::fmt;
4
5/// [`Options`] builder.
6pub struct Builder(Options);
7
8const DEFAULT: Options = (ByteOrder::Little as Options) << BYTEORDER_BIT;
9
10/// Start building new options.
11///
12/// Call [`Builder::build`] to construct them.
13///
14/// # Examples
15///
16/// ```
17/// use musli::options::{self, Integer, Options};
18///
19/// const OPTIONS: Options = options::new().integer(Integer::Fixed).build();
20/// ```
21#[inline]
22pub const fn new() -> Builder {
23    Builder(DEFAULT)
24}
25
26/// Construct a [`Builder`] from the raw underlying value of an [`Options`].
27///
28/// This can be used to modify a value at compile time.
29///
30/// # Examples
31///
32/// ```
33/// use musli::options::{self, Float, Options};
34///
35/// const BASE: Options = options::new().build();
36/// const MODIFIED: Options = options::from_raw(BASE).float(Float::Fixed).build();
37/// ```
38#[inline]
39pub const fn from_raw(value: Options) -> Builder {
40    Builder(value)
41}
42
43/// Type encapsulating a static options for an encoding.
44///
45/// Note: despite being made up of a primitive type, this cannot be serialized
46/// and correctly re-used. This is simply the case because of restrictions in
47/// constant evaluation.
48///
49/// Making assumptions about its layout might lead to unspecified behavior
50/// during encoding. Only use this type through the provided [`options`] APIs.
51///
52/// [`options`]: crate::options
53pub type Options = u32;
54
55const BYTEORDER_BIT: Options = 0;
56const INTEGER_BIT: Options = 4;
57const FLOAT_BIT: Options = 8;
58const LENGTH_BIT: Options = 12;
59const MAP_KEYS_AS_NUMBERS_BIT: Options = 16;
60
61impl Builder {
62    /// Indicates if an integer serialization should be variable.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use musli::options::{self, Integer, Options};
68    ///
69    /// const OPTIONS: Options = options::new().integer(Integer::Fixed).build();
70    /// ```
71    #[inline]
72    pub const fn integer(self, integer: Integer) -> Self {
73        const MASK: Options = Integer::MASK << INTEGER_BIT;
74        Self((self.0 & !MASK) | ((integer as Options) << INTEGER_BIT))
75    }
76
77    /// Indicates the configuration of float serialization.
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use musli::options::{self, Float, Options};
83    ///
84    /// const OPTIONS: Options = options::new().float(Float::Fixed).build();
85    /// ```
86    #[inline]
87    pub const fn float(self, float: Float) -> Self {
88        const MASK: Options = Float::MASK << FLOAT_BIT;
89        Self((self.0 & !MASK) | ((float as Options) << FLOAT_BIT))
90    }
91
92    /// Specify which byte order to use.
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use musli::options::{self, ByteOrder, Options};
98    ///
99    /// const OPTIONS: Options = options::new().byte_order(ByteOrder::Little).build();
100    /// ```
101    #[inline]
102    pub const fn byte_order(self, byte_order: ByteOrder) -> Self {
103        const MASK: Options = ByteOrder::MASK << BYTEORDER_BIT;
104        Self((self.0 & !MASK) | ((byte_order as Options) << BYTEORDER_BIT))
105    }
106
107    /// Specify that the [`ByteOrder::NATIVE`] byte order should be used.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use musli::options::{self, Width, Options};
113    ///
114    /// const OPTIONS: Options = options::new().native_byte_order().build();
115    /// ```
116    #[inline]
117    pub const fn native_byte_order(self) -> Self {
118        self.byte_order(ByteOrder::NATIVE)
119    }
120
121    /// Sets the way in which pointer-like `usize` and `isize` types are
122    /// encoded.
123    ///
124    /// # Examples
125    ///
126    /// ```
127    /// use musli::options::{self, Width, Options};
128    ///
129    /// const OPTIONS: Options = options::new().pointer(Width::U32).build();
130    /// ```
131    #[inline]
132    pub const fn pointer(self, width: Width) -> Self {
133        const MASK: Options = Width::MASK << LENGTH_BIT;
134        Self((self.0 & !MASK) | ((width as Options) << LENGTH_BIT))
135    }
136
137    /// Configured a format to use numbers as map keys.
138    ///
139    /// This options is used for an encoding such as JSON to allow for storing
140    /// numbers as string keys, since this would otherwise not be possible and
141    /// cause an error.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use musli::options::{self, Options};
147    ///
148    /// const OPTIONS: Options = options::new().map_keys_as_numbers().build();
149    /// ```
150    #[inline]
151    pub const fn map_keys_as_numbers(self) -> Self {
152        const MASK: Options = 0b1 << MAP_KEYS_AS_NUMBERS_BIT;
153        Self((self.0 & !MASK) | (1 << MAP_KEYS_AS_NUMBERS_BIT))
154    }
155
156    /// Configure the options to use fixed serialization.
157    ///
158    /// This causes numerical types to use the default fixed-length
159    /// serialization which is typically more efficient than variable-length
160    /// through [`variable()`] but is less compact.
161    ///
162    /// This is the same as calling [`integer(Integer::Fixed)`],
163    /// [`float(Float::Fixed)`], and [`pointer(Width::NATIVE)`].
164    ///
165    /// [`variable()`]: Builder::variable
166    /// [`integer(Integer::Fixed)`]: Builder::integer
167    /// [`float(Float::Fixed)`]: Builder::float
168    /// [`pointer(Width::NATIVE)`]: Builder::pointer
169    ///
170    /// # Examples
171    ///
172    /// ```
173    /// use musli::options::{self, Options};
174    ///
175    /// const OPTIONS: Options = options::new().fixed().build();
176    /// ```
177    #[inline]
178    pub const fn fixed(self) -> Self {
179        self.integer(Integer::Fixed)
180            .float(Float::Fixed)
181            .pointer(Width::NATIVE)
182    }
183
184    /// Configure the options to use variable serialization.
185    ///
186    /// This causes numerical types to use the default variable-length
187    /// serialization which is typically less efficient than fixed-length
188    /// through [`fixed()`] but is more compact.
189    ///
190    /// This is the same as calling [`integer(Integer::Variable)`],
191    /// [`float(Float::Variable)`], and [`pointer(Width::Variable)`].
192    ///
193    /// [`fixed()`]: Builder::fixed
194    /// [`integer(Integer::Variable)`]: Builder::integer
195    /// [`float(Float::Variable)`]: Builder::float
196    /// [`pointer(Width::Variable)`]: Builder::pointer
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// use musli::options::{self, Options};
202    ///
203    /// const OPTIONS: Options = options::new().variable().build();
204    /// ```
205    #[inline]
206    pub const fn variable(self) -> Self {
207        self.integer(Integer::Variable)
208            .float(Float::Variable)
209            .pointer(Width::Variable)
210    }
211
212    /// Built an options builder into a constant.
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// use musli::options::{self, Options};
218    ///
219    /// const OPTIONS: Options = options::new().variable().build();
220    /// ```
221    #[inline]
222    pub const fn build(self) -> Options {
223        self.0
224    }
225}
226
227impl fmt::Debug for Builder {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        f.debug_struct("Builder")
230            .field("byteorder", &byteorder_value(self.0))
231            .field("integer", &integer_value(self.0))
232            .field("float", &float_value(self.0))
233            .field("length", &length_value(self.0))
234            .field(
235                "is_map_keys_as_numbers",
236                &is_map_keys_as_numbers_value(self.0),
237            )
238            .finish()
239    }
240}
241
242#[cfg(any(
243    feature = "storage",
244    feature = "wire",
245    feature = "descriptive",
246    feature = "json",
247    feature = "value"
248))]
249#[inline]
250pub(crate) const fn integer<const OPT: Options>() -> Integer {
251    integer_value(OPT)
252}
253
254#[inline]
255const fn integer_value(opt: Options) -> Integer {
256    match (opt >> INTEGER_BIT) & 0b1 {
257        0 => Integer::Variable,
258        _ => Integer::Fixed,
259    }
260}
261
262#[cfg(any(
263    feature = "storage",
264    feature = "wire",
265    feature = "descriptive",
266    feature = "json",
267    feature = "value"
268))]
269#[inline]
270pub(crate) const fn float<const OPT: Options>() -> Float {
271    float_value(OPT)
272}
273
274#[inline]
275const fn float_value(opt: Options) -> Float {
276    match (opt >> FLOAT_BIT) & 0b11 {
277        0b00 => Float::Integer,
278        0b01 => Float::Variable,
279        0b10 => Float::Fixed,
280        _ => Float::Pad0,
281    }
282}
283
284#[cfg(any(
285    feature = "storage",
286    feature = "wire",
287    feature = "descriptive",
288    feature = "json",
289    feature = "value"
290))]
291#[inline]
292pub(crate) const fn length<const OPT: Options>() -> Width {
293    length_value(OPT)
294}
295
296#[inline]
297const fn length_value(opt: Options) -> Width {
298    match (opt >> LENGTH_BIT) & 0b111 {
299        0b000 => Width::Variable,
300        0b001 => Width::U8,
301        0b010 => Width::U16,
302        0b011 => Width::U32,
303        0b100 => Width::U64,
304        0b101 => Width::Pad0,
305        0b110 => Width::Pad1,
306        _ => Width::Pad2,
307    }
308}
309
310#[cfg(any(
311    feature = "storage",
312    feature = "wire",
313    feature = "descriptive",
314    feature = "json",
315    feature = "value"
316))]
317#[inline]
318pub(crate) const fn byteorder<const OPT: Options>() -> ByteOrder {
319    byteorder_value(OPT)
320}
321
322#[inline]
323pub(crate) const fn byteorder_value(opt: Options) -> ByteOrder {
324    match (opt >> BYTEORDER_BIT) & 0b1 {
325        0 => ByteOrder::Little,
326        _ => ByteOrder::Big,
327    }
328}
329
330#[cfg(feature = "value")]
331#[inline]
332pub(crate) const fn is_map_keys_as_numbers<const OPT: Options>() -> bool {
333    is_map_keys_as_numbers_value(OPT)
334}
335
336const fn is_map_keys_as_numbers_value(opt: Options) -> bool {
337    ((opt >> MAP_KEYS_AS_NUMBERS_BIT) & 0b1) == 1
338}
339
340#[cfg(any(
341    feature = "storage",
342    feature = "wire",
343    feature = "descriptive",
344    feature = "value"
345))]
346pub(crate) const fn is_native_fixed<const OPT: Options>() -> bool {
347    matches!(
348        (integer::<OPT>(), float::<OPT>(), length::<OPT>(),),
349        (Integer::Fixed, Float::Fixed, Width::NATIVE)
350    )
351}
352
353/// Integer serialization mode.
354///
355/// # Examples
356///
357/// ```
358/// use musli::options::{self, Integer};
359///
360/// const OPTIONS_VARIABLE: musli::options::Options = options::new()
361///     .integer(Integer::Variable)
362///     .build();
363///
364/// const OPTIONS_FIXED: musli::options::Options = options::new()
365///     .integer(Integer::Fixed)
366///     .build();
367/// ```
368#[derive(Debug, PartialEq, Eq)]
369#[repr(u8)]
370#[non_exhaustive]
371pub enum Integer {
372    /// Variable number encoding.
373    Variable = 0b0,
374    /// Fixed number encoding.
375    Fixed = 0b1,
376}
377
378impl Integer {
379    const MASK: Options = 0b1;
380}
381
382/// Float serialization mode.
383///
384/// # Examples
385///
386/// ```
387/// use musli::options::{self, Float};
388///
389/// const OPTIONS_INTEGER: musli::options::Options = options::new()
390///     .float(Float::Integer)
391///     .build();
392///
393/// const OPTIONS_VARIABLE: musli::options::Options = options::new()
394///     .float(Float::Variable)
395///     .build();
396///
397/// const OPTIONS_FIXED: musli::options::Options = options::new()
398///     .float(Float::Fixed)
399///     .build();
400/// ```
401#[derive(Debug, PartialEq, Eq)]
402#[repr(u8)]
403#[non_exhaustive]
404pub enum Float {
405    /// Use the same serialization as integers, after coercing the bits of a
406    /// float into an unsigned integer.
407    Integer = 0b00,
408    /// Use variable float encoding.
409    Variable = 0b01,
410    /// Use fixed float encoding.
411    Fixed = 0b10,
412    /// Padding.
413    #[doc(hidden)]
414    Pad0 = 0b11,
415}
416
417impl Float {
418    const MASK: Options = 0b11;
419}
420
421/// Byte order to use when encoding numbers.
422///
423/// By default, this is the [`ByteOrder::NATIVE`] byte order of the target
424/// platform.
425///
426/// # Examples
427///
428/// ```
429/// use musli::options::{self, ByteOrder};
430///
431/// const OPTIONS_LITTLE: musli::options::Options = options::new()
432///     .byte_order(ByteOrder::Little)
433///     .build();
434///
435/// const OPTIONS_BIG: musli::options::Options = options::new()
436///     .byte_order(ByteOrder::Big)
437///     .build();
438///
439/// const OPTIONS_NATIVE: musli::options::Options = options::new()
440///     .byte_order(ByteOrder::NATIVE)
441///     .build();
442/// ```
443#[derive(Debug, PartialEq, Eq)]
444#[repr(u8)]
445#[non_exhaustive]
446pub enum ByteOrder {
447    /// Little endian byte order.
448    Little = 0,
449    /// Big endian byte order.
450    Big = 1,
451}
452
453impl ByteOrder {
454    const MASK: Options = 0b1;
455
456    /// The native byte order.
457    ///
458    /// [`Little`] for little and [`Big`] for big endian platforms.
459    ///
460    /// [`Little`]: ByteOrder::Little
461    /// [`Big`]: ByteOrder::Big
462    pub const NATIVE: Self = if cfg!(target_endian = "little") {
463        Self::Little
464    } else {
465        Self::Big
466    };
467
468    /// The network byte order.
469    ///
470    /// This is the same as [`Big`].
471    ///
472    /// [`Big`]: ByteOrder::Big
473    ///
474    /// # Examples
475    ///
476    /// ```
477    /// use musli::options::{self, ByteOrder};
478    /// use musli::packed::Encoding;
479    ///
480    /// // Configure network byte order (big endian) for cross-platform compatibility
481    /// const OPTIONS: musli::options::Options = options::new()
482    ///     .byte_order(ByteOrder::NETWORK)
483    ///     .build();
484    ///
485    /// const CONFIG: Encoding<OPTIONS> = Encoding::new().with_options();
486    /// let data = CONFIG.to_vec(&0x1234u16)?;
487    /// let decoded: u16 = CONFIG.from_slice(&data)?;
488    /// assert_eq!(decoded, 0x1234);
489    /// # Ok::<_, musli::packed::Error>(())
490    /// ```
491    pub const NETWORK: Self = Self::Big;
492}
493
494#[doc(hidden)]
495#[cfg(any(
496    feature = "storage",
497    feature = "wire",
498    feature = "descriptive",
499    feature = "value"
500))]
501macro_rules! width_arm {
502    ($width:expr, $macro:path) => {
503        match $width {
504            $crate::options::Width::U8 => {
505                $macro!(u8)
506            }
507            $crate::options::Width::U16 => {
508                $macro!(u16)
509            }
510            $crate::options::Width::U32 => {
511                $macro!(u32)
512            }
513            _ => {
514                $macro!(u64)
515            }
516        }
517    };
518}
519
520#[cfg(any(
521    feature = "storage",
522    feature = "wire",
523    feature = "descriptive",
524    feature = "value"
525))]
526pub(crate) use width_arm;
527
528/// The width of a numerical type.
529///
530/// # Examples
531///
532/// ```
533/// use musli::options::{self, Width, Integer};
534/// use musli::packed::Encoding;
535///
536/// // Configure encoding to use variable integer encoding
537/// const OPTIONS: musli::options::Options = options::new()
538///     .integer(Integer::Variable)
539///     .build();
540///
541/// const CONFIG: Encoding<OPTIONS> = Encoding::new().with_options();
542///
543/// // Variable encoding is more efficient for small numbers
544/// let data = CONFIG.to_vec(&42u32)?;
545/// let decoded: u32 = CONFIG.from_slice(&data)?;
546/// assert_eq!(decoded, 42);
547/// # Ok::<_, musli::packed::Error>(())
548/// ```
549#[derive(Clone, Copy, Debug, PartialEq, Eq)]
550#[repr(u8)]
551#[non_exhaustive]
552pub enum Width {
553    /// Use a variable width encoding.
554    Variable = 0b000,
555    /// 8 bit width.
556    U8 = 0b001,
557    /// 16 bit width.
558    U16 = 0b010,
559    /// 32 bit width.
560    U32 = 0b011,
561    /// 64 bit width.
562    U64 = 0b100,
563    /// Padding.
564    #[doc(hidden)]
565    Pad0 = 0b101,
566    /// Padding.
567    #[doc(hidden)]
568    Pad1 = 0b110,
569    /// Padding.
570    #[doc(hidden)]
571    Pad2 = 0b111,
572}
573
574impl Width {
575    const MASK: Options = 0b111;
576
577    /// The native width.
578    ///
579    /// This is the width of the target platform's native integer type.
580    ///
581    /// # Examples
582    ///
583    /// ```
584    /// use musli::options::{self, Width};
585    /// use musli::packed::Encoding;
586    ///
587    /// // Use native pointer width for platform-optimized encoding
588    /// const OPTIONS: musli::options::Options = options::new()
589    ///     .pointer(Width::NATIVE)
590    ///     .build();
591    ///
592    /// const CONFIG: Encoding<OPTIONS> = Encoding::new().with_options();
593    ///
594    /// // Encode a usize value using native width
595    /// let value: usize = 42;
596    /// let data = CONFIG.to_vec(&value)?;
597    /// let decoded: usize = CONFIG.from_slice(&data)?;
598    /// assert_eq!(value, decoded);
599    /// # Ok::<_, musli::packed::Error>(())
600    /// ```
601    pub const NATIVE: Self = const {
602        if cfg!(target_pointer_width = "64") {
603            Self::U64
604        } else if cfg!(target_pointer_width = "32") {
605            Self::U32
606        } else if cfg!(target_pointer_width = "16") {
607            Self::U16
608        } else {
609            panic!("Unsupported target pointer width")
610        }
611    };
612}
613
614#[test]
615fn test_builds() {
616    macro_rules! assert_or_default {
617        ($expr:expr, $test:expr, $default:expr, ()) => {
618            assert_eq!(
619                $test,
620                $default,
621                "{}: Expected default value for {}",
622                stringify!($expr),
623                stringify!($test)
624            );
625        };
626
627        ($expr:expr, $test:expr, $_default:expr, ($expected:expr)) => {
628            assert_eq!(
629                $test,
630                $expected,
631                "{}: Expected custom value for {}",
632                stringify!($expr),
633                stringify!($test)
634            );
635        };
636    }
637
638    macro_rules! test_case {
639        ($expr:expr => {
640            $(byteorder = $byteorder:expr,)?
641            $(integer = $integer:expr,)?
642            $(float = $float:expr,)?
643            $(length = $length:expr,)?
644            $(is_map_keys_as_numbers = $is_map_keys_as_numbers:expr,)?
645        }) => {{
646            const O: Options = $expr.build();
647            assert_or_default!($expr, byteorder::<O>(), ByteOrder::Little, ($($byteorder)?));
648            assert_or_default!($expr, integer::<O>(), Integer::Variable, ($($integer)?));
649            assert_or_default!($expr, float::<O>(), Float::Integer, ($($float)?));
650            assert_or_default!($expr, length::<O>(), Width::Variable, ($($length)?));
651            assert_or_default!($expr, is_map_keys_as_numbers::<O>(), false, ($($is_map_keys_as_numbers)?));
652        }}
653    }
654
655    test_case! {
656        self::new() => {}
657    }
658
659    test_case! {
660        self::new().map_keys_as_numbers() => {
661            is_map_keys_as_numbers = true,
662        }
663    }
664
665    test_case! {
666        self::new().integer(Integer::Fixed) => {
667            integer = Integer::Fixed,
668        }
669    }
670
671    test_case! {
672        self::new().float(Float::Fixed) => {
673            float = Float::Fixed,
674        }
675    }
676
677    test_case! {
678        self::new().float(Float::Variable) => {
679            float = Float::Variable,
680        }
681    }
682
683    test_case! {
684        self::new().float(Float::Variable) => {
685            float = Float::Variable,
686        }
687    }
688
689    test_case! {
690        self::new().byte_order(ByteOrder::Big) => {
691            byteorder = ByteOrder::Big,
692        }
693    }
694
695    test_case! {
696        self::new().byte_order(ByteOrder::Little) => {
697            byteorder = ByteOrder::Little,
698        }
699    }
700
701    test_case! {
702        self::new().pointer(Width::Variable) => {
703            length = Width::Variable,
704        }
705    }
706
707    test_case! {
708        self::new().pointer(Width::U8) => {
709            length = Width::U8,
710        }
711    }
712
713    test_case! {
714        self::new().pointer(Width::U16) => {
715            length = Width::U16,
716        }
717    }
718
719    test_case! {
720        self::new().pointer(Width::U32) => {
721            length = Width::U32,
722        }
723    }
724
725    test_case! {
726        self::new().pointer(Width::U64) => {
727            length = Width::U64,
728        }
729    }
730}