faster_hex/serde.rs
1#![warn(missing_docs)]
2
3use core::iter::FromIterator;
4
5mod internal {
6 use crate::{
7 decode::{hex_decode_array_with_case, hex_decode_with_case, CheckCase},
8 encode::encode,
9 };
10 use alloc::{borrow::Cow, string::String, vec, vec::Vec};
11 use core::{fmt, iter::FromIterator, mem::MaybeUninit};
12 use serde::{
13 de::{Error, Unexpected, Visitor},
14 Deserialize, Deserializer, Serialize, Serializer,
15 };
16
17 // Serde's Cow<str> deserializer always allocates. This adapter preserves
18 // String's deserialize_string hint and accepted inputs, but borrows text
19 // that the format can lend (for example, unescaped JSON from a slice).
20 struct Text<'a>(Cow<'a, str>);
21
22 impl<'de> Deserialize<'de> for Text<'de> {
23 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
24 struct TextVisitor;
25
26 impl<'de> Visitor<'de> for TextVisitor {
27 type Value = Text<'de>;
28
29 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
30 f.write_str("a string")
31 }
32
33 fn visit_str<E: Error>(self, text: &str) -> Result<Self::Value, E> {
34 Ok(Text(Cow::Owned(text.into())))
35 }
36
37 fn visit_borrowed_str<E: Error>(self, text: &'de str) -> Result<Self::Value, E> {
38 Ok(Text(Cow::Borrowed(text)))
39 }
40
41 fn visit_string<E: Error>(self, text: String) -> Result<Self::Value, E> {
42 Ok(Text(Cow::Owned(text)))
43 }
44
45 fn visit_bytes<E: Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
46 match core::str::from_utf8(bytes) {
47 Ok(text) => self.visit_str(text),
48 Err(_) => Err(E::invalid_value(Unexpected::Bytes(bytes), &self)),
49 }
50 }
51
52 fn visit_borrowed_bytes<E: Error>(
53 self,
54 bytes: &'de [u8],
55 ) -> Result<Self::Value, E> {
56 match core::str::from_utf8(bytes) {
57 Ok(text) => self.visit_borrowed_str(text),
58 Err(_) => Err(E::invalid_value(Unexpected::Bytes(bytes), &self)),
59 }
60 }
61
62 fn visit_byte_buf<E: Error>(self, bytes: Vec<u8>) -> Result<Self::Value, E> {
63 match String::from_utf8(bytes) {
64 Ok(text) => self.visit_string(text),
65 Err(error) => {
66 Err(E::invalid_value(Unexpected::Bytes(error.as_bytes()), &self))
67 }
68 }
69 }
70 }
71 deserializer.deserialize_string(TextVisitor)
72 }
73 }
74
75 pub(crate) fn serialize<S, T>(
76 data: T,
77 serializer: S,
78 with_prefix: bool,
79 case: CheckCase,
80 ) -> Result<S::Ok, S::Error>
81 where
82 S: Serializer,
83 T: AsRef<[u8]>,
84 {
85 let src = data.as_ref();
86 let prefix: &[u8] = if with_prefix { b"0x" } else { b"" };
87 let len = src
88 .len()
89 .checked_mul(2)
90 .and_then(|len| len.checked_add(prefix.len()))
91 .ok_or_else(|| serde::ser::Error::custom(crate::Error::Overflow))?;
92 // Fixed values through 65 bytes (including H520) fit with the prefix.
93 // Initialize only the storage selected for this call.
94 let mut stack;
95 let mut heap;
96 let dst = if len <= 132 {
97 stack = [MaybeUninit::uninit(); 132];
98 &mut stack[..len]
99 } else {
100 heap = Vec::<u8>::with_capacity(len);
101 &mut heap.spare_capacity_mut()[..len]
102 };
103 for (slot, &byte) in dst.iter_mut().zip(prefix) {
104 slot.write(byte);
105 }
106 encode(src, &mut dst[prefix.len()..], case == CheckCase::Upper)
107 .map_err(serde::ser::Error::custom)?;
108 // SAFETY: The prefix and encoder initialized all len bytes as ASCII.
109 // MaybeUninit<u8> has u8's layout; unused capacity is excluded and the
110 // backing stack buffer or allocation stays live throughout serialization.
111 serializer.serialize_str(unsafe {
112 core::str::from_utf8_unchecked(core::slice::from_raw_parts(dst.as_ptr().cast(), len))
113 })
114 }
115
116 fn payload<E: Error>(text: &str, with_prefix: bool) -> Result<&str, E> {
117 let text = if with_prefix {
118 text.strip_prefix("0x")
119 .ok_or_else(|| E::custom("invalid prefix"))?
120 } else {
121 text
122 };
123 if !text.len().is_multiple_of(2) {
124 return Err(E::custom("invalid length"));
125 }
126 Ok(text)
127 }
128
129 fn decode<E: Error>(
130 text: &str,
131 with_prefix: bool,
132 case: CheckCase,
133 max_bytes: usize,
134 ) -> Result<Vec<u8>, E> {
135 let text = payload::<E>(text, with_prefix)?;
136 let len = text.len() / 2;
137 if len > max_bytes {
138 return Err(E::custom(format_args!(
139 "expected at most {max_bytes} decoded bytes, got {len}"
140 )));
141 }
142 let mut bytes = vec![0; len];
143 hex_decode_with_case(text.as_bytes(), &mut bytes, case).map_err(E::custom)?;
144 Ok(bytes)
145 }
146
147 fn decode_array<E: Error, const N: usize>(
148 text: &str,
149 with_prefix: bool,
150 case: CheckCase,
151 ) -> Result<[u8; N], E> {
152 let text = payload::<E>(text, with_prefix)?;
153 hex_decode_array_with_case(text.as_bytes(), case).map_err(E::custom)
154 }
155
156 pub(crate) fn deserialize_array<'de, D, const N: usize>(
157 deserializer: D,
158 with_prefix: bool,
159 case: CheckCase,
160 ) -> Result<[u8; N], D::Error>
161 where
162 D: Deserializer<'de>,
163 {
164 let text = Text::deserialize(deserializer)?;
165 decode_array(&text.0, with_prefix, case)
166 }
167
168 pub(crate) fn deserialize_option_array<'de, D, const N: usize>(
169 deserializer: D,
170 with_prefix: bool,
171 case: CheckCase,
172 ) -> Result<Option<[u8; N]>, D::Error>
173 where
174 D: Deserializer<'de>,
175 {
176 Option::<Text>::deserialize(deserializer)?
177 .map(|text| decode_array(&text.0, with_prefix, case))
178 .transpose()
179 }
180
181 pub(crate) fn deserialize<'de, D, T>(
182 deserializer: D,
183 with_prefix: bool,
184 check_case: CheckCase,
185 max_bytes: usize,
186 ) -> Result<T, D::Error>
187 where
188 D: Deserializer<'de>,
189 T: FromIterator<u8>,
190 {
191 let text = Text::deserialize(deserializer)?;
192 decode(&text.0, with_prefix, check_case, max_bytes).map(|bytes| bytes.into_iter().collect())
193 }
194
195 pub(crate) fn serialize_option<S, T>(
196 data: &Option<T>,
197 serializer: S,
198 with_prefix: bool,
199 case: CheckCase,
200 ) -> Result<S::Ok, S::Error>
201 where
202 S: Serializer,
203 T: AsRef<[u8]>,
204 {
205 // Some serializers attach an Option tag before serializing its value.
206 // Give them a hex representation that implements Serde's value protocol.
207 struct HexValue<'a> {
208 data: &'a [u8],
209 with_prefix: bool,
210 case: CheckCase,
211 }
212
213 impl Serialize for HexValue<'_> {
214 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
215 serialize(self.data, serializer, self.with_prefix, self.case)
216 }
217 }
218
219 match data {
220 Some(data) => serializer.serialize_some(&HexValue {
221 data: data.as_ref(),
222 with_prefix,
223 case,
224 }),
225 None => serializer.serialize_none(),
226 }
227 }
228
229 pub(crate) fn deserialize_option<'de, D, T>(
230 deserializer: D,
231 with_prefix: bool,
232 check_case: CheckCase,
233 max_bytes: usize,
234 ) -> Result<Option<T>, D::Error>
235 where
236 D: Deserializer<'de>,
237 T: FromIterator<u8>,
238 {
239 // Let Serde retain its complete Option protocol, including untagged
240 // handling; the same decoder serves both optional and required text.
241 let bytes = Option::<Text>::deserialize(deserializer)?
242 .map(|text| decode(&text.0, with_prefix, check_case, max_bytes))
243 .transpose()?;
244 Ok(bytes.map(|bytes| bytes.into_iter().collect()))
245 }
246}
247
248/// Serializes a byte view as lowercase hex with a `0x` prefix.
249///
250/// Available with `serde`. This is the default serializer used by
251/// `#[serde(with = "faster_hex")]`. It accepts any [`AsRef<[u8]>`], reads that view
252/// once, and writes a Serde string in every format, including binary formats.
253/// Empty bytes serialize as `"0x"`. Use a named policy module to change the
254/// prefix or letter case. Temporary storage may allocate; allocation failure
255/// follows the allocator's error handling.
256///
257/// # Errors
258///
259/// Returns the serializer's error if writing the string fails, or if the encoded
260/// length including the prefix cannot be represented as a `usize`.
261///
262/// # Panics
263///
264/// Panics if temporary output storage would exceed [`isize::MAX`] bytes.
265///
266/// # Examples
267///
268/// ```
269/// #[derive(serde::Serialize)]
270/// struct Record {
271/// #[serde(with = "faster_hex")]
272/// bytes: Vec<u8>,
273/// }
274///
275/// let value = Record { bytes: vec![0xab, 1] };
276/// assert_eq!(serde_json::to_string(&value)?, r#"{"bytes":"0xab01"}"#);
277/// # Ok::<(), serde_json::Error>(())
278/// ```
279#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
280pub fn serialize<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
281where
282 S: serde::Serializer,
283 T: AsRef<[u8]>,
284{
285 withpfx_ignorecase::serialize(data, serializer)
286}
287
288/// Deserializes a `0x`-prefixed hex string into a byte collection.
289///
290/// Available with `serde`. This is the default deserializer for
291/// `#[serde(with = "faster_hex")]`. The prefix must be exactly `0x`; payload
292/// letters may use either case, including mixed case. Whitespace and separators
293/// are rejected. `"0x"` produces an empty collection.
294///
295/// Decoded bytes are collected into `T`, which implements [`FromIterator<u8>`],
296/// for example [`Vec<u8>`](alloc::vec::Vec) or
297/// [`VecDeque<u8>`](alloc::collections::VecDeque).
298/// Input text is borrowed when the format can lend it; transient,
299/// escaped or owned text may require storage. Use [`array`](mod@crate::array)
300/// for `[u8; N]` fields, or [`deserialize_bounded`] to limit decoded output.
301/// Allocation failure follows the allocator's error handling.
302///
303/// # Errors
304///
305/// Propagates deserializer errors, including invalid input types or UTF-8. Once
306/// text is available, checks the prefix, even payload length, then characters,
307/// in that order. Character diagnostics report the first invalid byte and its
308/// byte position within the payload, excluding `0x`. Collection starts only
309/// after successful decoding.
310///
311/// # Panics
312///
313/// A custom collector may panic, for example when its fixed capacity is exceeded.
314/// This adapter does not provide fallible collection.
315///
316/// # Examples
317///
318/// ```
319/// #[derive(serde::Deserialize)]
320/// struct Record {
321/// #[serde(with = "faster_hex")]
322/// bytes: Vec<u8>,
323/// }
324///
325/// let value: Record = serde_json::from_str(r#"{"bytes":"0x00aB"}"#)?;
326/// assert_eq!(value.bytes, [0, 0xab]);
327/// # Ok::<(), serde_json::Error>(())
328/// ```
329#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
330pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
331where
332 D: serde::Deserializer<'de>,
333 T: FromIterator<u8>,
334{
335 withpfx_ignorecase::deserialize(deserializer)
336}
337
338/// Deserializes at most `MAX` decoded bytes from a `0x`-prefixed hex string.
339///
340/// Available with `serde`. The prefix, case and collection behavior is the same
341/// as [`deserialize`]. `MAX` counts decoded bytes, so at most twice that many
342/// hex digits are accepted. A zero limit accepts `"0x"`.
343///
344/// The limit is checked before allocating decoded output or invoking the collector.
345/// It does not limit input text storage used by the format, error-message storage,
346/// or allocations performed by a custom collector. It also does not constrain
347/// serialization; pair this function with the ordinary [`serialize`] function.
348/// Allocation failure follows the allocator's error handling.
349///
350/// # Errors
351///
352/// Propagates deserializer errors. After obtaining text, checks the prefix, even
353/// payload length, the decoded-byte limit, then characters/case, in that order.
354/// An over-limit value is rejected even if it also contains invalid hex digits.
355///
356/// # Panics
357///
358/// A custom collector can still panic when full; the acceptance limit does not
359/// change its capacity or collection implementation.
360///
361/// # Examples
362///
363/// ```
364/// #[derive(serde::Serialize, serde::Deserialize)]
365/// struct Packet {
366/// #[serde(
367/// serialize_with = "faster_hex::serialize",
368/// deserialize_with = "faster_hex::deserialize_bounded::<2, _, _>"
369/// )]
370/// bytes: Vec<u8>,
371/// }
372///
373/// let packet: Packet = serde_json::from_str(r#"{"bytes":"0xab01"}"#)?;
374/// assert_eq!(packet.bytes, [0xab, 1]);
375/// assert!(serde_json::from_str::<Packet>(r#"{"bytes":"0x000102"}"#).is_err());
376/// # Ok::<(), serde_json::Error>(())
377/// ```
378#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
379pub fn deserialize_bounded<'de, const MAX: usize, D, T>(deserializer: D) -> Result<T, D::Error>
380where
381 D: serde::Deserializer<'de>,
382 T: FromIterator<u8>,
383{
384 withpfx_ignorecase::deserialize_bounded::<MAX, D, T>(deserializer)
385}
386
387// Required and optional adapters share one prefix/case policy declaration.
388macro_rules! serde_adapters {
389 ($mod_name:ident, $option_name:ident, $with_pfx:expr, $check_case:expr, $prefix:literal, $description:literal) => {
390 #[doc = $description]
391 #[doc = concat!(
392 r###"
393Use `#[serde(with = "...")]` for byte collections. Serialization reads one
394[`AsRef<[u8]>`] view; deserialization collects into [`FromIterator<u8>`] after
395validating the complete input. All formats use strings, including binary formats.
396
397A required prefix is exactly `0x`. Payloads contain only ASCII hex digits;
398empty payloads are accepted. For arrays use this module's [`array`] adapter;
399for a decoded-byte limit use [`deserialize_bounded`].
400
401# Examples
402
403```
404#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
405struct Record {
406 #[serde(with = "faster_hex::"###, stringify!($mod_name), r###"")]
407 bytes: Vec<u8>,
408}
409let record = Record { bytes: vec![0x12, 0x34] };
410let json = serde_json::to_string(&record)?;
411assert_eq!(json, r#"{"bytes":""###, $prefix, r###"1234"}"#);
412assert_eq!(serde_json::from_str::<Record>(&json)?, record);
413# Ok::<(), serde_json::Error>(())
414```
415"###
416 )]
417 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
418 pub mod $mod_name {
419 use crate::decode::CheckCase;
420 use crate::serde::internal;
421 use core::iter::FromIterator;
422
423 /// Serializes a byte view using this module's prefix and case policy.
424 ///
425 /// Reads [`AsRef::as_ref`] once and sends a string to every serializer.
426 /// Allocation behavior matches [`crate::serialize`].
427 ///
428 /// # Errors
429 ///
430 /// Returns an error if serialization fails or the encoded length overflows.
431 ///
432 /// # Panics
433 ///
434 /// Panics if temporary output storage would exceed [`isize::MAX`] bytes.
435 ///
436 /// # Examples
437 ///
438 /// See the [module example](self#examples).
439 pub fn serialize<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
440 where
441 S: serde::Serializer,
442 T: AsRef<[u8]>,
443 {
444 internal::serialize(data, serializer, $with_pfx, $check_case)
445 }
446
447 /// Deserializes a hex string using this module's prefix and case policy.
448 ///
449 /// Borrows input text when the format can lend it.
450 /// Allocation behavior matches [`crate::deserialize`].
451 ///
452 /// # Errors
453 ///
454 /// Propagates deserializer errors. Checks any required prefix, even payload
455 /// length, then characters/case before collection. Positions exclude the prefix.
456 ///
457 /// # Panics
458 ///
459 /// A custom collector may panic, for example when its fixed capacity is exceeded.
460 ///
461 /// # Examples
462 ///
463 /// See the [module example](self#examples).
464 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
465 where
466 D: serde::Deserializer<'de>,
467 T: FromIterator<u8>,
468 {
469 internal::deserialize(deserializer, $with_pfx, $check_case, usize::MAX)
470 }
471
472 /// Deserializes at most `MAX` decoded bytes using this module's policy.
473 ///
474 /// The limit precedes decoded-output allocation and collection; it does not
475 /// bound input, error or collector storage. `MAX == 0` accepts an empty payload.
476 /// Allocation behavior matches [`crate::deserialize_bounded`].
477 ///
478 /// # Errors
479 ///
480 /// Propagates deserializer errors. Checks any required prefix, even length,
481 /// decoded-byte limit, then characters/case. Byte positions exclude the prefix.
482 ///
483 /// # Panics
484 ///
485 /// A custom collector may still panic; the limit does not change its capacity.
486 ///
487 /// # Examples
488 ///
489 /// See [`crate::deserialize_bounded`] for the Serde field attributes.
490 pub fn deserialize_bounded<'de, const MAX: usize, D, T>(
491 deserializer: D,
492 ) -> Result<T, D::Error>
493 where
494 D: serde::Deserializer<'de>,
495 T: FromIterator<u8>,
496 {
497 internal::deserialize(deserializer, $with_pfx, $check_case, MAX)
498 }
499
500 /// Exact-length arrays using the parent module's prefix and case policy.
501 ///
502 /// Deserialization writes into `[u8; N]` without an intermediate byte vector.
503 /// Input text is borrowed where possible; formats may need storage for
504 /// escaped or transient text. Serialization uses the parent adapter's
505 /// string representation, including any framing added by the format.
506 #[doc = concat!(
507 r###"
508# Examples
509
510```
511#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
512struct Record {
513 #[serde(with = "faster_hex::"###, stringify!($mod_name), r###"::array")]
514 id: [u8; 2],
515}
516let record = Record { id: [0x12, 0x34] };
517let json = serde_json::to_string(&record)?;
518assert_eq!(serde_json::from_str::<Record>(&json)?, record);
519# Ok::<(), serde_json::Error>(())
520```
521"###
522 )]
523 pub mod array {
524 use super::{internal, CheckCase};
525
526 pub use super::serialize;
527
528 /// Deserializes exactly `N` bytes into an owned array.
529 ///
530 /// Empty payloads succeed only for `N == 0`.
531 ///
532 /// # Errors
533 ///
534 /// Checks any required prefix, even payload length, exact decoded length,
535 /// then characters/case. Length errors count decoded bytes; invalid-byte
536 /// positions exclude the prefix. Format errors propagate.
537 ///
538 /// # Examples
539 ///
540 /// See the [module example](self#examples).
541 pub fn deserialize<'de, D, const N: usize>(
542 deserializer: D,
543 ) -> Result<[u8; N], D::Error>
544 where
545 D: serde::Deserializer<'de>,
546 {
547 internal::deserialize_array(deserializer, $with_pfx, $check_case)
548 }
549 }
550 }
551
552 #[doc = concat!($description, " Optional values.")]
553 #[doc = concat!(
554 r###"
555Present values follow [`"###, stringify!($mod_name), r###"`](crate::"###,
556 stringify!($mod_name), r###"). Absent values use Serde's `None`
557representation (`null` in JSON). Binary formats retain their normal [`Option`]
558tags, so `Some(empty)` remains distinct from `None`.
559
560Use `#[serde(default)]` to accept a missing struct field. For fixed arrays use
561[`array`]; for a limit on present values use [`deserialize_bounded`].
562
563# Examples
564
565```
566#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
567struct Record {
568 #[serde(default, with = "faster_hex::"###, stringify!($option_name), r###"")]
569 bytes: Option<Vec<u8>>,
570}
571let record = Record { bytes: Some(vec![0x12, 0x34]) };
572let json = serde_json::to_string(&record)?;
573assert_eq!(serde_json::from_str::<Record>(&json)?, record);
574assert_eq!(serde_json::from_str::<Record>("{}")?.bytes, None);
575# Ok::<(), serde_json::Error>(())
576```
577"###
578 )]
579 #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
580 pub mod $option_name {
581 use crate::decode::CheckCase;
582 use crate::serde::internal;
583 use core::iter::FromIterator;
584
585 /// Serializes an optional byte view, preserving Serde's [`Option`] tags.
586 ///
587 /// Present values use this module's hex string policy and read
588 /// [`AsRef::as_ref`] once. `None` uses the format's absent-value representation.
589 /// Allocation behavior matches [`crate::serialize`].
590 ///
591 /// # Errors
592 ///
593 /// Propagates serializer errors, including errors writing an Option tag.
594 /// Present values also fail if their encoded length overflows.
595 ///
596 /// # Panics
597 ///
598 /// Panics if temporary output storage would exceed [`isize::MAX`] bytes.
599 ///
600 /// # Examples
601 ///
602 /// See the [module example](self#examples).
603 pub fn serialize<S, T>(data: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
604 where
605 S: serde::Serializer,
606 T: AsRef<[u8]>,
607 {
608 internal::serialize_option(data, serializer, $with_pfx, $check_case)
609 }
610
611 /// Deserializes an optional hex string into a byte collection.
612 ///
613 /// Present strings use this module's policy; an empty payload is `Some(empty)`.
614 /// Allocation behavior matches [`crate::deserialize`].
615 ///
616 /// # Errors
617 ///
618 /// Propagates deserializer errors. Checks present text for any required prefix,
619 /// even payload length, then characters/case before collection. Invalid input
620 /// is an error, not `None`. Byte positions exclude the prefix.
621 ///
622 /// # Panics
623 ///
624 /// A custom collector may panic, for example when its fixed capacity is exceeded.
625 ///
626 /// # Examples
627 ///
628 /// See the [module example](self#examples).
629 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
630 where
631 D: serde::Deserializer<'de>,
632 T: FromIterator<u8>,
633 {
634 internal::deserialize_option(deserializer, $with_pfx, $check_case, usize::MAX)
635 }
636
637 /// Deserializes an optional collection of at most `MAX` decoded bytes.
638 ///
639 /// Accepts `None` and empty payloads even when `MAX == 0`. Storage limits
640 /// and allocation behavior match [`crate::deserialize_bounded`].
641 ///
642 /// # Errors
643 ///
644 /// Propagates deserializer errors. Checks present text for any required prefix,
645 /// even payload length, decoded-byte limit, then characters/case. Invalid or
646 /// over-limit values are errors, not `None`. Byte positions exclude the prefix.
647 ///
648 /// # Panics
649 ///
650 /// A custom collector may still panic; the limit does not change its capacity.
651 ///
652 /// # Examples
653 ///
654 /// See [`crate::deserialize_bounded`] for the Serde field attributes.
655 pub fn deserialize_bounded<'de, const MAX: usize, D, T>(
656 deserializer: D,
657 ) -> Result<Option<T>, D::Error>
658 where
659 D: serde::Deserializer<'de>,
660 T: FromIterator<u8>,
661 {
662 internal::deserialize_option(deserializer, $with_pfx, $check_case, MAX)
663 }
664
665 /// Optional fixed-length arrays using this module's prefix and case policy.
666 ///
667 /// Present strings decode directly into `[u8; N]` without an intermediate
668 /// byte vector; input text may still need storage. `None` and `Some([])`
669 /// remain distinct in every format. Add `#[serde(default)]` for missing fields.
670 ///
671 /// # Examples
672 ///
673 /// See the [crate example](crate#serde-adapters) for optional array fields.
674 pub mod array {
675 use super::{internal, CheckCase};
676
677 pub use super::serialize;
678
679 /// Deserializes an optional array of exactly `N` bytes.
680 ///
681 /// An empty present payload requires `N == 0`.
682 ///
683 /// # Errors
684 ///
685 /// Checks present text for any required prefix, even payload length, exact
686 /// decoded length, then characters/case. Format errors propagate; invalid input
687 /// is an error, not `None`.
688 /// Lengths count decoded bytes; invalid-byte positions exclude the prefix.
689 ///
690 /// # Examples
691 ///
692 /// See the [crate example](crate#serde-adapters) for optional array fields.
693 pub fn deserialize<'de, D, const N: usize>(
694 deserializer: D,
695 ) -> Result<Option<[u8; N]>, D::Error>
696 where
697 D: serde::Deserializer<'de>,
698 {
699 internal::deserialize_option_array(deserializer, $with_pfx, $check_case)
700 }
701 }
702 }
703 };
704}
705
706serde_adapters!(
707 withpfx_ignorecase,
708 option_withpfx_ignorecase,
709 true,
710 CheckCase::None,
711 "0x",
712 "Lowercase serialization with a 0x prefix; accepts either letter case."
713);
714serde_adapters!(
715 nopfx_ignorecase,
716 option_nopfx_ignorecase,
717 false,
718 CheckCase::None,
719 "",
720 "Lowercase serialization without a prefix; accepts either letter case."
721);
722serde_adapters!(
723 withpfx_lowercase,
724 option_withpfx_lowercase,
725 true,
726 CheckCase::Lower,
727 "0x",
728 "Lowercase hex with a required 0x prefix."
729);
730serde_adapters!(
731 nopfx_lowercase,
732 option_nopfx_lowercase,
733 false,
734 CheckCase::Lower,
735 "",
736 "Lowercase hex without a prefix."
737);
738serde_adapters!(
739 withpfx_uppercase,
740 option_withpfx_uppercase,
741 true,
742 CheckCase::Upper,
743 "0x",
744 "Uppercase hex with a required 0x prefix."
745);
746serde_adapters!(
747 nopfx_uppercase,
748 option_nopfx_uppercase,
749 false,
750 CheckCase::Upper,
751 "",
752 "Uppercase hex without a prefix."
753);