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}