wincode/schema/adapter.rs
1//! Schema adapters that serialize a type by converting it to and from the value
2//! of an intermediate "wire" schema.
3
4#[cfg(feature = "alloc")]
5use crate::len::SeqLen;
6use {
7 crate::{
8 TypeMeta,
9 config::ConfigCore,
10 error::{ReadError, ReadResult, WriteResult},
11 io::{ReadError as IoReadError, Reader, Writer},
12 schema::{SchemaRead, SchemaWrite},
13 },
14 core::{marker::PhantomData, mem::MaybeUninit},
15};
16
17/// Serialize a `Target` by going through an intermediate wire representation
18/// using the standard library [`From`]/[`Into`] conversions.
19///
20/// `Wire` is a *schema* (any `SchemaWrite`/`SchemaRead` implementor), and the
21/// intermediate value is the type that schema serializes — its
22/// [`SchemaWrite::Src`] on write and [`SchemaRead::Dst`] on read. On write the
23/// `Target` is converted into that value (via `From<&Target>`) and serialized
24/// with `Wire`; on read the value is deserialized with `Wire` and converted back
25/// into the `Target` (via `Target: From<…>`).
26///
27/// Because `Wire` is a schema rather than the value itself, the wire side may be:
28/// - a self-describing type, where the value is the type itself — e.g. `i64`, or
29/// any type deriving [`SchemaWrite`](wincode_derive::SchemaWrite) /
30/// [`SchemaRead`](wincode_derive::SchemaRead);
31/// - a schema adapter over some other value — e.g.
32/// [`containers::Vec<u8, UseIntLen<u16>>`](crate::containers::Vec), whose value
33/// is `Vec<u8>`.
34///
35/// The `Target` is the type of the annotated field. Use the `_` inference token
36/// to have it filled in automatically from the field type:
37///
38/// ```
39/// # #[cfg(feature = "derive")] {
40/// # use wincode::adapter::FromInto;
41/// # use wincode_derive::{SchemaWrite, SchemaRead};
42/// // Our in-memory type with a layout we don't want to serialize directly.
43/// #[derive(Debug, PartialEq, Clone, Copy)]
44/// struct Celsius(f64);
45///
46/// // The on-the-wire representation: hundredths of a degree as an integer.
47/// impl From<&Celsius> for i64 {
48/// fn from(c: &Celsius) -> i64 {
49/// (c.0 * 100.0) as i64
50/// }
51/// }
52/// impl From<i64> for Celsius {
53/// fn from(raw: i64) -> Celsius {
54/// Celsius(raw as f64 / 100.0)
55/// }
56/// }
57///
58/// #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
59/// struct Reading {
60/// #[wincode(with = "FromInto<i64, _>")]
61/// temp: Celsius,
62/// }
63///
64/// let reading = Reading { temp: Celsius(21.5) };
65/// let bytes = wincode::serialize(&reading).unwrap();
66/// assert_eq!(reading, wincode::deserialize(&bytes).unwrap());
67/// # }
68/// ```
69pub struct FromInto<Wire, Target>(PhantomData<Wire>, PhantomData<Target>);
70
71unsafe impl<C, Wire, Target> SchemaWrite<C> for FromInto<Wire, Target>
72where
73 C: ConfigCore,
74 Wire: SchemaWrite<C>,
75 Wire::Src: Sized + for<'a> From<&'a Target>,
76{
77 type Src = Target;
78
79 // The serialized form is exactly the wire value's, so the size is forwarded.
80 // The conversion means the `Target`'s in-memory representation does not match
81 // the serialized form, so `zero_copy` is cleared.
82 const TYPE_META: TypeMeta = <Wire as SchemaWrite<C>>::TYPE_META.keep_zero_copy(false);
83
84 #[inline]
85 fn size_of(src: &Self::Src) -> WriteResult<usize> {
86 if let TypeMeta::Static { size, .. } = <Self as SchemaWrite<C>>::TYPE_META {
87 return Ok(size);
88 }
89 let wire: Wire::Src = src.into();
90 <Wire as SchemaWrite<C>>::size_of(&wire)
91 }
92
93 #[inline]
94 fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
95 let wire: Wire::Src = src.into();
96 <Wire as SchemaWrite<C>>::write(writer, &wire)
97 }
98}
99
100unsafe impl<'de, C, Wire, Target> SchemaRead<'de, C> for FromInto<Wire, Target>
101where
102 C: ConfigCore,
103 Wire: SchemaRead<'de, C>,
104 Target: From<Wire::Dst>,
105{
106 type Dst = Target;
107
108 const TYPE_META: TypeMeta = <Wire as SchemaRead<'de, C>>::TYPE_META.keep_zero_copy(false);
109
110 #[inline]
111 fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
112 let wire = <Wire as SchemaRead<'de, C>>::get(reader)?;
113 dst.write(Target::from(wire));
114 Ok(())
115 }
116}
117
118/// Deserializes using the wire schema `T` normally, but yields
119/// `T::Dst::default()` when the reader is exhausted (hits EOF) instead of
120/// erroring.
121///
122/// The purpose is backward compatibility when new fields are appended to the
123/// tail of a persisted struct: older, shorter encodings that predate those
124/// fields still decode, with the missing trailing fields filled from their
125/// [`Default`] value. Reading a full encoding is unaffected.
126///
127/// Writing is unchanged — the value is serialized exactly as `T` would,
128/// producing bytes that decode identically with or without this adapter. Only
129/// the read path differs, so this is purely a decode-time compatibility shim.
130///
131/// Apply it via the `with` attribute on the (necessarily trailing) fields it
132/// covers, naming the field's own schema as `T`:
133///
134/// ```
135/// # #[cfg(feature = "derive")] {
136/// # use wincode::adapter::DefaultOnEmptyRead;
137/// # use wincode_derive::{SchemaWrite, SchemaRead};
138/// #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
139/// struct Record {
140/// id: u32,
141/// // Appended in a later version; older encodings omit it entirely.
142/// #[wincode(with = "DefaultOnEmptyRead<u64>")]
143/// added_later: u64,
144/// }
145///
146/// // A full encoding round-trips as usual.
147/// let record = Record { id: 7, added_later: 42 };
148/// let bytes = wincode::serialize(&record).unwrap();
149/// assert_eq!(record, wincode::deserialize(&bytes).unwrap());
150///
151/// // An older encoding that predates `added_later` decodes to its default.
152/// let legacy = wincode::serialize(&7u32).unwrap();
153/// let decoded: Record = wincode::deserialize(&legacy).unwrap();
154/// assert_eq!(decoded, Record { id: 7, added_later: 0 });
155/// # }
156/// ```
157///
158/// # Warning
159///
160/// The fallback is driven purely by running out of bytes, so it is only sound
161/// where "no more bytes" unambiguously means "this optional tail is absent":
162///
163/// - **Do not use it on sequence elements.** When more items follow, a missing
164/// field does not produce EOF — the read simply continues into the bytes that
165/// encode the *next* item. Instead of defaulting the absent field, the decoder
166/// consumes the following item's data, desynchronizing the rest of the
167/// sequence. The fallback only helps when the missing bytes are genuinely the
168/// end of input.
169/// - **Do not use it on a middle field followed by an always-present field.**
170/// The fallback catches *any* size-limit error from `T`, including a
171/// partially-present value, so a genuinely truncated field is masked instead
172/// of reported and the fields after it are misaligned. It is only safe on a
173/// trailing run of fields where every field from the first
174/// `DefaultOnEmptyRead` onward is itself optional-on-EOF.
175///
176/// Prefer applying it to trailing fields of a **top-level struct** decoded with
177/// [`deserialize_exact`](crate::deserialize_exact), where reaching EOF exactly
178/// at a field boundary is well defined and the end-of-input check is
179/// straightforward and cheap.
180pub struct DefaultOnEmptyRead<T>(PhantomData<T>);
181
182unsafe impl<'de, C, T> SchemaRead<'de, C> for DefaultOnEmptyRead<T>
183where
184 C: ConfigCore,
185 T: SchemaRead<'de, C>,
186 T::Dst: Default,
187{
188 type Dst = T::Dst;
189
190 // TYPE_META is intentionally left at the default `Dynamic`: decoding may read
191 // either 0 bytes (the EOF fallback) or `T`'s full encoding, so the decoded
192 // size is not fixed and a reader must not prefetch a static size.
193
194 #[inline]
195 fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
196 match <T as SchemaRead<'de, C>>::read(reader, dst) {
197 Ok(()) => Ok(()),
198 Err(ReadError::Io(IoReadError::ReadSizeLimit(_))) => {
199 dst.write(Self::Dst::default());
200 Ok(())
201 }
202 Err(e) => Err(e),
203 }
204 }
205}
206
207unsafe impl<C, T> SchemaWrite<C> for DefaultOnEmptyRead<T>
208where
209 C: ConfigCore,
210 T: SchemaWrite<C>,
211{
212 type Src = T::Src;
213
214 const TYPE_META: TypeMeta = <T as SchemaWrite<C>>::TYPE_META;
215
216 #[inline]
217 fn size_of(src: &Self::Src) -> WriteResult<usize> {
218 <T as SchemaWrite<C>>::size_of(src)
219 }
220
221 #[inline]
222 fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
223 <T as SchemaWrite<C>>::write(writer, src)
224 }
225}
226
227/// Reads a value with the `Inner` schema purely to advance the reader, then throws
228/// it away and yields [`Default::default()`] instead.
229///
230/// `Inner` is a *schema*, its bytes are consumed exactly as `Inner` would consume them
231/// — including any validation `Inner` performs — so the surrounding stream stays in sync,
232/// but the decoded value is dropped and the field is filled with the [`Default`] of `Inner`'s
233/// [`SchemaRead::Dst`].
234///
235/// It is meant for fields you don't want to persist, such as a deprecated field
236/// kept only for wire compatibility. On read the encoded value is stepped over and
237/// reset to its [`Default`]; on write the value held in the instance is likewise
238/// ignored and `Inner`'s [`Default`] is serialized in its place. The two directions
239/// agree, so `Discard` works on types deriving both
240/// [`SchemaWrite`](wincode_derive::SchemaWrite) and [`SchemaRead`] — with the
241/// caveat that the original value is *not* preserved across a round-trip: a
242/// discarded field always reads back, and re-serializes, as the default.
243///
244/// For a length-prefixed sequence, prefer [`DiscardSeq`], which steps over every
245/// element without allocating a backing buffer.
246///
247/// ```
248/// # #[cfg(feature = "derive")] {
249/// # use wincode::adapter::Discard;
250/// # use wincode_derive::{SchemaWrite, SchemaRead};
251/// #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
252/// struct Full {
253/// ignored: u32,
254/// kept: u16,
255/// }
256///
257/// #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
258/// struct Partial {
259/// #[wincode(with = "Discard<u32>")]
260/// ignored: u32,
261/// kept: u16,
262/// }
263///
264/// // On read, `ignored` is stepped over and reset to its default.
265/// let bytes = wincode::serialize(&Full { ignored: 7, kept: 9 }).unwrap();
266/// assert_eq!(
267/// wincode::deserialize::<Partial>(&bytes).unwrap(),
268/// Partial { ignored: 0, kept: 9 },
269/// );
270///
271/// // On write, the held value of `ignored` is discarded and its default is
272/// // serialized, so the output matches a `Full` whose `ignored` is `0`.
273/// let out = wincode::serialize(&Partial { ignored: 7, kept: 9 }).unwrap();
274/// assert_eq!(out, wincode::serialize(&Full { ignored: 0, kept: 9 }).unwrap());
275/// # }
276/// ```
277pub struct Discard<Inner>(PhantomData<Inner>);
278
279unsafe impl<'de, C, Inner> SchemaRead<'de, C> for Discard<Inner>
280where
281 C: ConfigCore,
282 Inner: SchemaRead<'de, C>,
283 Inner::Dst: Default,
284{
285 type Dst = Inner::Dst;
286
287 // Not zero-copy: a fresh `Default` is yielded, not a view of the wire bytes.
288 const TYPE_META: TypeMeta = Inner::TYPE_META.keep_zero_copy(false);
289
290 #[inline]
291 fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
292 // No byte-skip fast path (unlike `DiscardSeq`): a single value has no
293 // per-element bounds checks to hoist, and dropping the decoded value lets
294 // the optimizer elide the copy out of the reader.
295 Inner::get(reader)?;
296 dst.write(Default::default());
297 Ok(())
298 }
299}
300
301unsafe impl<C, Inner> SchemaWrite<C> for Discard<Inner>
302where
303 C: ConfigCore,
304 Inner: SchemaWrite<C>,
305 Inner::Src: Default,
306{
307 type Src = Inner::Src;
308
309 // Not zero-copy: the `Default` is written, not the instance's own bytes.
310 const TYPE_META: TypeMeta = Inner::TYPE_META.keep_zero_copy(false);
311
312 #[inline]
313 fn size_of(_src: &Self::Src) -> WriteResult<usize> {
314 if let TypeMeta::Static { size, .. } = Inner::TYPE_META {
315 return Ok(size);
316 }
317 Inner::size_of(&Default::default())
318 }
319
320 #[inline]
321 fn write(writer: impl Writer, _src: &Self::Src) -> WriteResult<()> {
322 // Ignore the provided value and match what `read` reconstructs.
323 Inner::write(writer, &Default::default())
324 }
325}
326
327/// Discards a length-prefixed sequence: reads the length (encoded per `Len`) and
328/// steps over every element with the element schema `T`, advancing the reader
329/// exactly as decoding the sequence would, yet never allocates a backing buffer and
330/// always yields an empty [`Vec`].
331///
332/// Every wincode sequence — `Vec`, `String`, slices, sets, and maps — shares the
333/// same *length prefix + elements* wire layout, so this discards **any** of them;
334/// `T` only names one element's wire form:
335///
336/// - a `Vec<u32>`: `DiscardSeq<u32, Len>`;
337/// - a `String`: `DiscardSeq<u8, Len>` (identical bytes to a `Vec<u8>`);
338/// - a map such as `HashMap<K, V>` / `BTreeMap<K, V>`: `DiscardSeq<(K, V), Len>`,
339/// since each entry is encoded as a `(K, V)` pair;
340/// - nested sequences, with no allocation at any level:
341/// `DiscardSeq<DiscardSeq<u8, L2>, L1>` for a `Vec<Vec<u8>>`.
342///
343/// The produced value is always an empty `Vec<T::Dst>`, and that type is
344/// incidental. Deprecating a field is then just repointing it at `DiscardSeq` and
345/// changing its type to the matching `Vec<…>`: the field is dead, so its concrete
346/// collection type no longer matters — only that the bytes are consumed and the
347/// stream stays aligned.
348///
349/// The length prefix is still validated against the configured preallocation
350/// limit, so a hostile length errors out identically to a real read.
351///
352/// Like [`Discard`], the sequence held in the instance is ignored on write and an
353/// empty sequence is emitted, matching what `read` reconstructs. Use the
354/// configuration's default length encoding, e.g.
355/// [`BincodeLen`](crate::len::BincodeLen), to match how the field was originally
356/// encoded.
357///
358/// ```
359/// # #[cfg(all(feature = "alloc", feature = "derive"))] {
360/// # use wincode::{adapter::DiscardSeq, len::BincodeLen};
361/// # use wincode_derive::{SchemaWrite, SchemaRead};
362/// #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
363/// struct Full {
364/// data: Vec<u32>,
365/// tag: u8,
366/// }
367///
368/// #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
369/// struct Partial {
370/// #[wincode(with = "DiscardSeq<u32, BincodeLen>")]
371/// data: Vec<u32>,
372/// tag: u8,
373/// }
374///
375/// // On read, `data` is stepped over and reset to an empty `Vec`.
376/// let bytes = wincode::serialize(&Full { data: vec![1, 2, 3], tag: 42 }).unwrap();
377/// assert_eq!(
378/// wincode::deserialize::<Partial>(&bytes).unwrap(),
379/// Partial { data: Vec::new(), tag: 42 },
380/// );
381///
382/// // On write, `data` is discarded and an empty sequence is serialized, so the
383/// // output matches a `Full` with empty `data`.
384/// let out = wincode::serialize(&Partial { data: vec![1, 2, 3], tag: 42 }).unwrap();
385/// assert_eq!(out, wincode::serialize(&Full { data: vec![], tag: 42 }).unwrap());
386/// # }
387/// ```
388#[cfg(feature = "alloc")]
389pub struct DiscardSeq<T, Len>(PhantomData<T>, PhantomData<Len>);
390
391#[cfg(feature = "alloc")]
392unsafe impl<'de, C, T, Len> SchemaRead<'de, C> for DiscardSeq<T, Len>
393where
394 C: ConfigCore,
395 Len: SeqLen<C>,
396 T: SchemaRead<'de, C>,
397{
398 type Dst = alloc::vec::Vec<T::Dst>;
399
400 #[inline]
401 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
402 let len = Len::read_prealloc_check::<T::Dst>(reader.by_ref())?;
403 if let TypeMeta::Static { size, .. } = T::TYPE_META {
404 // Reserve the whole `len * size`-byte body up front so the per-element
405 // decodes below elide their own bounds checks. `get` still validates
406 // each element, so non-zero-copy types are handled correctly too.
407 //
408 // SAFETY: exactly `len` decodes of `size` bytes run through the trusted window
409 let mut trusted = unsafe { reader.as_trusted_for_seq(len, size)? };
410 for _ in 0..len {
411 T::get(trusted.by_ref())?;
412 }
413 } else {
414 for _ in 0..len {
415 T::get(reader.by_ref())?;
416 }
417 }
418 dst.write(alloc::vec::Vec::new());
419 Ok(())
420 }
421}
422
423#[cfg(feature = "alloc")]
424unsafe impl<C, T, Len> SchemaWrite<C> for DiscardSeq<T, Len>
425where
426 C: ConfigCore,
427 Len: SeqLen<C>,
428 T: SchemaWrite<C>,
429 T::Src: Sized,
430{
431 type Src = alloc::vec::Vec<T::Src>;
432
433 #[inline]
434 fn size_of(_src: &Self::Src) -> WriteResult<usize> {
435 // An empty sequence: just the length prefix for 0.
436 Len::write_bytes_needed(0)
437 }
438
439 #[inline]
440 fn write(writer: impl Writer, _src: &Self::Src) -> WriteResult<()> {
441 // Ignore the provided sequence; emit an empty one, matching `read`.
442 Len::write(writer, 0)
443 }
444}
445
446#[cfg(all(test, feature = "derive", feature = "alloc"))]
447mod tests {
448 use {
449 crate::{
450 SchemaRead, SchemaWrite,
451 adapter::{DefaultOnEmptyRead, Discard, DiscardSeq, FromInto},
452 deserialize,
453 len::BincodeLen,
454 serialize,
455 },
456 alloc::{collections::BTreeMap, string::String, vec::Vec},
457 };
458
459 /// A self-describing wire schema: the wire value is a plain `u32`.
460 #[test]
461 fn scalar_wire() {
462 #[derive(Debug, PartialEq, Clone, Copy)]
463 struct Id(u32);
464
465 impl From<&Id> for u32 {
466 fn from(id: &Id) -> u32 {
467 id.0
468 }
469 }
470 impl From<u32> for Id {
471 fn from(raw: u32) -> Id {
472 Id(raw)
473 }
474 }
475
476 #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
477 #[wincode(internal)]
478 struct Msg {
479 #[wincode(with = "FromInto<u32, _>")]
480 id: Id,
481 }
482
483 let msg = Msg {
484 id: Id(0xdead_beef),
485 };
486 let bytes = serialize(&msg).unwrap();
487 // Serialized form is identical to a bare `u32`.
488 assert_eq!(bytes, serialize(&0xdead_beef_u32).unwrap());
489 assert_eq!(msg, deserialize(&bytes).unwrap());
490 }
491
492 /// `Wire` is a schema *adapter* whose value type differs from itself:
493 /// `containers::Vec<u8, _>` serializes a `Vec<u8>`.
494 #[test]
495 fn adapter_wire() {
496 use crate::{containers, len::UseIntLen};
497
498 #[derive(Debug, PartialEq, Clone)]
499 struct Name(String);
500
501 impl From<&Name> for Vec<u8> {
502 fn from(name: &Name) -> Vec<u8> {
503 name.0.as_bytes().to_vec()
504 }
505 }
506 impl From<Vec<u8>> for Name {
507 fn from(bytes: Vec<u8>) -> Name {
508 Name(String::from_utf8(bytes).unwrap())
509 }
510 }
511
512 #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
513 #[wincode(internal)]
514 struct Msg {
515 #[wincode(with = "FromInto<containers::Vec<u8, UseIntLen<u16>>, _>")]
516 name: Name,
517 }
518
519 let msg = Msg {
520 name: Name("wincode".into()),
521 };
522 let bytes = serialize(&msg).unwrap();
523 // u16 length prefix + the raw bytes.
524 assert_eq!(bytes.len(), 2 + "wincode".len());
525 assert_eq!(msg, deserialize(&bytes).unwrap());
526 }
527
528 #[test]
529 fn default_on_empty_read() {
530 #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
531 #[wincode(internal)]
532 struct Record {
533 id: u32,
534 #[wincode(with = "DefaultOnEmptyRead<u64>")]
535 added_later: u64,
536 }
537
538 // Full encoding round-trips unchanged, and writing is identical to a
539 // plain `u32` + `u64`.
540 let record = Record {
541 id: 7,
542 added_later: 42,
543 };
544 let bytes = serialize(&record).unwrap();
545 assert_eq!(bytes.len(), 4 + 8);
546 assert_eq!(record, deserialize(&bytes).unwrap());
547
548 // A legacy encoding that stops after `id` decodes `added_later` to its
549 // default rather than erroring on EOF.
550 let legacy = serialize(&7u32).unwrap();
551 assert_eq!(
552 deserialize::<Record>(&legacy).unwrap(),
553 Record {
554 id: 7,
555 added_later: 0,
556 }
557 );
558
559 // The fallback triggers on any `ReadSizeLimit`, so a *partially* present
560 // inner value (not just a clean field boundary) also decodes to the
561 // default. This is partly deliberate and partly a consequence of the
562 // reader interface: readers do not expose how many bytes remain, and
563 // probing that is neither efficient nor guaranteed to work across all
564 // reader implementations, so we cannot distinguish a clean boundary from
565 // a truncated field. Here 4 bytes cover `id` and the stray tail is too
566 // short for the `u64`.
567 let truncated = [0u8; 6];
568 assert_eq!(
569 deserialize::<Record>(&truncated).unwrap(),
570 Record {
571 id: 0,
572 added_later: 0,
573 }
574 );
575 }
576
577 #[test]
578 fn discard_seq_nested() {
579 #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
580 #[wincode(internal)]
581 struct Full {
582 pairs: Vec<(u32, u16)>,
583 singles: Vec<u32>,
584 nested: Vec<Vec<u8>>,
585 map: BTreeMap<u32, u64>,
586 tag: u8,
587 }
588
589 #[derive(SchemaRead, Debug, PartialEq)]
590 #[wincode(internal)]
591 struct Partial {
592 // Composite static element via the trusted-window path.
593 #[wincode(with = "DiscardSeq<(u32, u16), BincodeLen>")]
594 pairs: Vec<(u32, u16)>,
595 // `Discard` composed as the element schema.
596 #[wincode(with = "DiscardSeq<Discard<u32>, BincodeLen>")]
597 singles: Vec<u32>,
598 // Nested `DiscardSeq`: discards `Vec<Vec<u8>>` with no allocation at
599 // any level while still yielding `Vec<Vec<u8>>`.
600 #[wincode(with = "DiscardSeq<DiscardSeq<u8, BincodeLen>, BincodeLen>")]
601 nested: Vec<Vec<u8>>,
602 // A map is a sequence of `(K, V)` entries; the field type is now an
603 // incidental `Vec`.
604 #[wincode(with = "DiscardSeq<(u32, u64), BincodeLen>")]
605 map: Vec<(u32, u64)>,
606 tag: u8,
607 }
608
609 let bytes = serialize(&Full {
610 pairs: vec![(1, 2), (3, 4)],
611 singles: vec![10, 20, 30],
612 nested: vec![vec![1, 2], vec![], vec![9]],
613 map: BTreeMap::from([(1, 10), (2, 20), (3, 30)]),
614 tag: 7,
615 })
616 .unwrap();
617 assert_eq!(
618 deserialize::<Partial>(&bytes).unwrap(),
619 Partial {
620 pairs: Vec::new(),
621 singles: Vec::new(),
622 nested: Vec::new(),
623 map: Vec::new(),
624 tag: 7,
625 },
626 );
627 }
628
629 #[test]
630 fn discard_seq_validates_non_zero_copy_elements() {
631 #[derive(SchemaRead, Debug, PartialEq)]
632 #[wincode(internal)]
633 struct Partial {
634 #[wincode(with = "DiscardSeq<bool, BincodeLen>")]
635 flags: Vec<bool>,
636 }
637
638 let ok = serialize(&vec![true, false]).unwrap();
639 assert_eq!(
640 deserialize::<Partial>(&ok).unwrap(),
641 Partial { flags: Vec::new() },
642 );
643
644 // A non-canonical bool byte must still be rejected, not skipped.
645 let mut bad = ok.clone();
646 *bad.last_mut().unwrap() = 2;
647 assert!(deserialize::<Partial>(&bad).is_err());
648 }
649
650 #[test]
651 fn discard_string_as_bytes() {
652 #[derive(SchemaWrite, SchemaRead, Debug, PartialEq)]
653 #[wincode(internal)]
654 struct Full {
655 text: String,
656 tag: u8,
657 }
658
659 // A `String` shares its wire format with `Vec<u8>`, so it is discarded as
660 // raw bytes via `DiscardSeq<u8>`: no allocation, and the following field
661 // stays aligned.
662 #[derive(SchemaRead, Debug, PartialEq)]
663 #[wincode(internal)]
664 struct Partial {
665 #[wincode(with = "DiscardSeq<u8, BincodeLen>")]
666 text: Vec<u8>,
667 tag: u8,
668 }
669
670 let bytes = serialize(&Full {
671 text: "hello".into(),
672 tag: 7,
673 })
674 .unwrap();
675 assert_eq!(
676 deserialize::<Partial>(&bytes).unwrap(),
677 Partial {
678 text: Vec::new(),
679 tag: 7,
680 },
681 );
682 }
683}