From 1a415de7fe59d1bc36911c9e621a6c53628d03f1 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Thu, 21 Mar 2024 03:07:03 +0100 Subject: [PATCH] Switch strategy for bytes-oriented encoding (fixes #70) --- crates/musli-descriptive/src/en.rs | 18 ++- crates/musli-descriptive/src/tests.rs | 1 + crates/musli-json/src/en/mod.rs | 14 +- crates/musli-macros/src/internals/attr.rs | 16 +- crates/musli-macros/src/internals/build.rs | 4 +- crates/musli-macros/src/internals/mode.rs | 22 +-- crates/musli-macros/src/internals/tokens.rs | 4 + crates/musli-storage/src/en.rs | 18 ++- crates/musli-value/src/en.rs | 14 +- crates/musli-wire/src/en.rs | 18 ++- crates/musli-wire/src/tests.rs | 1 + crates/musli/src/compat.rs | 130 ++++++++++++++++ crates/musli/src/compat/alloc.rs | 82 ---------- crates/musli/src/compat/mod.rs | 116 -------------- crates/musli/src/compat/packed.rs | 4 - crates/musli/src/de/decode.rs | 4 +- crates/musli/src/de/decode_bytes.rs | 52 +++++++ crates/musli/src/de/decoder.rs | 14 +- crates/musli/src/de/mod.rs | 3 + crates/musli/src/derives.rs | 29 ++++ crates/musli/src/en/encode.rs | 2 +- crates/musli/src/en/encode_bytes.rs | 78 ++++++++++ crates/musli/src/en/encoder.rs | 85 +++++----- crates/musli/src/en/mod.rs | 3 + crates/musli/src/impls/alloc.rs | 146 ++++++++++++++++-- crates/musli/src/impls/mod.rs | 84 +++++++++- crates/musli/src/impls/net.rs | 4 +- crates/tests/src/models.rs | 4 + .../tests/tests/{bytes_compat.rs => bytes.rs} | 12 ++ crates/tests/tests/complex_field.rs | 9 +- crates/tests/tests/large_enum.rs | 6 + crates/tests/tests/pack_compat.rs | 2 + 32 files changed, 680 insertions(+), 319 deletions(-) create mode 100644 crates/musli/src/compat.rs delete mode 100644 crates/musli/src/compat/alloc.rs delete mode 100644 crates/musli/src/compat/mod.rs delete mode 100644 crates/musli/src/compat/packed.rs create mode 100644 crates/musli/src/de/decode_bytes.rs create mode 100644 crates/musli/src/en/encode_bytes.rs rename crates/tests/tests/{bytes_compat.rs => bytes.rs} (55%) diff --git a/crates/musli-descriptive/src/en.rs b/crates/musli-descriptive/src/en.rs index 3428c7da3..2f7372443 100644 --- a/crates/musli-descriptive/src/en.rs +++ b/crates/musli-descriptive/src/en.rs @@ -95,8 +95,8 @@ where } #[inline] - fn encode_array(self, cx: &C, array: [u8; N]) -> Result { - self.encode_bytes(cx, array.as_slice()) + fn encode_array(self, cx: &C, array: &[u8; N]) -> Result { + self.encode_bytes(cx, array) } #[inline] @@ -107,12 +107,20 @@ where } #[inline] - fn encode_bytes_vectored(mut self, cx: &C, vectors: &[&[u8]]) -> Result { - let len = vectors.iter().map(|v| v.len()).sum(); + fn encode_bytes_vectored( + mut self, + cx: &C, + len: usize, + vectors: I, + ) -> Result + where + I: IntoIterator, + I::Item: AsRef<[u8]>, + { encode_prefix::<_, _, F>(cx, self.writer.borrow_mut(), Kind::Bytes, len)?; for bytes in vectors { - self.writer.write_bytes(cx, bytes)?; + self.writer.write_bytes(cx, bytes.as_ref())?; } Ok(()) diff --git a/crates/musli-descriptive/src/tests.rs b/crates/musli-descriptive/src/tests.rs index 9f86d824a..65d3f59c9 100644 --- a/crates/musli-descriptive/src/tests.rs +++ b/crates/musli-descriptive/src/tests.rs @@ -23,6 +23,7 @@ struct To { #[derive(Debug, PartialEq, Encode, Decode)] #[musli(packed)] struct Field { + #[musli(bytes)] value: [u8; N], } diff --git a/crates/musli-json/src/en/mod.rs b/crates/musli-json/src/en/mod.rs index b8459f83d..08e4cdd0b 100644 --- a/crates/musli-json/src/en/mod.rs +++ b/crates/musli-json/src/en/mod.rs @@ -169,8 +169,8 @@ where } #[inline] - fn encode_array(self, cx: &C, bytes: [u8; N]) -> Result { - self.encode_bytes(cx, bytes.as_slice()) + fn encode_array(self, cx: &C, bytes: &[u8; N]) -> Result { + self.encode_bytes(cx, bytes) } #[inline] @@ -195,11 +195,15 @@ where } #[inline] - fn encode_bytes_vectored(self, cx: &C, bytes: &[&[u8]]) -> Result { + fn encode_bytes_vectored(self, cx: &C, _: usize, vectors: I) -> Result + where + I: IntoIterator, + I::Item: AsRef<[u8]>, + { let mut seq = JsonArrayEncoder::<_>::new(cx, self.writer)?; - for bb in bytes { - for b in *bb { + for bb in vectors { + for &b in bb.as_ref() { seq.push(cx, b)?; } } diff --git a/crates/musli-macros/src/internals/attr.rs b/crates/musli-macros/src/internals/attr.rs index a3867e6ec..c5bf5d7bb 100644 --- a/crates/musli-macros/src/internals/attr.rs +++ b/crates/musli-macros/src/internals/attr.rs @@ -574,6 +574,8 @@ layer! { default_field: (), /// Use the alternate TraceDecode for the field. trace: (), + /// Use the alternate EncodeBytes for the field. + bytes: (), @multiple } } @@ -587,7 +589,8 @@ impl Field { (*span, encode_path.clone()) } else { let trace = self.trace(mode).is_some(); - let encode_path = mode.encode_t_encode(trace); + let bytes = self.bytes(mode).is_some(); + let encode_path = mode.encode_t_encode(trace, bytes); (span, encode_path) } } @@ -600,7 +603,8 @@ impl Field { (*span, decode_path.clone()) } else { let trace = self.trace(mode).is_some(); - let decode_path = mode.decode_t_decode(trace); + let bytes = self.bytes(mode).is_some(); + let decode_path = mode.decode_t_decode(trace, bytes); (span, decode_path) } } @@ -692,12 +696,18 @@ pub(crate) fn field_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> Field { return Ok(()); } - // parse #[musli(default)] + // parse #[musli(trace)] if meta.path.is_ident("trace") { new.trace.push((meta.path.span(), ())); return Ok(()); } + // parse #[musli(bytes)] + if meta.path.is_ident("bytes") { + new.bytes.push((meta.path.span(), ())); + return Ok(()); + } + Err(syn::Error::new_spanned( meta.path, format_args!("#[{ATTR}] Unsupported field attribute"), diff --git a/crates/musli-macros/src/internals/build.rs b/crates/musli-macros/src/internals/build.rs index 84a5d8d96..016000c85 100644 --- a/crates/musli-macros/src/internals/build.rs +++ b/crates/musli-macros/src/internals/build.rs @@ -202,8 +202,8 @@ pub(crate) fn setup<'a>( decode_bounds: e.type_attr.decode_bounds(mode), expansion, data, - decode_t_decode: mode.decode_t_decode(false), - encode_t_encode: mode.encode_t_encode(false), + decode_t_decode: mode.decode_t_decode(false, false), + encode_t_encode: mode.encode_t_encode(false, false), enum_tagging_span: e.type_attr.enum_tagging_span(mode), }) } diff --git a/crates/musli-macros/src/internals/mode.rs b/crates/musli-macros/src/internals/mode.rs index df5a76b28..400cdf62d 100644 --- a/crates/musli-macros/src/internals/mode.rs +++ b/crates/musli-macros/src/internals/mode.rs @@ -32,11 +32,11 @@ pub(crate) struct Mode<'a> { impl<'a> Mode<'a> { /// Construct a typed encode call. - pub(crate) fn encode_t_encode(&self, trace: bool) -> syn::Path { - let (mut encode_t, name) = if trace { - (self.tokens.trace_encode_t.clone(), "trace_encode") - } else { - (self.tokens.encode_t.clone(), "encode") + pub(crate) fn encode_t_encode(&self, trace: bool, bytes: bool) -> syn::Path { + let (mut encode_t, name) = match (trace, bytes) { + (_, true) => (self.tokens.encode_bytes_t.clone(), "encode_bytes"), + (true, _) => (self.tokens.trace_encode_t.clone(), "trace_encode"), + _ => (self.tokens.encode_t.clone(), "encode"), }; if let Some(segment) = encode_t.segments.last_mut() { @@ -54,11 +54,11 @@ impl<'a> Mode<'a> { } /// Construct a typed encode call. - pub(crate) fn decode_t_decode(&self, trace: bool) -> syn::Path { - let (mut decode_t, method) = if trace { - (self.tokens.trace_decode_t.clone(), "trace_decode") - } else { - (self.tokens.decode_t.clone(), "decode") + pub(crate) fn decode_t_decode(&self, trace: bool, bytes: bool) -> syn::Path { + let (mut decode_t, name) = match (trace, bytes) { + (_, true) => (self.tokens.decode_bytes_t.clone(), "decode_bytes"), + (true, _) => (self.tokens.trace_decode_t.clone(), "trace_decode"), + _ => (self.tokens.decode_t.clone(), "decode"), }; if let Some(segment) = decode_t.segments.last_mut() { @@ -68,7 +68,7 @@ impl<'a> Mode<'a> { decode_t .segments .push(syn::PathSegment::from(syn::Ident::new( - method, + name, decode_t.span(), ))); diff --git a/crates/musli-macros/src/internals/tokens.rs b/crates/musli-macros/src/internals/tokens.rs index aaa6314a6..1a6f39d71 100644 --- a/crates/musli-macros/src/internals/tokens.rs +++ b/crates/musli-macros/src/internals/tokens.rs @@ -7,10 +7,12 @@ pub(crate) struct Tokens { pub(crate) context_t: syn::Path, pub(crate) core_result: syn::Path, pub(crate) decode_t: syn::Path, + pub(crate) decode_bytes_t: syn::Path, pub(crate) decoder_t: syn::Path, pub(crate) default_function: syn::Path, pub(crate) default_mode: syn::Path, pub(crate) encode_t: syn::Path, + pub(crate) encode_bytes_t: syn::Path, pub(crate) encoder_t: syn::Path, pub(crate) fmt: syn::Path, pub(crate) option_none: syn::Path, @@ -38,10 +40,12 @@ impl Tokens { context_t: path(span, prefix, ["Context"]), core_result: core(span, ["result", "Result"]), decode_t: path(span, prefix, ["de", "Decode"]), + decode_bytes_t: path(span, prefix, ["de", "DecodeBytes"]), decoder_t: path(span, prefix, ["de", "Decoder"]), default_function: core(span, ["default", "Default", "default"]), default_mode: path(span, prefix, ["mode", "DefaultMode"]), encode_t: path(span, prefix, ["en", "Encode"]), + encode_bytes_t: path(span, prefix, ["en", "EncodeBytes"]), encoder_t: path(span, prefix, ["en", "Encoder"]), fmt: core(span, ["fmt"]), option_none: core(span, ["option", "Option", "None"]), diff --git a/crates/musli-storage/src/en.rs b/crates/musli-storage/src/en.rs index 2305d84f6..29a68c00e 100644 --- a/crates/musli-storage/src/en.rs +++ b/crates/musli-storage/src/en.rs @@ -72,9 +72,9 @@ where fn encode_array( mut self, cx: &C, - array: [u8; N], + array: &[u8; N], ) -> Result { - self.writer.write_bytes(cx, &array) + self.writer.write_bytes(cx, array) } #[inline] @@ -85,12 +85,20 @@ where } #[inline] - fn encode_bytes_vectored(mut self, cx: &C, vectors: &[&[u8]]) -> Result { - let len = vectors.iter().map(|v| v.len()).sum(); + fn encode_bytes_vectored( + mut self, + cx: &C, + len: usize, + vectors: I, + ) -> Result + where + I: IntoIterator, + I::Item: AsRef<[u8]>, + { musli_common::int::encode_usize::<_, _, F>(cx, self.writer.borrow_mut(), len)?; for bytes in vectors { - self.writer.write_bytes(cx, bytes)?; + self.writer.write_bytes(cx, bytes.as_ref())?; } Ok(()) diff --git a/crates/musli-value/src/en.rs b/crates/musli-value/src/en.rs index a1c392865..0199dbc8e 100644 --- a/crates/musli-value/src/en.rs +++ b/crates/musli-value/src/en.rs @@ -206,7 +206,7 @@ where #[cfg(feature = "alloc")] #[inline] - fn encode_array(self, _: &C, array: [u8; N]) -> Result { + fn encode_array(self, _: &C, array: &[u8; N]) -> Result { self.output.write(Value::Bytes(array.into())); Ok(()) } @@ -220,11 +220,15 @@ where #[cfg(feature = "alloc")] #[inline] - fn encode_bytes_vectored(self, _: &C, input: &[&[u8]]) -> Result { - let mut bytes = Vec::new(); + fn encode_bytes_vectored(self, _: &C, len: usize, vectors: I) -> Result + where + I: IntoIterator, + I::Item: AsRef<[u8]>, + { + let mut bytes = Vec::with_capacity(len); - for b in input { - bytes.extend_from_slice(b); + for b in vectors { + bytes.extend_from_slice(b.as_ref()); } self.output.write(Value::Bytes(bytes)); diff --git a/crates/musli-wire/src/en.rs b/crates/musli-wire/src/en.rs index a1ee21546..f3ef7c152 100644 --- a/crates/musli-wire/src/en.rs +++ b/crates/musli-wire/src/en.rs @@ -126,8 +126,8 @@ where } #[inline] - fn encode_array(self, cx: &C, array: [u8; N]) -> Result { - self.encode_bytes(cx, array.as_slice()) + fn encode_array(self, cx: &C, array: &[u8; N]) -> Result { + self.encode_bytes(cx, array) } #[inline] @@ -138,12 +138,20 @@ where } #[inline] - fn encode_bytes_vectored(mut self, cx: &C, vectors: &[&[u8]]) -> Result { - let len = vectors.iter().map(|v| v.len()).sum(); + fn encode_bytes_vectored( + mut self, + cx: &C, + len: usize, + vectors: I, + ) -> Result + where + I: IntoIterator, + I::Item: AsRef<[u8]>, + { encode_prefix::<_, _, F>(cx, self.writer.borrow_mut(), len)?; for bytes in vectors { - self.writer.write_bytes(cx, bytes)?; + self.writer.write_bytes(cx, bytes.as_ref())?; } Ok(()) diff --git a/crates/musli-wire/src/tests.rs b/crates/musli-wire/src/tests.rs index 566a73bf7..6d48c9e52 100644 --- a/crates/musli-wire/src/tests.rs +++ b/crates/musli-wire/src/tests.rs @@ -23,6 +23,7 @@ struct To { #[derive(Debug, PartialEq, Encode, Decode)] #[musli(packed)] struct Field { + #[musli(bytes)] value: [u8; N], } diff --git a/crates/musli/src/compat.rs b/crates/musli/src/compat.rs new file mode 100644 index 000000000..273103839 --- /dev/null +++ b/crates/musli/src/compat.rs @@ -0,0 +1,130 @@ +//! Wrapper types which ensures that a given field is encoded or decoded as a +//! certain kind of value. + +use crate::de::{Decode, DecodeBytes, Decoder, SequenceDecoder}; +use crate::en::{Encode, EncodeBytes, Encoder, SequenceEncoder}; +use crate::Context; + +/// Ensures that the given value `T` is encoded as a sequence. +/// +/// This exists as a simple shim for certain types, to ensure they're encoded as +/// a sequence, such as `Sequence<()>`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct Sequence(pub T); + +impl Sequence { + /// Construct a new sequence wrapper. + pub const fn new(value: T) -> Self { + Self(value) + } +} + +impl Encode for Sequence<()> { + #[inline] + fn encode(&self, cx: &C, encoder: E) -> Result + where + C: ?Sized + Context, + E: Encoder, + { + encoder.encode_sequence(cx, 0)?.end(cx) + } +} + +impl<'de, M> Decode<'de, M> for Sequence<()> { + fn decode(cx: &C, decoder: D) -> Result + where + C: ?Sized + Context, + D: Decoder<'de, C>, + { + let seq = decoder.decode_sequence(cx)?; + seq.end(cx)?; + Ok(Self(())) + } +} + +/// Ensures that the given value `T` is encoded as bytes. +/// +/// This is useful for values which have a generic implementation to be encoded +/// as a sequence, such as [`Vec`] and [`VecDeque`]. +/// +/// # Examples +/// +/// ``` +/// use musli::{Context, Decode, Decoder}; +/// use musli::compat::Bytes; +/// +/// struct Struct { +/// field: Vec, +/// } +/// +/// impl<'de, M> Decode<'de, M> for Struct { +/// fn decode(cx: &C, decoder: D) -> Result +/// where +/// C: ?Sized + Context, +/// D: Decoder<'de, C>, +/// { +/// let Bytes(field) = Decode::decode(cx, decoder)?; +/// +/// Ok(Struct { +/// field, +/// }) +/// } +/// } +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)] +#[musli(crate, bound = {T: EncodeBytes}, decode_bound = {T: DecodeBytes<'de, M>})] +#[repr(transparent)] +pub struct Bytes(#[musli(bytes)] pub T); + +impl AsRef<[u8]> for Bytes +where + T: AsRef<[u8]>, +{ + #[inline] + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl AsMut<[u8]> for Bytes +where + T: AsMut<[u8]>, +{ + #[inline] + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } +} + +/// Treat `T` as if its packed. +/// +/// This is for example implemented for tuples. +/// +/// # Examples +/// +/// ``` +/// use musli::{Context, Decode, Decoder}; +/// use musli::compat::Packed; +/// +/// struct Struct { +/// field: u8, +/// field2: u32, +/// } +/// +/// impl<'de, M> Decode<'de, M> for Struct { +/// fn decode(cx: &C, decoder: D) -> Result +/// where +/// C: ?Sized + Context, +/// D: Decoder<'de, C>, +/// { +/// let Packed((field, field2)) = Decode::decode(cx, decoder)?; +/// +/// Ok(Struct { +/// field, +/// field2, +/// }) +/// } +/// } +/// ``` +pub struct Packed(pub T); diff --git a/crates/musli/src/compat/alloc.rs b/crates/musli/src/compat/alloc.rs deleted file mode 100644 index d5c14da95..000000000 --- a/crates/musli/src/compat/alloc.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! Wrapper types which ensures that a given field is encoded or decoded as a -//! certain kind of value. - -use core::fmt; - -use alloc::collections::VecDeque; -use alloc::vec::Vec; - -use crate::compat::Bytes; -use crate::de::{Decode, Decoder, ValueVisitor}; -use crate::en::{Encode, Encoder}; -use crate::Context; - -impl Encode for Bytes> { - #[inline] - fn encode(&self, cx: &C, encoder: E) -> Result - where - C: ?Sized + Context, - E: Encoder, - { - encoder.encode_bytes(cx, self.0.as_slice()) - } -} - -impl<'de, M> Decode<'de, M> for Bytes> { - #[inline] - fn decode(cx: &C, decoder: D) -> Result - where - C: ?Sized + Context, - D: Decoder<'de, C>, - { - struct Visitor; - - impl<'de, C> ValueVisitor<'de, C, [u8]> for Visitor - where - C: ?Sized + Context, - { - type Ok = Vec; - - #[inline] - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "bytes") - } - - #[inline] - fn visit_borrowed(self, _: &C, bytes: &'de [u8]) -> Result { - Ok(bytes.to_vec()) - } - - #[inline] - fn visit_ref(self, _: &C, bytes: &[u8]) -> Result { - Ok(bytes.to_vec()) - } - } - - decoder.decode_bytes(cx, Visitor).map(Bytes) - } -} - -impl Encode for Bytes> { - #[inline] - fn encode(&self, cx: &C, encoder: E) -> Result - where - C: ?Sized + Context, - E: Encoder, - { - let (first, second) = self.0.as_slices(); - encoder.encode_bytes_vectored(cx, &[first, second]) - } -} - -impl<'de, M> Decode<'de, M> for Bytes> { - #[inline] - fn decode(cx: &C, decoder: D) -> Result - where - C: ?Sized + Context, - D: Decoder<'de, C>, - { - cx.decode(decoder) - .map(|Bytes(bytes): Bytes>| Bytes(VecDeque::from(bytes))) - } -} diff --git a/crates/musli/src/compat/mod.rs b/crates/musli/src/compat/mod.rs deleted file mode 100644 index d976e96fb..000000000 --- a/crates/musli/src/compat/mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Wrapper types which ensures that a given field is encoded or decoded as a -//! certain kind of value. - -#[cfg(feature = "alloc")] -mod alloc; -mod packed; - -pub use self::packed::Packed; - -use crate::de::{Decode, Decoder, SequenceDecoder}; -use crate::en::{Encode, Encoder, SequenceEncoder}; -use crate::Context; - -/// Ensures that the given value `T` is encoded as a sequence. -/// -/// In contrast to the typical values that are encoded as sequences such as -/// [`Vec`], this can take a sequence by reference. -/// -/// We must use a wrapper like this, because we can't provide an implementation -/// for `&[T]` since it would conflict with `&[u8]` which is specialized to -/// encode and decode as a byte array. -/// -/// [`Vec`]: std::vec::Vec -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct Sequence(pub T); - -impl Sequence { - /// Construct a new sequence wrapper. - pub const fn new(value: T) -> Self { - Self(value) - } -} - -impl Encode for Sequence<&'_ [T]> -where - T: Encode, -{ - #[inline] - fn encode(&self, cx: &C, encoder: E) -> Result - where - C: ?Sized + Context, - E: Encoder, - { - let mut seq = encoder.encode_sequence(cx, self.0.len())?; - - for (index, value) in self.0.iter().enumerate() { - cx.enter_sequence_index(index); - let encoder = seq.encode_next(cx)?; - T::encode(value, cx, encoder)?; - cx.leave_sequence_index(); - } - - seq.end(cx) - } -} - -impl Encode for Sequence<()> { - #[inline] - fn encode(&self, cx: &C, encoder: E) -> Result - where - C: ?Sized + Context, - E: Encoder, - { - encoder.encode_sequence(cx, 0)?.end(cx) - } -} - -impl<'de, M> Decode<'de, M> for Sequence<()> { - fn decode(cx: &C, decoder: D) -> Result - where - C: ?Sized + Context, - D: Decoder<'de, C>, - { - let seq = decoder.decode_sequence(cx)?; - seq.end(cx)?; - Ok(Self(())) - } -} - -/// Ensures that the given value `T` is encoded as bytes. -/// -/// This is useful for values which have a generic implementation to be encoded -/// as a sequence, such as [`Vec`] and [`VecDeque`]. -/// -/// We must use a wrapper like this, because we can't provide an implementation -/// for `Vec` since it would conflict with `Vec` which is generalized to -/// encode as a sequence. -/// -/// [`Vec`]: std::vec::Vec -/// [`VecDeque`]: std::collections::VecDeque -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct Bytes(pub T); - -impl Encode for Bytes<[u8; N]> { - #[inline] - fn encode(&self, cx: &C, encoder: E) -> Result - where - C: ?Sized + Context, - E: Encoder, - { - encoder.encode_array(cx, self.0) - } -} - -impl<'de, M, const N: usize> Decode<'de, M> for Bytes<[u8; N]> { - #[inline] - fn decode(cx: &C, decoder: D) -> Result - where - C: ?Sized + Context, - D: Decoder<'de, C>, - { - decoder.decode_array(cx).map(Self) - } -} diff --git a/crates/musli/src/compat/packed.rs b/crates/musli/src/compat/packed.rs deleted file mode 100644 index dad906b39..000000000 --- a/crates/musli/src/compat/packed.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// Treat `T` as its packed. -/// -/// This is implemented for tuples. -pub struct Packed(pub T); diff --git a/crates/musli/src/de/decode.rs b/crates/musli/src/de/decode.rs index 0c279e097..959d0e8e5 100644 --- a/crates/musli/src/de/decode.rs +++ b/crates/musli/src/de/decode.rs @@ -8,9 +8,9 @@ pub use musli_macros::Decode; /// Trait governing how types are decoded. /// -/// This is typically implemented automatically using the [`Encode` derive]. +/// This is typically implemented automatically using the [`Decode` derive]. /// -/// [`Encode` derive]: https://docs.rs/musli/latest/musli/derives/ +/// [`Decode` derive]: https://docs.rs/musli/latest/musli/derives/ /// /// # Examples /// diff --git a/crates/musli/src/de/decode_bytes.rs b/crates/musli/src/de/decode_bytes.rs new file mode 100644 index 000000000..f99164389 --- /dev/null +++ b/crates/musli/src/de/decode_bytes.rs @@ -0,0 +1,52 @@ +use crate::de::Decoder; +use crate::mode::DefaultMode; +use crate::Context; + +/// Trait governing how types are decoded as bytes. +/// +/// This is typically used automatically through the `#[musli(bytes)]` attribute +/// through the [`Decode` derive]. +/// +/// [`Decode` derive]: https://docs.rs/musli/latest/musli/derives/ +/// +/// # Examples +/// +/// ``` +/// use musli::Decode; +/// +/// #[derive(Decode)] +/// struct MyType { +/// #[musli(bytes)] +/// data: [u8; 128], +/// } +/// ```` +/// +/// Implementing manually: +/// +/// ``` +/// use musli::{Context, Decode, Decoder}; +/// use musli::de::DecodeBytes; +/// +/// struct MyType { +/// data: [u8; 128], +/// } +/// +/// impl<'de, M> Decode<'de, M> for MyType { +/// fn decode(cx: &C, decoder: D) -> Result +/// where +/// C: ?Sized + Context, +/// D: Decoder<'de, C>, +/// { +/// Ok(Self { +/// data: DecodeBytes::decode_bytes(cx, decoder)?, +/// }) +/// } +/// } +/// ``` +pub trait DecodeBytes<'de, M = DefaultMode>: Sized { + /// Decode the given input as bytes. + fn decode_bytes(cx: &C, decoder: D) -> Result + where + C: ?Sized + Context, + D: Decoder<'de, C>; +} diff --git a/crates/musli/src/de/decoder.rs b/crates/musli/src/de/decoder.rs index 08bd32493..0df7cb6ad 100644 --- a/crates/musli/src/de/decoder.rs +++ b/crates/musli/src/de/decoder.rs @@ -1,3 +1,5 @@ +#![allow(unused_variables)] + use core::fmt; use core::marker::PhantomData; @@ -90,7 +92,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// detailed (`a 32-bit unsigned integer`) to vague (`a number`). /// /// This is used to construct dynamic containers of types. - fn type_hint(&mut self, _: &C) -> Result { + fn type_hint(&mut self, cx: &C) -> Result { Ok(TypeHint::Any) } @@ -876,7 +878,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// Decode an unknown number using a visitor that can handle arbitrary /// precision numbers. #[inline] - fn decode_number(self, cx: &C, _: V) -> Result + fn decode_number(self, cx: &C, visitor: V) -> Result where V: NumberVisitor<'de, C>, { @@ -986,7 +988,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// } /// ``` #[inline] - fn decode_bytes(self, cx: &C, _: V) -> Result + fn decode_bytes(self, cx: &C, visitor: V) -> Result where V: ValueVisitor<'de, C, [u8]>, { @@ -1054,7 +1056,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// } /// ``` #[inline] - fn decode_string(self, cx: &C, _: V) -> Result + fn decode_string(self, cx: &C, visitor: V) -> Result where V: ValueVisitor<'de, C, str>, { @@ -1592,7 +1594,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// } /// ``` #[inline] - fn decode_struct(self, cx: &C, _: Option) -> Result { + fn decode_struct(self, cx: &C, fields: Option) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Struct, &ExpectingWrapper::new(self), @@ -1651,7 +1653,7 @@ pub trait Decoder<'de, C: ?Sized + Context>: Sized { /// If the current encoding does not support dynamic decoding, /// [`Visitor::visit_any`] will be called with the current decoder. #[inline] - fn decode_any(self, cx: &C, _: V) -> Result + fn decode_any(self, cx: &C, visitor: V) -> Result where V: Visitor<'de, C>, { diff --git a/crates/musli/src/de/mod.rs b/crates/musli/src/de/mod.rs index 06c017e7d..6e731c74b 100644 --- a/crates/musli/src/de/mod.rs +++ b/crates/musli/src/de/mod.rs @@ -22,6 +22,9 @@ pub use self::as_decoder::AsDecoder; mod decode; pub use self::decode::{Decode, TraceDecode}; +mod decode_bytes; +pub use self::decode_bytes::DecodeBytes; + mod decoder; pub use self::decoder::Decoder; diff --git a/crates/musli/src/derives.rs b/crates/musli/src/derives.rs index 5345ba777..5becc93ad 100644 --- a/crates/musli/src/derives.rs +++ b/crates/musli/src/derives.rs @@ -564,6 +564,33 @@ //! //!
//! +//! #### `#[musli(bytes)]` +//! +//! This specifies that encoding and decoding should happen through the +//! [`EncodeBytes`] and [`DecodeBytes`] traits, instead of the default +//! [`Encode`] and [`Decode`]. +//! +//! These traits contained implementations which are biased towards encoding the +//! field as an array of bytes. +//! +//! ``` +//! use std::collections::VecDeque; +//! +//! use musli::{Decode, Encode}; +//! +//! #[derive(Decode, Encode)] +//! struct Container<'de> { +//! #[musli(bytes)] +//! vec: Vec, +//! #[musli(bytes)] +//! vec_deque: VecDeque, +//! #[musli(bytes)] +//! bytes: &'de [u8], +//! } +//! ``` +//! +//!
+//! //! #### `#[musli(with = )]` //! //! This specifies the path to a module to use instead of the fields default @@ -847,11 +874,13 @@ //! determine which decoder implementation to call. //! //! [`Decode`]: crate::Decode +//! [`DecodeBytes`]: crate::de::DecodeBytes //! [`Decoder::decode_buffer`]: crate::Decoder::decode_buffer //! [`Decoder::decode_variant`]: crate::Decoder::decode_variant //! [`Decoder`]: crate::Decoder //! [`DefaultMode`]: crate::mode::DefaultMode //! [`Encode`]: crate::Encode +//! [`EncodeBytes`]: crate::en::EncodeBytes //! [`Encoder::encode_variant`]: crate::Encoder::encode_variant //! [`Encoder`]: crate::Encoder //! [`TraceDecode`]: crate::de::TraceDecode diff --git a/crates/musli/src/en/encode.rs b/crates/musli/src/en/encode.rs index 927487c70..a430f75f4 100644 --- a/crates/musli/src/en/encode.rs +++ b/crates/musli/src/en/encode.rs @@ -36,7 +36,7 @@ pub use musli_macros::Encode; /// C: ?Sized + Context, /// E: Encoder, /// { -/// encoder.encode_array(cx, self.data) +/// encoder.encode_array(cx, &self.data) /// } /// } /// ``` diff --git a/crates/musli/src/en/encode_bytes.rs b/crates/musli/src/en/encode_bytes.rs new file mode 100644 index 000000000..1bb10e3b4 --- /dev/null +++ b/crates/musli/src/en/encode_bytes.rs @@ -0,0 +1,78 @@ +use crate::en::Encoder; +use crate::mode::DefaultMode; +use crate::Context; + +/// Trait governing how a type is encoded as bytes. +/// +/// This is typically used automatically through the `#[musli(bytes)]` attribute +/// through the [`Encode` derive]. +/// +/// [`Encode` derive]: https://docs.rs/musli/latest/musli/derives/ +/// +/// # Examples +/// +/// ``` +/// use musli::Encode; +/// +/// #[derive(Encode)] +/// struct MyType { +/// #[musli(bytes)] +/// data: [u8; 128], +/// } +/// ```` +/// +/// Implementing manually: +/// +/// ``` +/// use musli::{Context, Encode, Encoder}; +/// use musli::en::EncodeBytes; +/// +/// struct MyType { +/// data: [u8; 128], +/// } +/// +/// impl Encode for MyType { +/// fn encode(&self, cx: &C, encoder: E) -> Result +/// where +/// C: ?Sized + Context, +/// E: Encoder, +/// { +/// self.data.encode_bytes(cx, encoder) +/// } +/// } +/// ``` +pub trait EncodeBytes { + /// Encode the given output as bytes. + fn encode_bytes(&self, cx: &C, encoder: E) -> Result + where + C: ?Sized + Context, + E: Encoder; +} + +impl EncodeBytes for &T +where + T: ?Sized + EncodeBytes, +{ + #[inline] + fn encode_bytes(&self, cx: &C, encoder: E) -> Result + where + C: ?Sized + Context, + E: Encoder, + { + T::encode_bytes(*self, cx, encoder) + } +} + +impl EncodeBytes for &mut T +where + T: ?Sized + EncodeBytes, +{ + #[inline] + fn encode_bytes(&self, cx: &C, encoder: E) -> Result + where + C: ?Sized + Context, + E: Encoder, + { + T::encode_bytes(*self, cx, encoder) + } +} diff --git a/crates/musli/src/en/encoder.rs b/crates/musli/src/en/encoder.rs index a56f3a6f9..06d7e3b59 100644 --- a/crates/musli/src/en/encoder.rs +++ b/crates/musli/src/en/encoder.rs @@ -1,3 +1,5 @@ +#![allow(unused_variables)] + use core::fmt; use core::marker::PhantomData; @@ -132,7 +134,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_bool(self, cx: &C, _: bool) -> Result { + fn encode_bool(self, cx: &C, v: bool) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Bool, &ExpectingWrapper::new(self), @@ -172,7 +174,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_char(self, cx: &C, _: char) -> Result { + fn encode_char(self, cx: &C, v: char) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Char, &ExpectingWrapper::new(self), @@ -212,7 +214,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_u8(self, cx: &C, _: u8) -> Result { + fn encode_u8(self, cx: &C, v: u8) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Unsigned8, &ExpectingWrapper::new(self), @@ -252,7 +254,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_u16(self, cx: &C, _: u16) -> Result { + fn encode_u16(self, cx: &C, v: u16) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Unsigned16, &ExpectingWrapper::new(self), @@ -292,7 +294,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_u32(self, cx: &C, _: u32) -> Result { + fn encode_u32(self, cx: &C, v: u32) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Unsigned32, &ExpectingWrapper::new(self), @@ -332,7 +334,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_u64(self, cx: &C, _: u64) -> Result { + fn encode_u64(self, cx: &C, v: u64) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Unsigned64, &ExpectingWrapper::new(self), @@ -372,7 +374,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_u128(self, cx: &C, _: u128) -> Result { + fn encode_u128(self, cx: &C, v: u128) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Unsigned128, &ExpectingWrapper::new(self), @@ -412,7 +414,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_i8(self, cx: &C, _: i8) -> Result { + fn encode_i8(self, cx: &C, v: i8) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Signed8, &ExpectingWrapper::new(self), @@ -452,7 +454,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_i16(self, cx: &C, _: i16) -> Result { + fn encode_i16(self, cx: &C, v: i16) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Signed16, &ExpectingWrapper::new(self), @@ -492,7 +494,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_i32(self, cx: &C, _: i32) -> Result { + fn encode_i32(self, cx: &C, v: i32) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Signed32, &ExpectingWrapper::new(self), @@ -532,7 +534,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_i64(self, cx: &C, _: i64) -> Result { + fn encode_i64(self, cx: &C, v: i64) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Signed64, &ExpectingWrapper::new(self), @@ -572,7 +574,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_i128(self, cx: &C, _: i128) -> Result { + fn encode_i128(self, cx: &C, v: i128) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Signed128, &ExpectingWrapper::new(self), @@ -612,7 +614,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_usize(self, cx: &C, _: usize) -> Result { + fn encode_usize(self, cx: &C, v: usize) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Usize, &ExpectingWrapper::new(self), @@ -652,7 +654,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_isize(self, cx: &C, _: isize) -> Result { + fn encode_isize(self, cx: &C, v: isize) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Isize, &ExpectingWrapper::new(self), @@ -692,7 +694,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_f32(self, cx: &C, _: f32) -> Result { + fn encode_f32(self, cx: &C, v: f32) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Float32, &ExpectingWrapper::new(self), @@ -732,7 +734,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_f64(self, cx: &C, _: f64) -> Result { + fn encode_f64(self, cx: &C, v: f64) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Float64, &ExpectingWrapper::new(self), @@ -767,12 +769,12 @@ pub trait Encoder: Sized { /// C: ?Sized + Context, /// E: Encoder, /// { - /// encoder.encode_array(cx, self.data) + /// encoder.encode_array(cx, &self.data) /// } /// } /// ``` #[inline] - fn encode_array(self, cx: &C, _: [u8; N]) -> Result { + fn encode_array(self, cx: &C, array: &[u8; N]) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Array, &ExpectingWrapper::new(self), @@ -791,6 +793,7 @@ pub trait Encoder: Sized { /// #[derive(Encode)] /// #[musli(packed)] /// struct MyType { + /// #[musli(bytes)] /// data: Vec /// } /// ``` @@ -812,7 +815,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_bytes(self, cx: &C, _: &[u8]) -> Result { + fn encode_bytes(self, cx: &C, bytes: &[u8]) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Bytes, &ExpectingWrapper::new(self), @@ -822,6 +825,10 @@ pub trait Encoder: Sized { /// Encode the given slices of bytes in sequence, with one following another /// as a single contiguous byte array. /// + /// The provided `len` is trusted, but providing the wrong length must never + /// result in any memory unsafety. It might just cause the payload to be + /// corrupted. + /// /// This can be useful to avoid allocations when a caller doesn't have /// access to a single byte sequence like in /// [`VecDeque`][std::collections::VecDeque]. @@ -855,12 +862,16 @@ pub trait Encoder: Sized { /// E: Encoder, /// { /// let (first, second) = self.data.as_slices(); - /// encoder.encode_bytes_vectored(cx, &[first, second]) + /// encoder.encode_bytes_vectored(cx, self.data.len(), [first, second]) /// } /// } /// ``` #[inline] - fn encode_bytes_vectored(self, cx: &C, _: &[&[u8]]) -> Result { + fn encode_bytes_vectored(self, cx: &C, len: usize, vectors: I) -> Result + where + I: IntoIterator, + I::Item: AsRef<[u8]>, + { Err(cx.message(expecting::unsupported_type( &expecting::Bytes, &ExpectingWrapper::new(self), @@ -900,7 +911,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_string(self, cx: &C, _: &str) -> Result { + fn encode_string(self, cx: &C, string: &str) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::String, &ExpectingWrapper::new(self), @@ -1086,11 +1097,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_sequence( - self, - cx: &C, - #[allow(unused)] len: usize, - ) -> Result { + fn encode_sequence(self, cx: &C, len: usize) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Sequence, &ExpectingWrapper::new(self), @@ -1129,11 +1136,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_tuple( - self, - cx: &C, - #[allow(unused)] len: usize, - ) -> Result { + fn encode_tuple(self, cx: &C, len: usize) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Tuple, &ExpectingWrapper::new(self), @@ -1177,7 +1180,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_map(self, cx: &C, #[allow(unused)] len: usize) -> Result { + fn encode_map(self, cx: &C, len: usize) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Map, &ExpectingWrapper::new(self), @@ -1216,11 +1219,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_map_entries( - self, - cx: &C, - #[allow(unused)] len: usize, - ) -> Result { + fn encode_map_entries(self, cx: &C, len: usize) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::MapPairs, &ExpectingWrapper::new(self), @@ -1254,7 +1253,7 @@ pub trait Encoder: Sized { /// } /// ``` #[inline] - fn encode_struct(self, cx: &C, _: usize) -> Result { + fn encode_struct(self, cx: &C, fields: usize) -> Result { Err(cx.message(expecting::unsupported_type( &expecting::Struct, &ExpectingWrapper::new(self), @@ -1414,8 +1413,8 @@ pub trait Encoder: Sized { fn encode_tuple_variant( self, cx: &C, - _: &T, - _: usize, + tag: &T, + fields: usize, ) -> Result where T: ?Sized + Encode, @@ -1472,8 +1471,8 @@ pub trait Encoder: Sized { fn encode_struct_variant( self, cx: &C, - _: &T, - _: usize, + tag: &T, + fields: usize, ) -> Result where T: ?Sized + Encode, diff --git a/crates/musli/src/en/mod.rs b/crates/musli/src/en/mod.rs index d3bfd42dd..be77a2520 100644 --- a/crates/musli/src/en/mod.rs +++ b/crates/musli/src/en/mod.rs @@ -19,6 +19,9 @@ mod encode; pub use self::encode::{Encode, TraceEncode}; +mod encode_bytes; +pub use self::encode_bytes::EncodeBytes; + mod encoder; pub use self::encoder::Encoder; diff --git a/crates/musli/src/impls/alloc.rs b/crates/musli/src/impls/alloc.rs index b8c1fe855..f7295db42 100644 --- a/crates/musli/src/impls/alloc.rs +++ b/crates/musli/src/impls/alloc.rs @@ -20,9 +20,12 @@ use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use crate::de::{ - Decode, Decoder, MapDecoder, MapEntryDecoder, SequenceDecoder, TraceDecode, ValueVisitor, + Decode, DecodeBytes, Decoder, MapDecoder, MapEntryDecoder, SequenceDecoder, TraceDecode, + ValueVisitor, +}; +use crate::en::{ + Encode, EncodeBytes, Encoder, MapEncoder, MapEntryEncoder, SequenceEncoder, TraceEncode, }; -use crate::en::{Encode, Encoder, MapEncoder, MapEntryEncoder, SequenceEncoder, TraceEncode}; use crate::internal::size_hint; use crate::Context; @@ -77,7 +80,10 @@ impl<'de, M> Decode<'de, M> for String { } } -impl Encode for Box { +impl Encode for Box +where + T: ?Sized + Encode, +{ #[inline] fn encode(&self, cx: &C, encoder: E) -> Result where @@ -100,22 +106,60 @@ impl<'de, M> Decode<'de, M> for Box { } } +impl<'de, M, T> Decode<'de, M> for Box<[T]> +where + T: Decode<'de, M>, +{ + #[inline] + fn decode(cx: &C, decoder: D) -> Result + where + C: ?Sized + Context, + D: Decoder<'de, C>, + { + let vec: Vec = cx.decode(decoder)?; + Ok(Box::from(vec)) + } +} + +impl<'de, M, T> Decode<'de, M> for Box +where + T: Decode<'de, M>, +{ + #[inline] + fn decode(cx: &C, decoder: D) -> Result + where + C: ?Sized + Context, + D: Decoder<'de, C>, + { + let vec: T = cx.decode(decoder)?; + Ok(Box::new(vec)) + } +} + macro_rules! cow { - ($ty:ty, $source:ty, $decode:ident, $cx:pat, |$owned:ident| $owned_expr:expr, |$borrowed:ident| $borrowed_expr:expr, |$reference:ident| $reference_expr:expr) => { - impl Encode for Cow<'_, $ty> { + ( + $encode:ident :: $encode_fn:ident, + $decode:ident :: $decode_fn:ident, + $ty:ty, $source:ty, + $decode_method:ident, $cx:pat, + |$owned:ident| $owned_expr:expr, + |$borrowed:ident| $borrowed_expr:expr, + |$reference:ident| $reference_expr:expr $(,)? + ) => { + impl $encode for Cow<'_, $ty> { #[inline] - fn encode(&self, cx: &C, encoder: E) -> Result + fn $encode_fn(&self, cx: &C, encoder: E) -> Result where C: ?Sized + Context, E: Encoder, { - self.as_ref().encode(cx, encoder) + self.as_ref().$encode_fn(cx, encoder) } } - impl<'de, M> Decode<'de, M> for Cow<'de, $ty> { + impl<'de, M> $decode<'de, M> for Cow<'de, $ty> { #[inline] - fn decode(cx: &C, decoder: D) -> Result + fn $decode_fn(cx: &C, decoder: D) -> Result where C: ?Sized + Context, D: Decoder<'de, C>, @@ -161,13 +205,15 @@ macro_rules! cow { } } - decoder.$decode(cx, Visitor) + decoder.$decode_method(cx, Visitor) } } }; } cow! { + Encode::encode, + Decode::decode, str, str, decode_string, _, |owned| Cow::Owned(owned), |borrowed| Cow::Borrowed(borrowed), @@ -175,12 +221,23 @@ cow! { } cow! { + Encode::encode, + Decode::decode, CStr, [u8], decode_bytes, cx, |owned| Cow::Owned(CString::from_vec_with_nul(owned).map_err(|error| cx.custom(error))?), |borrowed| Cow::Borrowed(CStr::from_bytes_with_nul(borrowed).map_err(|error| cx.custom(error))?), |reference| Cow::Owned(CStr::from_bytes_with_nul(reference).map_err(|error| cx.custom(error))?.to_owned()) } +cow! { + EncodeBytes::encode_bytes, + DecodeBytes::decode_bytes, + [u8], [u8], decode_bytes, _, + |owned| Cow::Owned(owned), + |borrowed| Cow::Borrowed(borrowed), + |reference| Cow::Owned(reference.to_owned()) +} + macro_rules! sequence { ( $cx:ident, @@ -663,3 +720,72 @@ impl<'de, M> Decode<'de, M> for PathBuf { Ok(PathBuf::from(string)) } } + +impl EncodeBytes for Vec { + #[inline] + fn encode_bytes(&self, cx: &C, encoder: E) -> Result + where + C: ?Sized + Context, + E: Encoder, + { + encoder.encode_bytes(cx, self.as_slice()) + } +} + +impl<'de, M> DecodeBytes<'de, M> for Vec { + #[inline] + fn decode_bytes(cx: &C, decoder: D) -> Result + where + C: ?Sized + Context, + D: Decoder<'de, C>, + { + struct Visitor; + + impl<'de, C> ValueVisitor<'de, C, [u8]> for Visitor + where + C: ?Sized + Context, + { + type Ok = Vec; + + #[inline] + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "bytes") + } + + #[inline] + fn visit_borrowed(self, _: &C, bytes: &'de [u8]) -> Result { + Ok(bytes.to_vec()) + } + + #[inline] + fn visit_ref(self, _: &C, bytes: &[u8]) -> Result { + Ok(bytes.to_vec()) + } + } + + decoder.decode_bytes(cx, Visitor) + } +} + +impl EncodeBytes for VecDeque { + #[inline] + fn encode_bytes(&self, cx: &C, encoder: E) -> Result + where + C: ?Sized + Context, + E: Encoder, + { + let (first, second) = self.as_slices(); + encoder.encode_bytes_vectored(cx, self.len(), &[first, second]) + } +} + +impl<'de, M> DecodeBytes<'de, M> for VecDeque { + #[inline] + fn decode_bytes(cx: &C, decoder: D) -> Result + where + C: ?Sized + Context, + D: Decoder<'de, C>, + { + Ok(VecDeque::from(>::decode_bytes(cx, decoder)?)) + } +} diff --git a/crates/musli/src/impls/mod.rs b/crates/musli/src/impls/mod.rs index 52649c05a..dbf4382d5 100644 --- a/crates/musli/src/impls/mod.rs +++ b/crates/musli/src/impls/mod.rs @@ -12,8 +12,8 @@ use core::num::{ }; use core::{fmt, marker}; -use crate::de::{Decode, Decoder, ValueVisitor, VariantDecoder}; -use crate::en::{Encode, Encoder, VariantEncoder}; +use crate::de::{Decode, DecodeBytes, Decoder, ValueVisitor, VariantDecoder}; +use crate::en::{Encode, EncodeBytes, Encoder, SequenceEncoder, VariantEncoder}; use crate::Context; impl Encode for () { @@ -147,14 +147,23 @@ where } } -impl Encode for [u8; N] { +impl Encode for [T; N] +where + T: Encode, +{ #[inline] fn encode(&self, cx: &C, encoder: E) -> Result where - C: ?Sized + Context, + C: ?Sized + Context, E: Encoder, { - encoder.encode_array(cx, *self) + let mut seq = encoder.encode_sequence(cx, N)?; + + for value in self.iter() { + value.encode(cx, seq.encode_next(cx)?)?; + } + + seq.end(cx) } } @@ -295,13 +304,28 @@ impl<'de, M> Decode<'de, M> for &'de str { } } -impl Encode for [u8] { +impl Encode for [T] +where + T: Encode, +{ fn encode(&self, cx: &C, encoder: E) -> Result where - C: ?Sized + Context, + C: ?Sized + Context, E: Encoder, { - encoder.encode_bytes(cx, self) + let mut seq = encoder.encode_sequence(cx, self.len())?; + + let mut index = 0; + + for value in self { + cx.enter_sequence_index(index); + let encoder = seq.encode_next(cx)?; + T::encode(value, cx, encoder)?; + cx.leave_sequence_index(); + index = index.wrapping_add(index); + } + + seq.end(cx) } } @@ -472,3 +496,47 @@ impl<'de, M> Decode<'de, M> for &'de CStr { CStr::from_bytes_with_nul(bytes).map_err(|error| cx.custom(error)) } } + +impl EncodeBytes for [u8] { + #[inline] + fn encode_bytes(&self, cx: &C, encoder: E) -> Result + where + C: ?Sized + Context, + E: Encoder, + { + encoder.encode_bytes(cx, self) + } +} + +impl EncodeBytes for [u8; N] { + #[inline] + fn encode_bytes(&self, cx: &C, encoder: E) -> Result + where + C: ?Sized + Context, + E: Encoder, + { + encoder.encode_array(cx, self) + } +} + +impl<'de, M> DecodeBytes<'de, M> for &'de [u8] { + #[inline] + fn decode_bytes(cx: &C, decoder: D) -> Result + where + C: ?Sized + Context, + D: Decoder<'de, C>, + { + Decode::decode(cx, decoder) + } +} + +impl<'de, M, const N: usize> DecodeBytes<'de, M> for [u8; N] { + #[inline] + fn decode_bytes(cx: &C, decoder: D) -> Result + where + C: ?Sized + Context, + D: Decoder<'de, C>, + { + decoder.decode_array(cx) + } +} diff --git a/crates/musli/src/impls/net.rs b/crates/musli/src/impls/net.rs index 8cbe0d6f8..2639404cc 100644 --- a/crates/musli/src/impls/net.rs +++ b/crates/musli/src/impls/net.rs @@ -11,7 +11,7 @@ impl Encode for Ipv4Addr { C: ?Sized + Context, E: Encoder, { - encoder.encode_array(cx, self.octets()) + encoder.encode_array(cx, &self.octets()) } } @@ -33,7 +33,7 @@ impl Encode for Ipv6Addr { C: ?Sized + Context, E: Encoder, { - encoder.encode_array(cx, self.octets()) + encoder.encode_array(cx, &self.octets()) } } diff --git a/crates/tests/src/models.rs b/crates/tests/src/models.rs index 6b67ef2db..a6919dbc0 100644 --- a/crates/tests/src/models.rs +++ b/crates/tests/src/models.rs @@ -45,6 +45,7 @@ miri! { #[cfg_attr(any(feature = "musli-zerocopy", feature = "zerocopy"), repr(C))] pub struct PrimitivesPacked { unsigned8: u8, + #[cfg_attr(feature = "musli", musli(bytes))] _pad0: [u8; 1], unsigned16: u16, unsigned32: u32, @@ -52,6 +53,7 @@ pub struct PrimitivesPacked { #[cfg(not(feature = "model-no-128"))] unsigned128: u128, signed8: i8, + #[cfg_attr(feature = "musli", musli(bytes))] _pad1: [u8; 1], signed16: i16, signed32: i32, @@ -63,6 +65,7 @@ pub struct PrimitivesPacked { #[cfg(not(feature = "model-no-usize"))] signedsize: isize, float32: f32, + #[cfg_attr(feature = "musli", musli(bytes))] _pad3: [u8; 4], float64: f64, } @@ -148,6 +151,7 @@ impl PartialEq for &Primitives { #[cfg_attr(feature = "musli", musli(mode = Packed, packed))] pub struct Allocated { string: String, + #[cfg_attr(feature = "musli", musli(bytes))] bytes: Vec, #[cfg(not(feature = "model-no-cstring"))] c_string: CString, diff --git a/crates/tests/tests/bytes_compat.rs b/crates/tests/tests/bytes.rs similarity index 55% rename from crates/tests/tests/bytes_compat.rs rename to crates/tests/tests/bytes.rs index 6572e453f..aeb7a6883 100644 --- a/crates/tests/tests/bytes_compat.rs +++ b/crates/tests/tests/bytes.rs @@ -1,6 +1,18 @@ +use std::collections::VecDeque; + use musli::compat::Bytes; use musli::{Decode, Encode}; +#[derive(Decode, Encode)] +struct Container<'de> { + #[musli(bytes)] + vec: Vec, + #[musli(bytes)] + vec_deque: VecDeque, + #[musli(bytes)] + bytes: &'de [u8], +} + #[derive(Debug, PartialEq, Encode, Decode)] pub struct BytesCompat { pub empty_bytes: Bytes<[u8; 0]>, diff --git a/crates/tests/tests/complex_field.rs b/crates/tests/tests/complex_field.rs index fa7b3f855..fbc9bba6a 100644 --- a/crates/tests/tests/complex_field.rs +++ b/crates/tests/tests/complex_field.rs @@ -1,6 +1,7 @@ use core::fmt; use bstr::BStr; +use musli::compat::Bytes; use musli::{Decode, Encode}; #[derive(Debug, Encode, Decode, PartialEq, Eq)] @@ -16,7 +17,7 @@ impl fmt::Display for FieldVariantTag<'_> { #[derive(Debug, PartialEq, Eq, Encode, Decode)] #[musli(transparent)] -struct BytesTag<'a>(&'a [u8]); +struct BytesTag<'a>(#[musli(bytes)] &'a [u8]); impl fmt::Display for BytesTag<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -60,11 +61,11 @@ fn custom_struct_tag() { } #[derive(Debug, PartialEq, Encode, Decode)] -#[musli(name_type = [u8; 4], name_format_with = BStr::new)] +#[musli(name_type = Bytes<[u8; 4]>, name_format_with = BStr::new)] pub struct StructCustomTag { - #[musli(rename = [1, 2, 3, 4])] + #[musli(rename = Bytes([1, 2, 3, 4]))] field1: u32, - #[musli(rename = [2, 3, 4, 5])] + #[musli(rename = Bytes([2, 3, 4, 5]))] field2: u32, } diff --git a/crates/tests/tests/large_enum.rs b/crates/tests/tests/large_enum.rs index cb6143db4..1e88e97b6 100644 --- a/crates/tests/tests/large_enum.rs +++ b/crates/tests/tests/large_enum.rs @@ -4,28 +4,34 @@ use musli::{Decode, Encode}; #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct A { + #[musli(bytes)] pub id: [u8; 16], pub ip: IpAddr, } #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct B { + #[musli(bytes)] pub id: [u8; 16], + #[musli(bytes)] pub user_id: [u8; 16], } #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct C { + #[musli(bytes)] pub id: [u8; 16], } #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct D { + #[musli(bytes)] pub id: [u8; 16], } #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct E { + #[musli(bytes)] pub id: [u8; 16], } diff --git a/crates/tests/tests/pack_compat.rs b/crates/tests/tests/pack_compat.rs index 08f7a209b..17828f467 100644 --- a/crates/tests/tests/pack_compat.rs +++ b/crates/tests/tests/pack_compat.rs @@ -13,12 +13,14 @@ pub struct Inner; #[derive(Debug, PartialEq, Encode, Decode)] #[musli(packed)] struct SmallPack { + #[musli(bytes)] small: [u8; MAX_INLINE_LEN], } #[derive(Debug, PartialEq, Encode, Decode)] #[musli(packed)] struct LargePack { + #[musli(bytes)] large: [u8; 128], }