diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8487ba65d..81a861a55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,11 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - run: cd test_suite && cargo test --features unstable + - uses: actions/upload-artifact@v4 + if: always() + with: + name: Cargo.lock + path: Cargo.lock windows: name: Test suite (windows) @@ -84,6 +89,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{matrix.rust}} + - run: sed -i '/"test_suite"/d' Cargo.toml - run: cd serde && cargo build --features rc - run: cd serde && cargo build --no-default-features - run: cd serde && cargo build @@ -95,6 +101,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@1.56.0 + - run: sed -i '/"test_suite"/d' Cargo.toml - run: cd serde && cargo check --no-default-features - run: cd serde && cargo check - run: cd serde_derive && cargo check @@ -106,6 +113,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@1.36.0 + - run: sed -i '/"test_suite"/d' Cargo.toml - run: cd serde && cargo build --no-default-features --features alloc minimal: diff --git a/crates-io.md b/crates-io.md index 187100358..b49a5483b 100644 --- a/crates-io.md +++ b/crates-io.md @@ -46,8 +46,8 @@ fn main() { Serde is one of the most widely used Rust libraries so any place that Rustaceans congregate will be able to help you out. For chat, consider trying the [#rust-questions] or [#rust-beginners] channels of the unofficial community -Discord (invite: , the [#rust-usage] or -[#beginners] channels of the official Rust Project Discord (invite: +Discord (invite: ), the [#rust-usage] +or [#beginners] channels of the official Rust Project Discord (invite: ), or the [#general][zulip] stream in Zulip. For asynchronous, consider the [\[rust\] tag on StackOverflow][stackoverflow], the [/r/rust] subreddit which has a pinned weekly easy questions post, or the Rust diff --git a/serde/Cargo.toml b/serde/Cargo.toml index 3d9832268..b6ad57682 100644 --- a/serde/Cargo.toml +++ b/serde/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde" -version = "1.0.203" +version = "1.0.210" authors = ["Erick Tryzelaar ", "David Tolnay "] build = "build.rs" categories = ["encoding", "no-std", "no-std::no-alloc"] @@ -37,7 +37,7 @@ rustdoc-args = ["--generate-link-to-definition"] # is compatible with exactly one serde release because the generated code # involves nonpublic APIs which are not bound by semver. [target.'cfg(any())'.dependencies] -serde_derive = { version = "=1.0.203", path = "../serde_derive" } +serde_derive = { version = "=1.0.210", path = "../serde_derive" } ### FEATURES ################################################################# diff --git a/serde/build.rs b/serde/build.rs index 46ca435d5..8a4f7257c 100644 --- a/serde/build.rs +++ b/serde/build.rs @@ -15,8 +15,11 @@ fn main() { if minor >= 77 { println!("cargo:rustc-check-cfg=cfg(no_core_cstr)"); + println!("cargo:rustc-check-cfg=cfg(no_core_error)"); + println!("cargo:rustc-check-cfg=cfg(no_core_net)"); println!("cargo:rustc-check-cfg=cfg(no_core_num_saturating)"); println!("cargo:rustc-check-cfg=cfg(no_core_try_from)"); + println!("cargo:rustc-check-cfg=cfg(no_diagnostic_namespace)"); println!("cargo:rustc-check-cfg=cfg(no_float_copysign)"); println!("cargo:rustc-check-cfg=cfg(no_num_nonzero_signed)"); println!("cargo:rustc-check-cfg=cfg(no_relaxed_trait_bounds)"); @@ -84,6 +87,24 @@ fn main() { if minor < 74 { println!("cargo:rustc-cfg=no_core_num_saturating"); } + + // Support for core::net stabilized in Rust 1.77. + // https://blog.rust-lang.org/2024/03/21/Rust-1.77.0.html + if minor < 77 { + println!("cargo:rustc-cfg=no_core_net"); + } + + // Support for the `#[diagnostic]` tool attribute namespace + // https://blog.rust-lang.org/2024/05/02/Rust-1.78.0.html#diagnostic-attributes + if minor < 78 { + println!("cargo:rustc-cfg=no_diagnostic_namespace"); + } + + // The Error trait became available in core in 1.81. + // https://blog.rust-lang.org/2024/09/05/Rust-1.81.0.html#coreerrorerror + if minor < 81 { + println!("cargo:rustc-cfg=no_core_error"); + } } fn rustc_minor_version() -> Option { diff --git a/serde/src/de/impls.rs b/serde/src/de/impls.rs index 02591d982..2d8c99030 100644 --- a/serde/src/de/impls.rs +++ b/serde/src/de/impls.rs @@ -1583,12 +1583,9 @@ map_impl! { //////////////////////////////////////////////////////////////////////////////// +#[cfg(any(feature = "std", not(no_core_net)))] macro_rules! parse_ip_impl { - ( - $(#[$attr:meta])* - $ty:ty, $expecting:expr, $size:tt - ) => { - $(#[$attr])* + ($ty:ty, $expecting:expr, $size:tt) => { impl<'de> Deserialize<'de> for $ty { fn deserialize(deserializer: D) -> Result where @@ -1604,7 +1601,7 @@ macro_rules! parse_ip_impl { }; } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", not(no_core_net)))] macro_rules! variant_identifier { ( $name_kind:ident ($($variant:ident; $bytes:expr; $index:expr),*) @@ -1679,7 +1676,7 @@ macro_rules! variant_identifier { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", not(no_core_net)))] macro_rules! deserialize_enum { ( $name:ident $name_kind:ident ($($variant:ident; $bytes:expr; $index:expr),*) @@ -1716,8 +1713,7 @@ macro_rules! deserialize_enum { } } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(any(feature = "std", not(no_core_net)))] impl<'de> Deserialize<'de> for net::IpAddr { fn deserialize(deserializer: D) -> Result where @@ -1736,25 +1732,18 @@ impl<'de> Deserialize<'de> for net::IpAddr { } } -parse_ip_impl! { - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - net::Ipv4Addr, "IPv4 address", 4 -} +#[cfg(any(feature = "std", not(no_core_net)))] +parse_ip_impl!(net::Ipv4Addr, "IPv4 address", 4); -parse_ip_impl! { - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - net::Ipv6Addr, "IPv6 address", 16 -} +#[cfg(any(feature = "std", not(no_core_net)))] +parse_ip_impl!(net::Ipv6Addr, "IPv6 address", 16); +#[cfg(any(feature = "std", not(no_core_net)))] macro_rules! parse_socket_impl { ( - $(#[$attr:meta])* $ty:ty, $expecting:tt, $new:expr, ) => { - $(#[$attr])* impl<'de> Deserialize<'de> for $ty { fn deserialize(deserializer: D) -> Result where @@ -1770,8 +1759,7 @@ macro_rules! parse_socket_impl { }; } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(any(feature = "std", not(no_core_net)))] impl<'de> Deserialize<'de> for net::SocketAddr { fn deserialize(deserializer: D) -> Result where @@ -1790,16 +1778,14 @@ impl<'de> Deserialize<'de> for net::SocketAddr { } } +#[cfg(any(feature = "std", not(no_core_net)))] parse_socket_impl! { - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] net::SocketAddrV4, "IPv4 socket address", |(ip, port)| net::SocketAddrV4::new(ip, port), } +#[cfg(any(feature = "std", not(no_core_net)))] parse_socket_impl! { - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] net::SocketAddrV6, "IPv6 socket address", |(ip, port)| net::SocketAddrV6::new(ip, port, 0, 0), } @@ -3160,13 +3146,13 @@ atomic_impl! { AtomicU64 "64" } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", not(no_core_net)))] struct FromStrVisitor { expecting: &'static str, ty: PhantomData, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", not(no_core_net)))] impl FromStrVisitor { fn new(expecting: &'static str) -> Self { FromStrVisitor { @@ -3176,7 +3162,7 @@ impl FromStrVisitor { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", not(no_core_net)))] impl<'de, T> Visitor<'de> for FromStrVisitor where T: str::FromStr, diff --git a/serde/src/de/mod.rs b/serde/src/de/mod.rs index 602054a18..21cfd0414 100644 --- a/serde/src/de/mod.rs +++ b/serde/src/de/mod.rs @@ -118,17 +118,16 @@ use crate::lib::*; pub mod value; -mod format; mod ignored_any; mod impls; pub(crate) mod size_hint; pub use self::ignored_any::IgnoredAny; -#[cfg(not(any(feature = "std", feature = "unstable")))] +#[cfg(all(not(feature = "std"), no_core_error))] #[doc(no_inline)] pub use crate::std_error::Error as StdError; -#[cfg(all(feature = "unstable", not(feature = "std")))] +#[cfg(not(any(feature = "std", no_core_error)))] #[doc(no_inline)] pub use core::error::Error as StdError; #[cfg(feature = "std")] @@ -532,6 +531,13 @@ impl<'a> Display for Expected + 'a { /// deserializer lifetimes] for a more detailed explanation of these lifetimes. /// /// [Understanding deserializer lifetimes]: https://serde.rs/lifetimes.html +#[cfg_attr( + not(no_diagnostic_namespace), + diagnostic::on_unimplemented( + note = "for local types consider adding `#[derive(serde::Deserialize)]` to your `{Self}` type", + note = "for types from other crates check whether the crate offers a `serde` feature flag", + ) +)] pub trait Deserialize<'de>: Sized { /// Deserialize this value from the given Serde deserializer. /// @@ -1367,7 +1373,7 @@ pub trait Visitor<'de>: Sized { E: Error, { let mut buf = [0u8; 58]; - let mut writer = format::Buf::new(&mut buf); + let mut writer = crate::format::Buf::new(&mut buf); fmt::Write::write_fmt(&mut writer, format_args!("integer `{}` as i128", v)).unwrap(); Err(Error::invalid_type( Unexpected::Other(writer.as_str()), @@ -1429,7 +1435,7 @@ pub trait Visitor<'de>: Sized { E: Error, { let mut buf = [0u8; 57]; - let mut writer = format::Buf::new(&mut buf); + let mut writer = crate::format::Buf::new(&mut buf); fmt::Write::write_fmt(&mut writer, format_args!("integer `{}` as u128", v)).unwrap(); Err(Error::invalid_type( Unexpected::Other(writer.as_str()), diff --git a/serde/src/de/format.rs b/serde/src/format.rs similarity index 100% rename from serde/src/de/format.rs rename to serde/src/format.rs diff --git a/serde/src/lib.rs b/serde/src/lib.rs index 1d9e6bc12..15710d8d9 100644 --- a/serde/src/lib.rs +++ b/serde/src/lib.rs @@ -95,7 +95,7 @@ //////////////////////////////////////////////////////////////////////////////// // Serde types in rustdoc of other crates get linked to here. -#![doc(html_root_url = "https://docs.rs/serde/1.0.203")] +#![doc(html_root_url = "https://docs.rs/serde/1.0.210")] // Support using Serde without the standard library! #![cfg_attr(not(feature = "std"), no_std)] // Show which crate feature enables conditionally compiled APIs in documentation. @@ -105,7 +105,7 @@ // discussion of these features please refer to this issue: // // https://github.com/serde-rs/serde/issues/812 -#![cfg_attr(feature = "unstable", feature(error_in_core, never_type))] +#![cfg_attr(feature = "unstable", feature(never_type))] #![allow(unknown_lints, bare_trait_objects, deprecated)] // Ignored clippy and clippy_pedantic lints #![allow( @@ -238,8 +238,13 @@ mod lib { #[cfg(feature = "std")] pub use std::ffi::CString; + #[cfg(all(not(no_core_net), not(feature = "std")))] + pub use self::core::net; #[cfg(feature = "std")] - pub use std::{error, net}; + pub use std::net; + + #[cfg(feature = "std")] + pub use std::error; #[cfg(feature = "std")] pub use std::collections::{HashMap, HashSet}; @@ -305,6 +310,8 @@ mod integer128; pub mod de; pub mod ser; +mod format; + #[doc(inline)] pub use crate::de::{Deserialize, Deserializer}; #[doc(inline)] @@ -318,7 +325,7 @@ pub mod __private; #[path = "de/seed.rs"] mod seed; -#[cfg(not(any(feature = "std", feature = "unstable")))] +#[cfg(all(not(feature = "std"), no_core_error))] mod std_error; // Re-export #[derive(Serialize, Deserialize)]. diff --git a/serde/src/private/de.rs b/serde/src/private/de.rs index c402d2c66..f0eca71ff 100644 --- a/serde/src/private/de.rs +++ b/serde/src/private/de.rs @@ -904,7 +904,9 @@ mod content { /// Not public API. pub struct TagOrContentFieldVisitor { + /// Name of the tag field of the adjacently tagged enum pub tag: &'static str, + /// Name of the content field of the adjacently tagged enum pub content: &'static str, } @@ -979,7 +981,9 @@ mod content { /// Not public API. pub struct TagContentOtherFieldVisitor { + /// Name of the tag field of the adjacently tagged enum pub tag: &'static str, + /// Name of the content field of the adjacently tagged enum pub content: &'static str, } @@ -1894,10 +1898,19 @@ mod content { where V: Visitor<'de>, { + // Covered by tests/test_enum_untagged.rs + // with_optional_field::* match *self.content { Content::None => visitor.visit_none(), Content::Some(ref v) => visitor.visit_some(ContentRefDeserializer::new(v)), Content::Unit => visitor.visit_unit(), + // This case is to support data formats which do not encode an + // indication whether a value is optional. An example of such a + // format is JSON, and a counterexample is RON. When requesting + // `deserialize_any` in JSON, the data format never performs + // `Visitor::visit_some` but we still must be able to + // deserialize the resulting Content into data structures with + // optional fields. _ => visitor.visit_some(self), } } @@ -1927,10 +1940,21 @@ mod content { where V: Visitor<'de>, { + // Covered by tests/test_enum_untagged.rs + // newtype_struct match *self.content { Content::Newtype(ref v) => { visitor.visit_newtype_struct(ContentRefDeserializer::new(v)) } + // This case is to support data formats that encode newtype + // structs and their underlying data the same, with no + // indication whether a newtype wrapper was present. For example + // JSON does this, while RON does not. In RON a newtype's name + // is included in the serialized representation and it knows to + // call `Visitor::visit_newtype_struct` from `deserialize_any`. + // JSON's `deserialize_any` never calls `visit_newtype_struct` + // but in this code we still must be able to deserialize the + // resulting Content into newtypes. _ => visitor.visit_newtype_struct(self), } } @@ -2135,6 +2159,10 @@ mod content { fn unit_variant(self) -> Result<(), E> { match self.value { Some(value) => de::Deserialize::deserialize(ContentRefDeserializer::new(value)), + // Covered by tests/test_annotations.rs + // test_partially_untagged_adjacently_tagged_enum + // Covered by tests/test_enum_untagged.rs + // newtype_enum::unit None => Ok(()), } } @@ -2144,6 +2172,11 @@ mod content { T: de::DeserializeSeed<'de>, { match self.value { + // Covered by tests/test_annotations.rs + // test_partially_untagged_enum_desugared + // test_partially_untagged_enum_generic + // Covered by tests/test_enum_untagged.rs + // newtype_enum::newtype Some(value) => seed.deserialize(ContentRefDeserializer::new(value)), None => Err(de::Error::invalid_type( de::Unexpected::UnitVariant, @@ -2157,9 +2190,13 @@ mod content { V: de::Visitor<'de>, { match self.value { - Some(Content::Seq(v)) => { - de::Deserializer::deserialize_any(SeqRefDeserializer::new(v), visitor) - } + // Covered by tests/test_annotations.rs + // test_partially_untagged_enum + // test_partially_untagged_enum_desugared + // Covered by tests/test_enum_untagged.rs + // newtype_enum::tuple0 + // newtype_enum::tuple2 + Some(Content::Seq(v)) => visit_content_seq_ref(v, visitor), Some(other) => Err(de::Error::invalid_type( other.unexpected(), &"tuple variant", @@ -2180,12 +2217,13 @@ mod content { V: de::Visitor<'de>, { match self.value { - Some(Content::Map(v)) => { - de::Deserializer::deserialize_any(MapRefDeserializer::new(v), visitor) - } - Some(Content::Seq(v)) => { - de::Deserializer::deserialize_any(SeqRefDeserializer::new(v), visitor) - } + // Covered by tests/test_enum_untagged.rs + // newtype_enum::struct_from_map + Some(Content::Map(v)) => visit_content_map_ref(v, visitor), + // Covered by tests/test_enum_untagged.rs + // newtype_enum::struct_from_seq + // newtype_enum::empty_struct_from_seq + Some(Content::Seq(v)) => visit_content_seq_ref(v, visitor), Some(other) => Err(de::Error::invalid_type( other.unexpected(), &"struct variant", @@ -2198,158 +2236,6 @@ mod content { } } - struct SeqRefDeserializer<'a, 'de: 'a, E> - where - E: de::Error, - { - iter: <&'a [Content<'de>] as IntoIterator>::IntoIter, - err: PhantomData, - } - - impl<'a, 'de, E> SeqRefDeserializer<'a, 'de, E> - where - E: de::Error, - { - fn new(slice: &'a [Content<'de>]) -> Self { - SeqRefDeserializer { - iter: slice.iter(), - err: PhantomData, - } - } - } - - impl<'de, 'a, E> de::Deserializer<'de> for SeqRefDeserializer<'a, 'de, E> - where - E: de::Error, - { - type Error = E; - - #[inline] - fn deserialize_any(mut self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - let len = self.iter.len(); - if len == 0 { - visitor.visit_unit() - } else { - let ret = tri!(visitor.visit_seq(&mut self)); - let remaining = self.iter.len(); - if remaining == 0 { - Ok(ret) - } else { - Err(de::Error::invalid_length(len, &"fewer elements in array")) - } - } - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum identifier ignored_any - } - } - - impl<'de, 'a, E> de::SeqAccess<'de> for SeqRefDeserializer<'a, 'de, E> - where - E: de::Error, - { - type Error = E; - - fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> - where - T: de::DeserializeSeed<'de>, - { - match self.iter.next() { - Some(value) => seed - .deserialize(ContentRefDeserializer::new(value)) - .map(Some), - None => Ok(None), - } - } - - fn size_hint(&self) -> Option { - size_hint::from_bounds(&self.iter) - } - } - - struct MapRefDeserializer<'a, 'de: 'a, E> - where - E: de::Error, - { - iter: <&'a [(Content<'de>, Content<'de>)] as IntoIterator>::IntoIter, - value: Option<&'a Content<'de>>, - err: PhantomData, - } - - impl<'a, 'de, E> MapRefDeserializer<'a, 'de, E> - where - E: de::Error, - { - fn new(map: &'a [(Content<'de>, Content<'de>)]) -> Self { - MapRefDeserializer { - iter: map.iter(), - value: None, - err: PhantomData, - } - } - } - - impl<'de, 'a, E> de::MapAccess<'de> for MapRefDeserializer<'a, 'de, E> - where - E: de::Error, - { - type Error = E; - - fn next_key_seed(&mut self, seed: T) -> Result, Self::Error> - where - T: de::DeserializeSeed<'de>, - { - match self.iter.next() { - Some((key, value)) => { - self.value = Some(value); - seed.deserialize(ContentRefDeserializer::new(key)).map(Some) - } - None => Ok(None), - } - } - - fn next_value_seed(&mut self, seed: T) -> Result - where - T: de::DeserializeSeed<'de>, - { - match self.value.take() { - Some(value) => seed.deserialize(ContentRefDeserializer::new(value)), - None => Err(de::Error::custom("value is missing")), - } - } - - fn size_hint(&self) -> Option { - size_hint::from_bounds(&self.iter) - } - } - - impl<'de, 'a, E> de::Deserializer<'de> for MapRefDeserializer<'a, 'de, E> - where - E: de::Error, - { - type Error = E; - - #[inline] - fn deserialize_any(self, visitor: V) -> Result - where - V: de::Visitor<'de>, - { - visitor.visit_map(self) - } - - forward_to_deserialize_any! { - bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string - bytes byte_buf option unit unit_struct newtype_struct seq tuple - tuple_struct map struct enum identifier ignored_any - } - } - impl<'de, E> de::IntoDeserializer<'de, E> for ContentDeserializer<'de, E> where E: de::Error, @@ -2710,6 +2596,17 @@ where visitor.visit_unit() } + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } + fn deserialize_ignored_any(self, visitor: V) -> Result where V: Visitor<'de>, @@ -2734,7 +2631,6 @@ where deserialize_string() deserialize_bytes() deserialize_byte_buf() - deserialize_unit_struct(&'static str) deserialize_seq() deserialize_tuple(usize) deserialize_tuple_struct(&'static str, usize) diff --git a/serde/src/private/ser.rs b/serde/src/private/ser.rs index 40cc6cbdb..ebfeba97e 100644 --- a/serde/src/private/ser.rs +++ b/serde/src/private/ser.rs @@ -51,8 +51,6 @@ enum Unsupported { String, ByteArray, Optional, - #[cfg(any(feature = "std", feature = "alloc"))] - UnitStruct, Sequence, Tuple, TupleStruct, @@ -69,8 +67,6 @@ impl Display for Unsupported { Unsupported::String => formatter.write_str("a string"), Unsupported::ByteArray => formatter.write_str("a byte array"), Unsupported::Optional => formatter.write_str("an optional"), - #[cfg(any(feature = "std", feature = "alloc"))] - Unsupported::UnitStruct => formatter.write_str("unit struct"), Unsupported::Sequence => formatter.write_str("a sequence"), Unsupported::Tuple => formatter.write_str("a tuple"), Unsupported::TupleStruct => formatter.write_str("a tuple struct"), @@ -1092,7 +1088,7 @@ where } fn serialize_unit_struct(self, _: &'static str) -> Result { - Err(Self::bad_type(Unsupported::UnitStruct)) + Ok(()) } fn serialize_unit_variant( @@ -1125,8 +1121,7 @@ where where T: ?Sized + Serialize, { - tri!(self.0.serialize_key(variant)); - self.0.serialize_value(value) + self.0.serialize_entry(variant, value) } fn serialize_seq(self, _: Option) -> Result { diff --git a/serde/src/ser/impls.rs b/serde/src/ser/impls.rs index 557b6aa12..fb574eae1 100644 --- a/serde/src/ser/impls.rs +++ b/serde/src/ser/impls.rs @@ -773,28 +773,17 @@ impl Serialize for SystemTime { /// statically known to never have more than a constant `MAX_LEN` bytes. /// /// Panics if the `Display` impl tries to write more than `MAX_LEN` bytes. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", not(no_core_net)))] macro_rules! serialize_display_bounded_length { ($value:expr, $max:expr, $serializer:expr) => {{ let mut buffer = [0u8; $max]; - let remaining_len = { - let mut remaining = &mut buffer[..]; - write!(remaining, "{}", $value).unwrap(); - remaining.len() - }; - let written_len = buffer.len() - remaining_len; - let written = &buffer[..written_len]; - - // write! only provides fmt::Formatter to Display implementations, which - // has methods write_str and write_char but no method to write arbitrary - // bytes. Therefore `written` must be valid UTF-8. - let written_str = str::from_utf8(written).expect("must be valid UTF-8"); - $serializer.serialize_str(written_str) + let mut writer = crate::format::Buf::new(&mut buffer); + write!(&mut writer, "{}", $value).unwrap(); + $serializer.serialize_str(writer.as_str()) }}; } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(any(feature = "std", not(no_core_net)))] impl Serialize for net::IpAddr { fn serialize(&self, serializer: S) -> Result where @@ -818,7 +807,7 @@ impl Serialize for net::IpAddr { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", not(no_core_net)))] const DEC_DIGITS_LUT: &[u8] = b"\ 0001020304050607080910111213141516171819\ 2021222324252627282930313233343536373839\ @@ -826,7 +815,7 @@ const DEC_DIGITS_LUT: &[u8] = b"\ 6061626364656667686970717273747576777879\ 8081828384858687888990919293949596979899"; -#[cfg(feature = "std")] +#[cfg(any(feature = "std", not(no_core_net)))] #[inline] fn format_u8(mut n: u8, out: &mut [u8]) -> usize { if n >= 100 { @@ -847,7 +836,7 @@ fn format_u8(mut n: u8, out: &mut [u8]) -> usize { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", not(no_core_net)))] #[test] fn test_format_u8() { let mut i = 0u8; @@ -864,8 +853,7 @@ fn test_format_u8() { } } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(any(feature = "std", not(no_core_net)))] impl Serialize for net::Ipv4Addr { fn serialize(&self, serializer: S) -> Result where @@ -889,8 +877,7 @@ impl Serialize for net::Ipv4Addr { } } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(any(feature = "std", not(no_core_net)))] impl Serialize for net::Ipv6Addr { fn serialize(&self, serializer: S) -> Result where @@ -906,8 +893,7 @@ impl Serialize for net::Ipv6Addr { } } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(any(feature = "std", not(no_core_net)))] impl Serialize for net::SocketAddr { fn serialize(&self, serializer: S) -> Result where @@ -931,8 +917,7 @@ impl Serialize for net::SocketAddr { } } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(any(feature = "std", not(no_core_net)))] impl Serialize for net::SocketAddrV4 { fn serialize(&self, serializer: S) -> Result where @@ -948,8 +933,7 @@ impl Serialize for net::SocketAddrV4 { } } -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(any(feature = "std", not(no_core_net)))] impl Serialize for net::SocketAddrV6 { fn serialize(&self, serializer: S) -> Result where diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index 74b5e0769..fb0033ec0 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -115,10 +115,10 @@ mod impossible; pub use self::impossible::Impossible; -#[cfg(not(any(feature = "std", feature = "unstable")))] +#[cfg(all(not(feature = "std"), no_core_error))] #[doc(no_inline)] pub use crate::std_error::Error as StdError; -#[cfg(all(feature = "unstable", not(feature = "std")))] +#[cfg(not(any(feature = "std", no_core_error)))] #[doc(no_inline)] pub use core::error::Error as StdError; #[cfg(feature = "std")] @@ -215,6 +215,13 @@ declare_error_trait!(Error: Sized + Debug + Display); /// [`linked-hash-map`]: https://crates.io/crates/linked-hash-map /// [`serde_derive`]: https://crates.io/crates/serde_derive /// [derive section of the manual]: https://serde.rs/derive.html +#[cfg_attr( + not(no_diagnostic_namespace), + diagnostic::on_unimplemented( + note = "for local types consider adding `#[derive(serde::Serialize)]` to your `{Self}` type", + note = "for types from other crates check whether the crate offers a `serde` feature flag", + ) +)] pub trait Serialize { /// Serialize this value into the given Serde serializer. /// diff --git a/serde_derive/Cargo.toml b/serde_derive/Cargo.toml index f006f716a..d6e5c24e6 100644 --- a/serde_derive/Cargo.toml +++ b/serde_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde_derive" -version = "1.0.203" +version = "1.0.210" authors = ["Erick Tryzelaar ", "David Tolnay "] categories = ["no-std", "no-std::no-alloc"] description = "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]" diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index e3b737c61..996e97e86 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -461,7 +461,10 @@ fn deserialize_tuple( cattrs: &attr::Container, form: TupleForm, ) -> Fragment { - assert!(!cattrs.has_flatten()); + assert!( + !has_flatten(fields), + "tuples and tuple variants cannot have flatten fields" + ); let field_count = fields .iter() @@ -579,7 +582,10 @@ fn deserialize_tuple_in_place( fields: &[Field], cattrs: &attr::Container, ) -> Fragment { - assert!(!cattrs.has_flatten()); + assert!( + !has_flatten(fields), + "tuples and tuple variants cannot have flatten fields" + ); let field_count = fields .iter() @@ -958,13 +964,15 @@ fn deserialize_struct( ) }) .collect(); - let field_visitor = deserialize_field_identifier(&field_names_idents, cattrs); + + let has_flatten = has_flatten(fields); + let field_visitor = deserialize_field_identifier(&field_names_idents, cattrs, has_flatten); // untagged struct variants do not get a visit_seq method. The same applies to // structs that only have a map representation. let visit_seq = match form { StructForm::Untagged(..) => None, - _ if cattrs.has_flatten() => None, + _ if has_flatten => None, _ => { let mut_seq = if field_names_idents.is_empty() { quote!(_) @@ -987,10 +995,16 @@ fn deserialize_struct( }) } }; - let visit_map = Stmts(deserialize_map(&type_path, params, fields, cattrs)); + let visit_map = Stmts(deserialize_map( + &type_path, + params, + fields, + cattrs, + has_flatten, + )); let visitor_seed = match form { - StructForm::ExternallyTagged(..) if cattrs.has_flatten() => Some(quote! { + StructForm::ExternallyTagged(..) if has_flatten => Some(quote! { impl #de_impl_generics _serde::de::DeserializeSeed<#delife> for __Visitor #de_ty_generics #where_clause { type Value = #this_type #ty_generics; @@ -1005,7 +1019,7 @@ fn deserialize_struct( _ => None, }; - let fields_stmt = if cattrs.has_flatten() { + let fields_stmt = if has_flatten { None } else { let field_names = field_names_idents @@ -1025,7 +1039,7 @@ fn deserialize_struct( } }; let dispatch = match form { - StructForm::Struct if cattrs.has_flatten() => quote! { + StructForm::Struct if has_flatten => quote! { _serde::Deserializer::deserialize_map(__deserializer, #visitor_expr) }, StructForm::Struct => { @@ -1034,7 +1048,7 @@ fn deserialize_struct( _serde::Deserializer::deserialize_struct(__deserializer, #type_name, FIELDS, #visitor_expr) } } - StructForm::ExternallyTagged(_) if cattrs.has_flatten() => quote! { + StructForm::ExternallyTagged(_) if has_flatten => quote! { _serde::de::VariantAccess::newtype_variant_seed(__variant, #visitor_expr) }, StructForm::ExternallyTagged(_) => quote! { @@ -1091,7 +1105,7 @@ fn deserialize_struct_in_place( ) -> Option { // for now we do not support in_place deserialization for structs that // are represented as map. - if cattrs.has_flatten() { + if has_flatten(fields) { return None; } @@ -1116,7 +1130,7 @@ fn deserialize_struct_in_place( }) .collect(); - let field_visitor = deserialize_field_identifier(&field_names_idents, cattrs); + let field_visitor = deserialize_field_identifier(&field_names_idents, cattrs, false); let mut_seq = if field_names_idents.is_empty() { quote!(_) @@ -1210,10 +1224,7 @@ fn deserialize_homogeneous_enum( } } -fn prepare_enum_variant_enum( - variants: &[Variant], - cattrs: &attr::Container, -) -> (TokenStream, Stmts) { +fn prepare_enum_variant_enum(variants: &[Variant]) -> (TokenStream, Stmts) { let mut deserialized_variants = variants .iter() .enumerate() @@ -1247,7 +1258,7 @@ fn prepare_enum_variant_enum( let variant_visitor = Stmts(deserialize_generated_identifier( &variant_names_idents, - cattrs, + false, // variant identifiers do not depend on the presence of flatten fields true, None, fallthrough, @@ -1270,7 +1281,7 @@ fn deserialize_externally_tagged_enum( let expecting = format!("enum {}", params.type_name()); let expecting = cattrs.expecting().unwrap_or(&expecting); - let (variants_stmt, variant_visitor) = prepare_enum_variant_enum(variants, cattrs); + let (variants_stmt, variant_visitor) = prepare_enum_variant_enum(variants); // Match arms to extract a variant from a string let variant_arms = variants @@ -1355,7 +1366,7 @@ fn deserialize_internally_tagged_enum( cattrs: &attr::Container, tag: &str, ) -> Fragment { - let (variants_stmt, variant_visitor) = prepare_enum_variant_enum(variants, cattrs); + let (variants_stmt, variant_visitor) = prepare_enum_variant_enum(variants); // Match arms to extract a variant from a string let variant_arms = variants @@ -1409,7 +1420,7 @@ fn deserialize_adjacently_tagged_enum( split_with_de_lifetime(params); let delife = params.borrowed.de_lifetime(); - let (variants_stmt, variant_visitor) = prepare_enum_variant_enum(variants, cattrs); + let (variants_stmt, variant_visitor) = prepare_enum_variant_enum(variants); let variant_arms: &Vec<_> = &variants .iter() @@ -1985,7 +1996,7 @@ fn deserialize_untagged_newtype_variant( fn deserialize_generated_identifier( fields: &[(&str, Ident, &BTreeSet)], - cattrs: &attr::Container, + has_flatten: bool, is_variant: bool, ignore_variant: Option, fallthrough: Option, @@ -1999,11 +2010,11 @@ fn deserialize_generated_identifier( is_variant, fallthrough, None, - !is_variant && cattrs.has_flatten(), + !is_variant && has_flatten, None, )); - let lifetime = if !is_variant && cattrs.has_flatten() { + let lifetime = if !is_variant && has_flatten { Some(quote!(<'de>)) } else { None @@ -2043,8 +2054,9 @@ fn deserialize_generated_identifier( fn deserialize_field_identifier( fields: &[(&str, Ident, &BTreeSet)], cattrs: &attr::Container, + has_flatten: bool, ) -> Stmts { - let (ignore_variant, fallthrough) = if cattrs.has_flatten() { + let (ignore_variant, fallthrough) = if has_flatten { let ignore_variant = quote!(__other(_serde::__private::de::Content<'de>),); let fallthrough = quote!(_serde::__private::Ok(__Field::__other(__value))); (Some(ignore_variant), Some(fallthrough)) @@ -2058,7 +2070,7 @@ fn deserialize_field_identifier( Stmts(deserialize_generated_identifier( fields, - cattrs, + has_flatten, false, ignore_variant, fallthrough, @@ -2460,6 +2472,7 @@ fn deserialize_map( params: &Parameters, fields: &[Field], cattrs: &attr::Container, + has_flatten: bool, ) -> Fragment { // Create the field names for the fields. let fields_names: Vec<_> = fields @@ -2480,7 +2493,7 @@ fn deserialize_map( }); // Collect contents for flatten fields into a buffer - let let_collect = if cattrs.has_flatten() { + let let_collect = if has_flatten { Some(quote! { let mut __collect = _serde::__private::Vec::<_serde::__private::Option<( _serde::__private::de::Content, @@ -2532,7 +2545,7 @@ fn deserialize_map( }); // Visit ignored values to consume them - let ignored_arm = if cattrs.has_flatten() { + let ignored_arm = if has_flatten { Some(quote! { __Field::__other(__name) => { __collect.push(_serde::__private::Some(( @@ -2602,7 +2615,7 @@ fn deserialize_map( } }); - let collected_deny_unknown_fields = if cattrs.has_flatten() && cattrs.deny_unknown_fields() { + let collected_deny_unknown_fields = if has_flatten && cattrs.deny_unknown_fields() { Some(quote! { if let _serde::__private::Some(_serde::__private::Some((__key, _))) = __collect.into_iter().filter(_serde::__private::Option::is_some).next() @@ -2678,7 +2691,10 @@ fn deserialize_map_in_place( fields: &[Field], cattrs: &attr::Container, ) -> Fragment { - assert!(!cattrs.has_flatten()); + assert!( + !has_flatten(fields), + "inplace deserialization of maps does not support flatten fields" + ); // Create the field names for the fields. let fields_names: Vec<_> = fields @@ -3011,6 +3027,14 @@ fn effective_style(variant: &Variant) -> Style { } } +/// True if there is any field with a `#[serde(flatten)]` attribute, other than +/// fields which are skipped. +fn has_flatten(fields: &[Field]) -> bool { + fields + .iter() + .any(|field| field.attrs.flatten() && !field.attrs.skip_deserializing()) +} + struct DeImplGenerics<'a>(&'a Parameters); #[cfg(feature = "deserialize_in_place")] struct InPlaceImplGenerics<'a>(&'a Parameters); diff --git a/serde_derive/src/internals/ast.rs b/serde_derive/src/internals/ast.rs index a28d3ae7e..3293823a7 100644 --- a/serde_derive/src/internals/ast.rs +++ b/serde_derive/src/internals/ast.rs @@ -63,7 +63,7 @@ impl<'a> Container<'a> { item: &'a syn::DeriveInput, derive: Derive, ) -> Option> { - let mut attrs = attr::Container::from_ast(cx, item); + let attrs = attr::Container::from_ast(cx, item); let mut data = match &item.data { syn::Data::Enum(data) => Data::Enum(enum_from_ast(cx, &data.variants, attrs.default())), @@ -77,15 +77,11 @@ impl<'a> Container<'a> { } }; - let mut has_flatten = false; match &mut data { Data::Enum(variants) => { for variant in variants { variant.attrs.rename_by_rules(attrs.rename_all_rules()); for field in &mut variant.fields { - if field.attrs.flatten() { - has_flatten = true; - } field.attrs.rename_by_rules( variant .attrs @@ -97,18 +93,11 @@ impl<'a> Container<'a> { } Data::Struct(_, fields) => { for field in fields { - if field.attrs.flatten() { - has_flatten = true; - } field.attrs.rename_by_rules(attrs.rename_all_rules()); } } } - if has_flatten { - attrs.mark_has_flatten(); - } - let mut item = Container { ident: item.ident.clone(), attrs, diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index 0cfb23bf1..ac5f5d9a5 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -216,7 +216,6 @@ pub struct Container { type_into: Option, remote: Option, identifier: Identifier, - has_flatten: bool, serde_path: Option, is_packed: bool, /// Error message generated when type can't be deserialized @@ -587,7 +586,6 @@ impl Container { type_into: type_into.get(), remote: remote.get(), identifier: decide_identifier(cx, item, field_identifier, variant_identifier), - has_flatten: false, serde_path: serde_path.get(), is_packed, expecting: expecting.get(), @@ -655,14 +653,6 @@ impl Container { self.identifier } - pub fn has_flatten(&self) -> bool { - self.has_flatten - } - - pub fn mark_has_flatten(&mut self) { - self.has_flatten = true; - } - pub fn custom_serde_path(&self) -> Option<&syn::Path> { self.serde_path.as_ref() } diff --git a/serde_derive/src/lib.rs b/serde_derive/src/lib.rs index 019cbff62..bedd1cb3d 100644 --- a/serde_derive/src/lib.rs +++ b/serde_derive/src/lib.rs @@ -13,7 +13,7 @@ //! //! [https://serde.rs/derive.html]: https://serde.rs/derive.html -#![doc(html_root_url = "https://docs.rs/serde_derive/1.0.203")] +#![doc(html_root_url = "https://docs.rs/serde_derive/1.0.210")] #![cfg_attr(not(check_cfg), allow(unexpected_cfgs))] // Ignored clippy lints #![allow( diff --git a/serde_derive/src/ser.rs b/serde_derive/src/ser.rs index 7d89d2212..35f8ca4bd 100644 --- a/serde_derive/src/ser.rs +++ b/serde_derive/src/ser.rs @@ -289,9 +289,18 @@ fn serialize_tuple_struct( } fn serialize_struct(params: &Parameters, fields: &[Field], cattrs: &attr::Container) -> Fragment { - assert!(fields.len() as u64 <= u64::from(u32::MAX)); + assert!( + fields.len() as u64 <= u64::from(u32::MAX), + "too many fields in {}: {}, maximum supported count is {}", + cattrs.name().serialize_name(), + fields.len(), + u32::MAX, + ); - if cattrs.has_flatten() { + let has_non_skipped_flatten = fields + .iter() + .any(|field| field.attrs.flatten() && !field.attrs.skip_serializing()); + if has_non_skipped_flatten { serialize_struct_as_map(params, fields, cattrs) } else { serialize_struct_as_struct(params, fields, cattrs) @@ -370,26 +379,8 @@ fn serialize_struct_as_map( let let_mut = mut_if(serialized_fields.peek().is_some() || tag_field_exists); - let len = if cattrs.has_flatten() { - quote!(_serde::__private::None) - } else { - let len = serialized_fields - .map(|field| match field.attrs.skip_serializing_if() { - None => quote!(1), - Some(path) => { - let field_expr = get_member(params, field, &field.member); - quote!(if #path(#field_expr) { 0 } else { 1 }) - } - }) - .fold( - quote!(#tag_field_exists as usize), - |sum, expr| quote!(#sum + #expr), - ); - quote!(_serde::__private::Some(#len)) - }; - quote_block! { - let #let_mut __serde_state = _serde::Serializer::serialize_map(__serializer, #len)?; + let #let_mut __serde_state = _serde::Serializer::serialize_map(__serializer, _serde::__private::None)?; #tag_field #(#serialize_fields)* _serde::ser::SerializeMap::end(__serde_state) diff --git a/test_suite/Cargo.toml b/test_suite/Cargo.toml index b7686baf2..07e62656a 100644 --- a/test_suite/Cargo.toml +++ b/test_suite/Cargo.toml @@ -18,4 +18,4 @@ rustversion = "1.0" serde = { path = "../serde", features = ["rc"] } serde_derive = { path = "../serde_derive", features = ["deserialize_in_place"] } serde_test = "1.0.176" -trybuild = { version = "1.0.66", features = ["diff"] } +trybuild = { version = "1.0.97", features = ["diff"] } diff --git a/test_suite/no_std/src/main.rs b/test_suite/no_std/src/main.rs index 57b3f3151..f8ef34b44 100644 --- a/test_suite/no_std/src/main.rs +++ b/test_suite/no_std/src/main.rs @@ -23,21 +23,21 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { use serde_derive::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] -struct Unit; +pub struct Unit; #[derive(Serialize, Deserialize)] -struct Newtype(u8); +pub struct Newtype(u8); #[derive(Serialize, Deserialize)] -struct Tuple(u8, u8); +pub struct Tuple(u8, u8); #[derive(Serialize, Deserialize)] -struct Struct { +pub struct Struct { f: u8, } #[derive(Serialize, Deserialize)] -enum Enum { +pub enum Enum { Unit, Newtype(u8), Tuple(u8, u8), diff --git a/test_suite/tests/compiletest.rs b/test_suite/tests/compiletest.rs index 621660b08..8ab6342f2 100644 --- a/test_suite/tests/compiletest.rs +++ b/test_suite/tests/compiletest.rs @@ -1,6 +1,6 @@ -#[cfg_attr(target_os = "emscripten", ignore)] -#[rustversion::attr(not(nightly), ignore)] -#[cfg_attr(miri, ignore)] +#[cfg_attr(target_os = "emscripten", ignore = "disabled on Emscripten")] +#[rustversion::attr(not(nightly), ignore = "requires nightly")] +#[cfg_attr(miri, ignore = "incompatible with miri")] #[allow(unused_attributes)] #[test] fn ui() { diff --git a/test_suite/tests/regression/issue1904.rs b/test_suite/tests/regression/issue1904.rs new file mode 100644 index 000000000..b1d5c7314 --- /dev/null +++ b/test_suite/tests/regression/issue1904.rs @@ -0,0 +1,66 @@ +#![allow(dead_code)] // we do not read enum fields + +use serde_derive::Deserialize; + +#[derive(Deserialize)] +pub struct Nested; + +#[derive(Deserialize)] +pub enum ExternallyTagged1 { + Tuple(f64, String), + Flatten { + #[serde(flatten)] + nested: Nested, + }, +} + +#[derive(Deserialize)] +pub enum ExternallyTagged2 { + Flatten { + #[serde(flatten)] + nested: Nested, + }, + Tuple(f64, String), +} + +// Internally tagged enums cannot contain tuple variants so not tested here + +#[derive(Deserialize)] +#[serde(tag = "tag", content = "content")] +pub enum AdjacentlyTagged1 { + Tuple(f64, String), + Flatten { + #[serde(flatten)] + nested: Nested, + }, +} + +#[derive(Deserialize)] +#[serde(tag = "tag", content = "content")] +pub enum AdjacentlyTagged2 { + Flatten { + #[serde(flatten)] + nested: Nested, + }, + Tuple(f64, String), +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum Untagged1 { + Tuple(f64, String), + Flatten { + #[serde(flatten)] + nested: Nested, + }, +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum Untagged2 { + Flatten { + #[serde(flatten)] + nested: Nested, + }, + Tuple(f64, String), +} diff --git a/test_suite/tests/regression/issue2565.rs b/test_suite/tests/regression/issue2565.rs new file mode 100644 index 000000000..65cbb0a31 --- /dev/null +++ b/test_suite/tests/regression/issue2565.rs @@ -0,0 +1,41 @@ +use serde_derive::{Serialize, Deserialize}; +use serde_test::{assert_tokens, Token}; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +enum Enum { + Simple { + a: i32, + }, + Flatten { + #[serde(flatten)] + flatten: (), + a: i32, + }, +} + +#[test] +fn simple_variant() { + assert_tokens( + &Enum::Simple { a: 42 }, + &[ + Token::StructVariant { name: "Enum", variant: "Simple", len: 1 }, + Token::Str("a"), + Token::I32(42), + Token::StructVariantEnd, + ] + ); +} + +#[test] +fn flatten_variant() { + assert_tokens( + &Enum::Flatten { flatten: (), a: 42 }, + &[ + Token::NewtypeVariant { name: "Enum", variant: "Flatten" }, + Token::Map { len: None }, + Token::Str("a"), + Token::I32(42), + Token::MapEnd, + ] + ); +} diff --git a/test_suite/tests/regression/issue2792.rs b/test_suite/tests/regression/issue2792.rs new file mode 100644 index 000000000..a8c1604ca --- /dev/null +++ b/test_suite/tests/regression/issue2792.rs @@ -0,0 +1,18 @@ +#![allow(dead_code)] // we do not read enum fields + +use serde_derive::Deserialize; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub enum A { + B { + c: String, + }, + D { + #[serde(flatten)] + e: E, + }, +} + +#[derive(Deserialize)] +pub struct E {} diff --git a/test_suite/tests/test_annotations.rs b/test_suite/tests/test_annotations.rs index 566f7d43f..b26ec8722 100644 --- a/test_suite/tests/test_annotations.rs +++ b/test_suite/tests/test_annotations.rs @@ -1607,623 +1607,6 @@ fn test_collect_other() { ); } -#[test] -fn test_unknown_field_in_flatten() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(deny_unknown_fields)] - struct Outer { - dummy: String, - #[serde(flatten)] - inner: Inner, - } - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Inner { - foo: HashMap, - } - - assert_de_tokens_error::( - &[ - Token::Struct { - name: "Outer", - len: 1, - }, - Token::Str("dummy"), - Token::Str("23"), - Token::Str("foo"), - Token::Map { len: None }, - Token::Str("a"), - Token::U32(1), - Token::Str("b"), - Token::U32(2), - Token::MapEnd, - Token::Str("bar"), - Token::U32(23), - Token::StructEnd, - ], - "unknown field `bar`", - ); -} - -#[test] -fn test_complex_flatten() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Outer { - y: u32, - #[serde(flatten)] - first: First, - #[serde(flatten)] - second: Second, - z: u32, - } - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct First { - a: u32, - b: bool, - c: Vec, - d: String, - e: Option, - } - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Second { - f: u32, - } - - assert_de_tokens( - &Outer { - y: 0, - first: First { - a: 1, - b: true, - c: vec!["a".into(), "b".into()], - d: "c".into(), - e: Some(2), - }, - second: Second { f: 3 }, - z: 4, - }, - &[ - Token::Map { len: None }, - Token::Str("y"), - Token::U32(0), - Token::Str("a"), - Token::U32(1), - Token::Str("b"), - Token::Bool(true), - Token::Str("c"), - Token::Seq { len: Some(2) }, - Token::Str("a"), - Token::Str("b"), - Token::SeqEnd, - Token::Str("d"), - Token::Str("c"), - Token::Str("e"), - Token::U64(2), - Token::Str("f"), - Token::U32(3), - Token::Str("z"), - Token::U32(4), - Token::MapEnd, - ], - ); - - assert_ser_tokens( - &Outer { - y: 0, - first: First { - a: 1, - b: true, - c: vec!["a".into(), "b".into()], - d: "c".into(), - e: Some(2), - }, - second: Second { f: 3 }, - z: 4, - }, - &[ - Token::Map { len: None }, - Token::Str("y"), - Token::U32(0), - Token::Str("a"), - Token::U32(1), - Token::Str("b"), - Token::Bool(true), - Token::Str("c"), - Token::Seq { len: Some(2) }, - Token::Str("a"), - Token::Str("b"), - Token::SeqEnd, - Token::Str("d"), - Token::Str("c"), - Token::Str("e"), - Token::Some, - Token::U64(2), - Token::Str("f"), - Token::U32(3), - Token::Str("z"), - Token::U32(4), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_flatten_map_twice() { - #[derive(Debug, PartialEq, Deserialize)] - struct Outer { - #[serde(flatten)] - first: BTreeMap, - #[serde(flatten)] - between: Inner, - #[serde(flatten)] - second: BTreeMap, - } - - #[derive(Debug, PartialEq, Deserialize)] - struct Inner { - y: String, - } - - assert_de_tokens( - &Outer { - first: { - let mut first = BTreeMap::new(); - first.insert("x".to_owned(), "X".to_owned()); - first.insert("y".to_owned(), "Y".to_owned()); - first - }, - between: Inner { y: "Y".to_owned() }, - second: { - let mut second = BTreeMap::new(); - second.insert("x".to_owned(), "X".to_owned()); - second - }, - }, - &[ - Token::Map { len: None }, - Token::Str("x"), - Token::Str("X"), - Token::Str("y"), - Token::Str("Y"), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_flatten_unit() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Response { - #[serde(flatten)] - data: T, - status: usize, - } - - assert_tokens( - &Response { - data: (), - status: 0, - }, - &[ - Token::Map { len: None }, - Token::Str("status"), - Token::U64(0), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_flatten_unsupported_type() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Outer { - outer: String, - #[serde(flatten)] - inner: String, - } - - assert_ser_tokens_error( - &Outer { - outer: "foo".into(), - inner: "bar".into(), - }, - &[ - Token::Map { len: None }, - Token::Str("outer"), - Token::Str("foo"), - ], - "can only flatten structs and maps (got a string)", - ); - assert_de_tokens_error::( - &[ - Token::Map { len: None }, - Token::Str("outer"), - Token::Str("foo"), - Token::Str("a"), - Token::Str("b"), - Token::MapEnd, - ], - "can only flatten structs and maps", - ); -} - -#[test] -fn test_non_string_keys() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct TestStruct { - name: String, - age: u32, - #[serde(flatten)] - mapping: HashMap, - } - - let mut mapping = HashMap::new(); - mapping.insert(0, 42); - assert_tokens( - &TestStruct { - name: "peter".into(), - age: 3, - mapping, - }, - &[ - Token::Map { len: None }, - Token::Str("name"), - Token::Str("peter"), - Token::Str("age"), - Token::U32(3), - Token::U32(0), - Token::U32(42), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_lifetime_propagation_for_flatten() { - #[derive(Deserialize, Serialize, Debug, PartialEq)] - struct A { - #[serde(flatten)] - t: T, - } - - #[derive(Deserialize, Serialize, Debug, PartialEq)] - struct B<'a> { - #[serde(flatten, borrow)] - t: HashMap<&'a str, u32>, - } - - #[derive(Deserialize, Serialize, Debug, PartialEq)] - struct C<'a> { - #[serde(flatten, borrow)] - t: HashMap<&'a [u8], u32>, - } - - let mut owned_map = HashMap::new(); - owned_map.insert("x".to_string(), 42u32); - assert_tokens( - &A { t: owned_map }, - &[ - Token::Map { len: None }, - Token::Str("x"), - Token::U32(42), - Token::MapEnd, - ], - ); - - let mut borrowed_map = HashMap::new(); - borrowed_map.insert("x", 42u32); - assert_ser_tokens( - &B { - t: borrowed_map.clone(), - }, - &[ - Token::Map { len: None }, - Token::BorrowedStr("x"), - Token::U32(42), - Token::MapEnd, - ], - ); - - assert_de_tokens( - &B { t: borrowed_map }, - &[ - Token::Map { len: None }, - Token::BorrowedStr("x"), - Token::U32(42), - Token::MapEnd, - ], - ); - - let mut borrowed_map = HashMap::new(); - borrowed_map.insert(&b"x"[..], 42u32); - assert_ser_tokens( - &C { - t: borrowed_map.clone(), - }, - &[ - Token::Map { len: None }, - Token::Seq { len: Some(1) }, - Token::U8(120), - Token::SeqEnd, - Token::U32(42), - Token::MapEnd, - ], - ); - - assert_de_tokens( - &C { t: borrowed_map }, - &[ - Token::Map { len: None }, - Token::BorrowedBytes(b"x"), - Token::U32(42), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_externally_tagged_enum_containing_flatten() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - enum Data { - A { - a: i32, - #[serde(flatten)] - flat: Flat, - }, - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Flat { - b: i32, - } - - let data = Data::A { - a: 0, - flat: Flat { b: 0 }, - }; - - assert_tokens( - &data, - &[ - Token::NewtypeVariant { - name: "Data", - variant: "A", - }, - Token::Map { len: None }, - Token::Str("a"), - Token::I32(0), - Token::Str("b"), - Token::I32(0), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_internally_tagged_enum_with_skipped_conflict() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - #[serde(tag = "t")] - enum Data { - A, - #[serde(skip)] - #[allow(dead_code)] - B { - t: String, - }, - C { - #[serde(default, skip)] - t: String, - }, - } - - let data = Data::C { t: String::new() }; - - assert_tokens( - &data, - &[ - Token::Struct { - name: "Data", - len: 1, - }, - Token::Str("t"), - Token::Str("C"), - Token::StructEnd, - ], - ); -} - -#[test] -fn test_internally_tagged_enum_containing_flatten() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - #[serde(tag = "t")] - enum Data { - A { - a: i32, - #[serde(flatten)] - flat: Flat, - }, - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Flat { - b: i32, - } - - let data = Data::A { - a: 0, - flat: Flat { b: 0 }, - }; - - assert_tokens( - &data, - &[ - Token::Map { len: None }, - Token::Str("t"), - Token::Str("A"), - Token::Str("a"), - Token::I32(0), - Token::Str("b"), - Token::I32(0), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_internally_tagged_enum_new_type_with_unit() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - #[serde(tag = "t")] - enum Data { - A(()), - } - - assert_tokens( - &Data::A(()), - &[ - Token::Map { len: Some(1) }, - Token::Str("t"), - Token::Str("A"), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_adjacently_tagged_enum_bytes() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - #[serde(tag = "t", content = "c")] - enum Data { - A { a: i32 }, - } - - let data = Data::A { a: 0 }; - - assert_tokens( - &data, - &[ - Token::Struct { - name: "Data", - len: 2, - }, - Token::Str("t"), - Token::UnitVariant { - name: "Data", - variant: "A", - }, - Token::Str("c"), - Token::Struct { name: "A", len: 1 }, - Token::Str("a"), - Token::I32(0), - Token::StructEnd, - Token::StructEnd, - ], - ); - - assert_de_tokens( - &data, - &[ - Token::Struct { - name: "Data", - len: 2, - }, - Token::Bytes(b"t"), - Token::UnitVariant { - name: "Data", - variant: "A", - }, - Token::Bytes(b"c"), - Token::Struct { name: "A", len: 1 }, - Token::Str("a"), - Token::I32(0), - Token::StructEnd, - Token::StructEnd, - ], - ); -} - -#[test] -fn test_adjacently_tagged_enum_containing_flatten() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - #[serde(tag = "t", content = "c")] - enum Data { - A { - a: i32, - #[serde(flatten)] - flat: Flat, - }, - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Flat { - b: i32, - } - - let data = Data::A { - a: 0, - flat: Flat { b: 0 }, - }; - - assert_tokens( - &data, - &[ - Token::Struct { - name: "Data", - len: 2, - }, - Token::Str("t"), - Token::UnitVariant { - name: "Data", - variant: "A", - }, - Token::Str("c"), - Token::Map { len: None }, - Token::Str("a"), - Token::I32(0), - Token::Str("b"), - Token::I32(0), - Token::MapEnd, - Token::StructEnd, - ], - ); -} - -#[test] -fn test_untagged_enum_containing_flatten() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - #[serde(untagged)] - enum Data { - A { - a: i32, - #[serde(flatten)] - flat: Flat, - }, - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Flat { - b: i32, - } - - let data = Data::A { - a: 0, - flat: Flat { b: 0 }, - }; - - assert_tokens( - &data, - &[ - Token::Map { len: None }, - Token::Str("a"), - Token::I32(0), - Token::Str("b"), - Token::I32(0), - Token::MapEnd, - ], - ); -} - #[test] fn test_partially_untagged_enum() { #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -2393,148 +1776,21 @@ fn test_partially_untagged_internally_tagged_enum() { let data = Data::A; - assert_de_tokens( - &data, - &[ - Token::Map { len: None }, - Token::Str("t"), - Token::Str("A"), - Token::MapEnd, - ], - ); - - let data = Data::Var(42); - - assert_de_tokens(&data, &[Token::U32(42)]); - - // TODO test error output -} - -#[test] -fn test_partially_untagged_adjacently_tagged_enum() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - #[serde(tag = "t", content = "c")] - enum Data { - A(u32), - B, - #[serde(untagged)] - Var(u32), - } - - let data = Data::A(7); - - assert_de_tokens( - &data, - &[ - Token::Map { len: None }, - Token::Str("t"), - Token::Str("A"), - Token::Str("c"), - Token::U32(7), - Token::MapEnd, - ], - ); - - let data = Data::Var(42); - - assert_de_tokens(&data, &[Token::U32(42)]); - - // TODO test error output -} - -#[test] -fn test_flatten_option() { - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Outer { - #[serde(flatten)] - inner1: Option, - #[serde(flatten)] - inner2: Option, - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Inner1 { - inner1: i32, - } - - #[derive(Serialize, Deserialize, PartialEq, Debug)] - struct Inner2 { - inner2: i32, - } - - assert_tokens( - &Outer { - inner1: Some(Inner1 { inner1: 1 }), - inner2: Some(Inner2 { inner2: 2 }), - }, - &[ - Token::Map { len: None }, - Token::Str("inner1"), - Token::I32(1), - Token::Str("inner2"), - Token::I32(2), - Token::MapEnd, - ], - ); - - assert_tokens( - &Outer { - inner1: Some(Inner1 { inner1: 1 }), - inner2: None, - }, - &[ - Token::Map { len: None }, - Token::Str("inner1"), - Token::I32(1), - Token::MapEnd, - ], - ); - - assert_tokens( - &Outer { - inner1: None, - inner2: Some(Inner2 { inner2: 2 }), - }, + assert_de_tokens( + &data, &[ Token::Map { len: None }, - Token::Str("inner2"), - Token::I32(2), + Token::Str("t"), + Token::Str("A"), Token::MapEnd, ], ); - assert_tokens( - &Outer { - inner1: None, - inner2: None, - }, - &[Token::Map { len: None }, Token::MapEnd], - ); -} - -#[test] -fn test_flatten_ignored_any() { - #[derive(Deserialize, PartialEq, Debug)] - struct Outer { - #[serde(flatten)] - inner: IgnoredAny, - } + let data = Data::Var(42); - assert_de_tokens( - &Outer { inner: IgnoredAny }, - &[Token::Map { len: None }, Token::MapEnd], - ); + assert_de_tokens(&data, &[Token::U32(42)]); - assert_de_tokens( - &Outer { inner: IgnoredAny }, - &[ - Token::Struct { - name: "DoNotMatter", - len: 0, - }, - Token::StructEnd, - ], - ); + // TODO test error output } #[test] @@ -2575,152 +1831,6 @@ fn test_transparent_tuple_struct() { assert_tokens(&Transparent(false, 1, false, PhantomData), &[Token::U32(1)]); } -#[test] -fn test_internally_tagged_unit_enum_with_unknown_fields() { - #[derive(Deserialize, PartialEq, Debug)] - #[serde(tag = "t")] - enum Data { - A, - } - - let data = Data::A; - - assert_de_tokens( - &data, - &[ - Token::Map { len: None }, - Token::Str("t"), - Token::Str("A"), - Token::Str("b"), - Token::I32(0), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_flatten_any_after_flatten_struct() { - #[derive(PartialEq, Debug)] - struct Any; - - impl<'de> Deserialize<'de> for Any { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct AnyVisitor; - - impl<'de> Visitor<'de> for AnyVisitor { - type Value = Any; - - fn expecting(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { - unimplemented!() - } - - fn visit_map(self, mut map: M) -> Result - where - M: MapAccess<'de>, - { - while let Some((Any, Any)) = map.next_entry()? {} - Ok(Any) - } - } - - deserializer.deserialize_any(AnyVisitor) - } - } - - #[derive(Deserialize, PartialEq, Debug)] - struct Outer { - #[serde(flatten)] - inner: Inner, - #[serde(flatten)] - extra: Any, - } - - #[derive(Deserialize, PartialEq, Debug)] - struct Inner { - inner: i32, - } - - let s = Outer { - inner: Inner { inner: 0 }, - extra: Any, - }; - - assert_de_tokens( - &s, - &[ - Token::Map { len: None }, - Token::Str("inner"), - Token::I32(0), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_alias_in_flatten_context() { - #[derive(Debug, PartialEq, Deserialize)] - struct Outer { - #[serde(flatten)] - a: AliasStruct, - b: i32, - } - - assert_de_tokens( - &Outer { - a: AliasStruct { - a1: 1, - a2: 2, - a4: 4, - }, - b: 7, - }, - &[ - Token::Struct { - name: "Outer", - len: 4, - }, - Token::Str("a1"), - Token::I32(1), - Token::Str("a2"), - Token::I32(2), - Token::Str("a5"), - Token::I32(4), - Token::Str("b"), - Token::I32(7), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Outer { - a: AliasStruct { - a1: 1, - a2: 2, - a4: 4, - }, - b: 7, - }, - &[ - Token::Struct { - name: "Outer", - len: 4, - }, - Token::Str("a1"), - Token::I32(1), - Token::Str("a2"), - Token::I32(2), - Token::Str("a6"), - Token::I32(4), - Token::Str("b"), - Token::I32(7), - Token::StructEnd, - ], - ); -} - #[test] fn test_expecting_message() { #[derive(Deserialize, PartialEq, Debug)] @@ -2785,65 +1895,6 @@ fn test_expecting_message_externally_tagged_enum() { ); } -#[test] -fn test_expecting_message_internally_tagged_enum() { - #[derive(Deserialize)] - #[serde(tag = "tag")] - #[serde(expecting = "something strange...")] - enum Enum { - InternallyTagged, - } - - assert_de_tokens_error::( - &[Token::Str("InternallyTagged")], - r#"invalid type: string "InternallyTagged", expected something strange..."#, - ); - - // Check that #[serde(expecting = "...")] doesn't affect variant identifier error message - assert_de_tokens_error::( - &[Token::Map { len: None }, Token::Str("tag"), Token::Unit], - "invalid type: unit value, expected variant identifier", - ); -} - -#[test] -fn test_expecting_message_adjacently_tagged_enum() { - #[derive(Deserialize)] - #[serde(tag = "tag", content = "content")] - #[serde(expecting = "something strange...")] - enum Enum { - AdjacentlyTagged, - } - - assert_de_tokens_error::( - &[Token::Str("AdjacentlyTagged")], - r#"invalid type: string "AdjacentlyTagged", expected something strange..."#, - ); - - assert_de_tokens_error::( - &[Token::Map { len: None }, Token::Unit], - r#"invalid type: unit value, expected "tag", "content", or other ignored fields"#, - ); - - // Check that #[serde(expecting = "...")] doesn't affect variant identifier error message - assert_de_tokens_error::( - &[Token::Map { len: None }, Token::Str("tag"), Token::Unit], - "invalid type: unit value, expected variant of enum Enum", - ); -} - -#[test] -fn test_expecting_message_untagged_tagged_enum() { - #[derive(Deserialize)] - #[serde(untagged)] - #[serde(expecting = "something strange...")] - enum Enum { - Untagged, - } - - assert_de_tokens_error::(&[Token::Str("Untagged")], "something strange..."); -} - #[test] fn test_expecting_message_identifier_enum() { #[derive(Deserialize)] @@ -2894,6 +1945,660 @@ fn test_expecting_message_identifier_enum() { mod flatten { use super::*; + #[test] + fn complex() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Outer { + y: u32, + #[serde(flatten)] + first: First, + #[serde(flatten)] + second: Second, + z: u32, + } + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct First { + a: u32, + b: bool, + c: Vec, + d: String, + e: Option, + } + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Second { + f: u32, + } + + assert_de_tokens( + &Outer { + y: 0, + first: First { + a: 1, + b: true, + c: vec!["a".into(), "b".into()], + d: "c".into(), + e: Some(2), + }, + second: Second { f: 3 }, + z: 4, + }, + &[ + Token::Map { len: None }, + Token::Str("y"), + Token::U32(0), + Token::Str("a"), + Token::U32(1), + Token::Str("b"), + Token::Bool(true), + Token::Str("c"), + Token::Seq { len: Some(2) }, + Token::Str("a"), + Token::Str("b"), + Token::SeqEnd, + Token::Str("d"), + Token::Str("c"), + Token::Str("e"), + Token::U64(2), + Token::Str("f"), + Token::U32(3), + Token::Str("z"), + Token::U32(4), + Token::MapEnd, + ], + ); + + assert_ser_tokens( + &Outer { + y: 0, + first: First { + a: 1, + b: true, + c: vec!["a".into(), "b".into()], + d: "c".into(), + e: Some(2), + }, + second: Second { f: 3 }, + z: 4, + }, + &[ + Token::Map { len: None }, + Token::Str("y"), + Token::U32(0), + Token::Str("a"), + Token::U32(1), + Token::Str("b"), + Token::Bool(true), + Token::Str("c"), + Token::Seq { len: Some(2) }, + Token::Str("a"), + Token::Str("b"), + Token::SeqEnd, + Token::Str("d"), + Token::Str("c"), + Token::Str("e"), + Token::Some, + Token::U64(2), + Token::Str("f"), + Token::U32(3), + Token::Str("z"), + Token::U32(4), + Token::MapEnd, + ], + ); + } + + #[test] + fn map_twice() { + #[derive(Debug, PartialEq, Deserialize)] + struct Outer { + #[serde(flatten)] + first: BTreeMap, + #[serde(flatten)] + between: Inner, + #[serde(flatten)] + second: BTreeMap, + } + + #[derive(Debug, PartialEq, Deserialize)] + struct Inner { + y: String, + } + + assert_de_tokens( + &Outer { + first: { + let mut first = BTreeMap::new(); + first.insert("x".to_owned(), "X".to_owned()); + first.insert("y".to_owned(), "Y".to_owned()); + first + }, + between: Inner { y: "Y".to_owned() }, + second: { + let mut second = BTreeMap::new(); + second.insert("x".to_owned(), "X".to_owned()); + second + }, + }, + &[ + Token::Map { len: None }, + Token::Str("x"), + Token::Str("X"), + Token::Str("y"), + Token::Str("Y"), + Token::MapEnd, + ], + ); + } + + #[test] + fn unsupported_type() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Outer { + outer: String, + #[serde(flatten)] + inner: String, + } + + assert_ser_tokens_error( + &Outer { + outer: "foo".into(), + inner: "bar".into(), + }, + &[ + Token::Map { len: None }, + Token::Str("outer"), + Token::Str("foo"), + ], + "can only flatten structs and maps (got a string)", + ); + assert_de_tokens_error::( + &[ + Token::Map { len: None }, + Token::Str("outer"), + Token::Str("foo"), + Token::Str("a"), + Token::Str("b"), + Token::MapEnd, + ], + "can only flatten structs and maps", + ); + } + + #[test] + fn unknown_field() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(deny_unknown_fields)] + struct Outer { + dummy: String, + #[serde(flatten)] + inner: Inner, + } + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Inner { + foo: HashMap, + } + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "Outer", + len: 1, + }, + Token::Str("dummy"), + Token::Str("23"), + Token::Str("foo"), + Token::Map { len: None }, + Token::Str("a"), + Token::U32(1), + Token::Str("b"), + Token::U32(2), + Token::MapEnd, + Token::Str("bar"), + Token::U32(23), + Token::StructEnd, + ], + "unknown field `bar`", + ); + } + + #[test] + fn non_string_keys() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct TestStruct { + name: String, + age: u32, + #[serde(flatten)] + mapping: HashMap, + } + + let mut mapping = HashMap::new(); + mapping.insert(0, 42); + assert_tokens( + &TestStruct { + name: "peter".into(), + age: 3, + mapping, + }, + &[ + Token::Map { len: None }, + Token::Str("name"), + Token::Str("peter"), + Token::Str("age"), + Token::U32(3), + Token::U32(0), + Token::U32(42), + Token::MapEnd, + ], + ); + } + + #[test] + fn lifetime_propagation() { + #[derive(Deserialize, Serialize, Debug, PartialEq)] + struct A { + #[serde(flatten)] + t: T, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + struct B<'a> { + #[serde(flatten, borrow)] + t: HashMap<&'a str, u32>, + } + + #[derive(Deserialize, Serialize, Debug, PartialEq)] + struct C<'a> { + #[serde(flatten, borrow)] + t: HashMap<&'a [u8], u32>, + } + + let mut owned_map = HashMap::new(); + owned_map.insert("x".to_string(), 42u32); + assert_tokens( + &A { t: owned_map }, + &[ + Token::Map { len: None }, + Token::Str("x"), + Token::U32(42), + Token::MapEnd, + ], + ); + + let mut borrowed_map = HashMap::new(); + borrowed_map.insert("x", 42u32); + assert_ser_tokens( + &B { + t: borrowed_map.clone(), + }, + &[ + Token::Map { len: None }, + Token::BorrowedStr("x"), + Token::U32(42), + Token::MapEnd, + ], + ); + + assert_de_tokens( + &B { t: borrowed_map }, + &[ + Token::Map { len: None }, + Token::BorrowedStr("x"), + Token::U32(42), + Token::MapEnd, + ], + ); + + let mut borrowed_map = HashMap::new(); + borrowed_map.insert(&b"x"[..], 42u32); + assert_ser_tokens( + &C { + t: borrowed_map.clone(), + }, + &[ + Token::Map { len: None }, + Token::Seq { len: Some(1) }, + Token::U8(120), + Token::SeqEnd, + Token::U32(42), + Token::MapEnd, + ], + ); + + assert_de_tokens( + &C { t: borrowed_map }, + &[ + Token::Map { len: None }, + Token::BorrowedBytes(b"x"), + Token::U32(42), + Token::MapEnd, + ], + ); + } + + // Regression test for https://github.com/serde-rs/serde/issues/1904 + #[test] + fn enum_tuple_and_struct() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + enum Outer { + Tuple(f64, i32), + Flatten { + #[serde(flatten)] + nested: Nested, + }, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Nested { + a: i32, + b: i32, + } + + assert_tokens( + &Outer::Tuple(1.2, 3), + &[ + Token::TupleVariant { + name: "Outer", + variant: "Tuple", + len: 2, + }, + Token::F64(1.2), + Token::I32(3), + Token::TupleVariantEnd, + ], + ); + assert_tokens( + &Outer::Flatten { + nested: Nested { a: 1, b: 2 }, + }, + &[ + Token::NewtypeVariant { + name: "Outer", + variant: "Flatten", + }, + Token::Map { len: None }, + Token::Str("a"), + Token::I32(1), + Token::Str("b"), + Token::I32(2), + Token::MapEnd, + ], + ); + } + + #[test] + fn option() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Outer { + #[serde(flatten)] + inner1: Option, + #[serde(flatten)] + inner2: Option, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Inner1 { + inner1: i32, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Inner2 { + inner2: i32, + } + + assert_tokens( + &Outer { + inner1: Some(Inner1 { inner1: 1 }), + inner2: Some(Inner2 { inner2: 2 }), + }, + &[ + Token::Map { len: None }, + Token::Str("inner1"), + Token::I32(1), + Token::Str("inner2"), + Token::I32(2), + Token::MapEnd, + ], + ); + + assert_tokens( + &Outer { + inner1: Some(Inner1 { inner1: 1 }), + inner2: None, + }, + &[ + Token::Map { len: None }, + Token::Str("inner1"), + Token::I32(1), + Token::MapEnd, + ], + ); + + assert_tokens( + &Outer { + inner1: None, + inner2: Some(Inner2 { inner2: 2 }), + }, + &[ + Token::Map { len: None }, + Token::Str("inner2"), + Token::I32(2), + Token::MapEnd, + ], + ); + + assert_tokens( + &Outer { + inner1: None, + inner2: None, + }, + &[Token::Map { len: None }, Token::MapEnd], + ); + } + + #[test] + fn ignored_any() { + #[derive(Deserialize, PartialEq, Debug)] + struct Outer { + #[serde(flatten)] + inner: IgnoredAny, + } + + assert_de_tokens( + &Outer { inner: IgnoredAny }, + &[Token::Map { len: None }, Token::MapEnd], + ); + + assert_de_tokens( + &Outer { inner: IgnoredAny }, + &[ + Token::Struct { + name: "DoNotMatter", + len: 0, + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn flatten_any_after_flatten_struct() { + #[derive(PartialEq, Debug)] + struct Any; + + impl<'de> Deserialize<'de> for Any { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AnyVisitor; + + impl<'de> Visitor<'de> for AnyVisitor { + type Value = Any; + + fn expecting(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { + unimplemented!() + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + while let Some((Any, Any)) = map.next_entry()? {} + Ok(Any) + } + } + + deserializer.deserialize_any(AnyVisitor) + } + } + + #[derive(Deserialize, PartialEq, Debug)] + struct Outer { + #[serde(flatten)] + inner: Inner, + #[serde(flatten)] + extra: Any, + } + + #[derive(Deserialize, PartialEq, Debug)] + struct Inner { + inner: i32, + } + + let s = Outer { + inner: Inner { inner: 0 }, + extra: Any, + }; + + assert_de_tokens( + &s, + &[ + Token::Map { len: None }, + Token::Str("inner"), + Token::I32(0), + Token::MapEnd, + ], + ); + } + + #[test] + fn alias() { + #[derive(Debug, PartialEq, Deserialize)] + struct Outer { + #[serde(flatten)] + a: AliasStruct, + b: i32, + } + + assert_de_tokens( + &Outer { + a: AliasStruct { + a1: 1, + a2: 2, + a4: 4, + }, + b: 7, + }, + &[ + Token::Struct { + name: "Outer", + len: 4, + }, + Token::Str("a1"), + Token::I32(1), + Token::Str("a2"), + Token::I32(2), + Token::Str("a5"), + Token::I32(4), + Token::Str("b"), + Token::I32(7), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &Outer { + a: AliasStruct { + a1: 1, + a2: 2, + a4: 4, + }, + b: 7, + }, + &[ + Token::Struct { + name: "Outer", + len: 4, + }, + Token::Str("a1"), + Token::I32(1), + Token::Str("a2"), + Token::I32(2), + Token::Str("a6"), + Token::I32(4), + Token::Str("b"), + Token::I32(7), + Token::StructEnd, + ], + ); + } + + mod unit { + use super::*; + + #[test] + fn unit() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Response { + #[serde(flatten)] + data: T, + status: usize, + } + + assert_tokens( + &Response { + data: (), + status: 0, + }, + &[ + Token::Map { len: None }, + Token::Str("status"), + Token::U64(0), + Token::MapEnd, + ], + ); + } + + #[test] + fn unit_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Response { + #[serde(flatten)] + data: T, + status: usize, + } + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Unit; + + assert_tokens( + &Response { + data: Unit, + status: 0, + }, + &[ + Token::Map { len: None }, + Token::Str("status"), + Token::U64(0), + Token::MapEnd, + ], + ); + } + } + mod enum_ { use super::*; @@ -2901,6 +2606,44 @@ mod flatten { use super::*; use std::iter::FromIterator; + #[test] + fn straightforward() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + enum Data { + A { + a: i32, + #[serde(flatten)] + flat: Flat, + }, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Flat { + b: i32, + } + + let data = Data::A { + a: 0, + flat: Flat { b: 0 }, + }; + + assert_tokens( + &data, + &[ + Token::NewtypeVariant { + name: "Data", + variant: "A", + }, + Token::Map { len: None }, + Token::Str("a"), + Token::I32(0), + Token::Str("b"), + Token::I32(0), + Token::MapEnd, + ], + ); + } + #[derive(Debug, PartialEq, Serialize, Deserialize)] struct Flatten { #[serde(flatten)] diff --git a/test_suite/tests/test_borrow.rs b/test_suite/tests/test_borrow.rs index f749ec323..a9eee4550 100644 --- a/test_suite/tests/test_borrow.rs +++ b/test_suite/tests/test_borrow.rs @@ -162,7 +162,7 @@ fn test_cow() { #[test] fn test_lifetimes() { #[derive(Deserialize)] - struct Cows<'a, 'b> { + pub struct Cows<'a, 'b> { _copied: Cow<'a, str>, #[serde(borrow)] @@ -178,7 +178,7 @@ fn test_lifetimes() { } #[derive(Deserialize)] - struct Wrap<'a, 'b> { + pub struct Wrap<'a, 'b> { #[serde(borrow = "'b")] _cows: Cows<'a, 'b>, } diff --git a/test_suite/tests/test_de.rs b/test_suite/tests/test_de.rs index 8a7310cd2..31e726d4b 100644 --- a/test_suite/tests/test_de.rs +++ b/test_suite/tests/test_de.rs @@ -93,7 +93,7 @@ struct StructSkipDefault { #[derive(PartialEq, Debug, Deserialize)] #[serde(default)] -struct StructSkipDefaultGeneric { +pub struct StructSkipDefaultGeneric { #[serde(skip_deserializing)] t: T, } diff --git a/test_suite/tests/test_enum_adjacently_tagged.rs b/test_suite/tests/test_enum_adjacently_tagged.rs new file mode 100644 index 000000000..0bcdbc39e --- /dev/null +++ b/test_suite/tests/test_enum_adjacently_tagged.rs @@ -0,0 +1,799 @@ +#![deny(trivial_numeric_casts)] +#![allow( + clippy::derive_partial_eq_without_eq, + clippy::enum_variant_names, + clippy::redundant_field_names, + clippy::too_many_lines +)] + +use serde_derive::{Deserialize, Serialize}; +use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token}; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(tag = "t", content = "c")] +enum AdjacentlyTagged { + Unit, + Newtype(T), + Tuple(u8, u8), + Struct { f: u8 }, +} + +mod unit { + use super::*; + + #[test] + fn map_str_tag_only() { + // Map: tag only + assert_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 1, + }, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::StructEnd, + ], + ); + + // Map: tag only and incorrect hint for number of elements + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn map_int_tag_only() { + // Map: tag (as number) only + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 1, + }, + Token::U16(0), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn map_bytes_tag_only() { + // Map: tag only + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 1, + }, + Token::Bytes(b"t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::StructEnd, + ], + ); + + // Map: tag only + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 1, + }, + Token::BorrowedBytes(b"t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn map_str_tag_content() { + // Map: tag + content + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::Str("c"), + Token::Unit, + Token::StructEnd, + ], + ); + // Map: content + tag + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("c"), + Token::Unit, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::StructEnd, + ], + ); + + // Map: tag + content + excess fields (f, g, h) + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("f"), + Token::Unit, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::Str("g"), + Token::Unit, + Token::Str("c"), + Token::Unit, + Token::Str("h"), + Token::Unit, + Token::StructEnd, + ], + ); + } + + #[test] + fn map_int_tag_content() { + // Map: tag (as number) + content (as number) + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::U8(0), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::U8(1), + Token::Unit, + Token::StructEnd, + ], + ); + + // Map: content (as number) + tag (as number) + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::U64(1), + Token::Unit, + Token::U64(0), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn map_bytes_tag_content() { + // Map: tag + content + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::BorrowedBytes(b"t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::BorrowedBytes(b"c"), + Token::Unit, + Token::StructEnd, + ], + ); + + // Map: content + tag + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Bytes(b"c"), + Token::Unit, + Token::Bytes(b"t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn seq_tag_content() { + // Seq: tag and content + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Seq { len: Some(2) }, + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::Unit, + Token::SeqEnd, + ], + ); + + // Seq: tag (as string) and content + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Seq { len: None }, + Token::Str("Unit"), // tag + Token::Unit, // content + Token::SeqEnd, + ], + ); + + // Seq: tag (as borrowed string) and content + assert_de_tokens( + &AdjacentlyTagged::Unit::, + &[ + Token::Seq { len: None }, + Token::BorrowedStr("Unit"), // tag + Token::Unit, // content + Token::SeqEnd, + ], + ); + } +} + +mod newtype { + use super::*; + + #[test] + fn map_tag_only() { + // optional newtype with no content field + assert_de_tokens( + &AdjacentlyTagged::Newtype::>(None), + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 1, + }, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Newtype", + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn map_tag_content() { + let value = AdjacentlyTagged::Newtype::(1); + + // Map: tag + content + assert_tokens( + &value, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Newtype", + }, + Token::Str("c"), + Token::U8(1), + Token::StructEnd, + ], + ); + + // Map: content + tag + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("c"), + Token::U8(1), + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Newtype", + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn seq() { + let value = AdjacentlyTagged::Newtype::(1); + + // Seq: tag and content + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Newtype", + }, + Token::U8(1), + Token::SeqEnd, + ], + ); + + // Seq: tag (as string) and content + assert_de_tokens( + &value, + &[ + Token::Seq { len: None }, + Token::Str("Newtype"), // tag + Token::U8(1), // content + Token::SeqEnd, + ], + ); + + // Seq: tag (as borrowed string) and content + assert_de_tokens( + &value, + &[ + Token::Seq { len: None }, + Token::BorrowedStr("Newtype"), // tag + Token::U8(1), // content + Token::SeqEnd, + ], + ); + } +} + +#[test] +fn newtype_with_newtype() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct NewtypeStruct(u32); + + assert_de_tokens( + &AdjacentlyTagged::Newtype(NewtypeStruct(5)), + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("c"), + Token::NewtypeStruct { + name: "NewtypeStruct", + }, + Token::U32(5), + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Newtype", + }, + Token::StructEnd, + ], + ); +} + +mod tuple { + use super::*; + + #[test] + fn map() { + let value = AdjacentlyTagged::Tuple::(1, 1); + + // Map: tag + content + assert_tokens( + &value, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Tuple", + }, + Token::Str("c"), + Token::Tuple { len: 2 }, + Token::U8(1), + Token::U8(1), + Token::TupleEnd, + Token::StructEnd, + ], + ); + + // Map: content + tag + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("c"), + Token::Tuple { len: 2 }, + Token::U8(1), + Token::U8(1), + Token::TupleEnd, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Tuple", + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn seq() { + let value = AdjacentlyTagged::Tuple::(1, 1); + + // Seq: tag + content + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Tuple", + }, + Token::Tuple { len: 2 }, + Token::U8(1), + Token::U8(1), + Token::TupleEnd, + Token::SeqEnd, + ], + ); + } +} + +mod struct_ { + use super::*; + + #[test] + fn map() { + let value = AdjacentlyTagged::Struct:: { f: 1 }; + + // Map: tag + content + assert_tokens( + &value, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Struct", + }, + Token::Str("c"), + Token::Struct { + name: "Struct", + len: 1, + }, + Token::Str("f"), + Token::U8(1), + Token::StructEnd, + Token::StructEnd, + ], + ); + + // Map: content + tag + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("c"), + Token::Struct { + name: "Struct", + len: 1, + }, + Token::Str("f"), + Token::U8(1), + Token::StructEnd, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Struct", + }, + Token::StructEnd, + ], + ); + } + + #[test] + fn seq() { + let value = AdjacentlyTagged::Struct:: { f: 1 }; + + // Seq: tag + content + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Struct", + }, + Token::Struct { + name: "Struct", + len: 1, + }, + Token::Str("f"), + Token::U8(1), + Token::StructEnd, + Token::SeqEnd, + ], + ); + } +} + +#[test] +fn struct_with_flatten() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + #[serde(tag = "t", content = "c")] + enum Data { + A { + a: i32, + #[serde(flatten)] + flat: Flat, + }, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Flat { + b: i32, + } + + let data = Data::A { + a: 0, + flat: Flat { b: 0 }, + }; + + assert_tokens( + &data, + &[ + Token::Struct { + name: "Data", + len: 2, + }, + Token::Str("t"), + Token::UnitVariant { + name: "Data", + variant: "A", + }, + Token::Str("c"), + Token::Map { len: None }, + Token::Str("a"), + Token::I32(0), + Token::Str("b"), + Token::I32(0), + Token::MapEnd, + Token::StructEnd, + ], + ); +} + +#[test] +fn expecting_message() { + #[derive(Deserialize)] + #[serde(tag = "tag", content = "content")] + #[serde(expecting = "something strange...")] + enum Enum { + AdjacentlyTagged, + } + + assert_de_tokens_error::( + &[Token::Str("AdjacentlyTagged")], + r#"invalid type: string "AdjacentlyTagged", expected something strange..."#, + ); + + assert_de_tokens_error::( + &[Token::Map { len: None }, Token::Unit], + r#"invalid type: unit value, expected "tag", "content", or other ignored fields"#, + ); + + // Check that #[serde(expecting = "...")] doesn't affect variant identifier error message + assert_de_tokens_error::( + &[Token::Map { len: None }, Token::Str("tag"), Token::Unit], + "invalid type: unit value, expected variant of enum Enum", + ); +} + +#[test] +fn partially_untagged() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + #[serde(tag = "t", content = "c")] + enum Data { + A(u32), + B, + #[serde(untagged)] + Var(u32), + } + + let data = Data::A(7); + + assert_de_tokens( + &data, + &[ + Token::Map { len: None }, + Token::Str("t"), + Token::Str("A"), + Token::Str("c"), + Token::U32(7), + Token::MapEnd, + ], + ); + + let data = Data::Var(42); + + assert_de_tokens(&data, &[Token::U32(42)]); + + // TODO test error output +} + +#[test] +fn deny_unknown_fields() { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(tag = "t", content = "c", deny_unknown_fields)] + enum AdjacentlyTagged { + Unit, + } + + assert_de_tokens( + &AdjacentlyTagged::Unit, + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::Str("c"), + Token::Unit, + Token::StructEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("t"), + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::Str("c"), + Token::Unit, + Token::Str("h"), + ], + r#"invalid value: string "h", expected "t" or "c""#, + ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("h"), + ], + r#"invalid value: string "h", expected "t" or "c""#, + ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Str("c"), + Token::Unit, + Token::Str("h"), + ], + r#"invalid value: string "h", expected "t" or "c""#, + ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::U64(0), // tag field + Token::UnitVariant { + name: "AdjacentlyTagged", + variant: "Unit", + }, + Token::U64(3), + ], + r#"invalid value: integer `3`, expected "t" or "c""#, + ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "AdjacentlyTagged", + len: 2, + }, + Token::Bytes(b"c"), + Token::Unit, + Token::Bytes(b"h"), + ], + r#"invalid value: byte array, expected "t" or "c""#, + ); +} diff --git a/test_suite/tests/test_enum_internally_tagged.rs b/test_suite/tests/test_enum_internally_tagged.rs new file mode 100644 index 000000000..b4d428c4d --- /dev/null +++ b/test_suite/tests/test_enum_internally_tagged.rs @@ -0,0 +1,1478 @@ +#![deny(trivial_numeric_casts)] +#![allow( + clippy::derive_partial_eq_without_eq, + clippy::enum_variant_names, + clippy::redundant_field_names, + clippy::too_many_lines +)] + +mod bytes; + +use serde_derive::{Deserialize, Serialize}; +use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token}; +use std::collections::BTreeMap; +use std::iter::FromIterator; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct Unit; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct Newtype(BTreeMap); + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct Struct { + f: u8, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +enum Enum { + Unit, + Newtype(u8), + Tuple(u8, u8), + Struct { f: u8 }, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(tag = "tag")] +enum InternallyTagged { + Unit, + NewtypeUnit(()), + NewtypeUnitStruct(Unit), + NewtypeNewtype(Newtype), + NewtypeMap(BTreeMap), + NewtypeStruct(Struct), + NewtypeEnum(Enum), + Struct { a: u8 }, + StructEnum { enum_: Enum }, +} + +#[test] +fn unit() { + assert_tokens( + &InternallyTagged::Unit, + &[ + Token::Struct { + name: "InternallyTagged", + len: 1, + }, + Token::Str("tag"), + Token::Str("Unit"), + Token::StructEnd, + ], + ); + assert_de_tokens( + &InternallyTagged::Unit, + &[ + Token::Struct { + name: "InternallyTagged", + len: 1, + }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("Unit"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &InternallyTagged::Unit, + &[ + Token::Map { len: Some(1) }, + Token::Str("tag"), + Token::Str("Unit"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &InternallyTagged::Unit, + &[ + Token::Map { len: Some(1) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("Unit"), + Token::MapEnd, + ], + ); + + assert_de_tokens( + &InternallyTagged::Unit, + &[ + Token::Seq { len: Some(1) }, + Token::Str("Unit"), // tag + Token::SeqEnd, + ], + ); + assert_de_tokens( + &InternallyTagged::Unit, + &[ + Token::Seq { len: Some(1) }, + Token::BorrowedStr("Unit"), // tag + Token::SeqEnd, + ], + ); +} + +#[test] +fn newtype_unit() { + let value = InternallyTagged::NewtypeUnit(()); + + assert_tokens( + &value, + &[ + Token::Map { len: Some(1) }, + Token::Str("tag"), + Token::Str("NewtypeUnit"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(1) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeUnit"), + Token::MapEnd, + ], + ); + + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 1, + }, + Token::Str("tag"), + Token::Str("NewtypeUnit"), + Token::StructEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 1, + }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeUnit"), + Token::StructEnd, + ], + ); +} + +#[test] +fn newtype_unit_struct() { + let value = InternallyTagged::NewtypeUnitStruct(Unit); + + assert_tokens( + &value, + &[ + Token::Map { len: Some(1) }, + Token::Str("tag"), + Token::Str("NewtypeUnitStruct"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(1) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeUnitStruct"), + Token::MapEnd, + ], + ); + + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 1, + }, + Token::Str("tag"), + Token::Str("NewtypeUnitStruct"), + Token::StructEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 1, + }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeUnitStruct"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(1) }, + Token::Str("NewtypeUnitStruct"), // tag + Token::SeqEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(1) }, + Token::BorrowedStr("NewtypeUnitStruct"), // tag + Token::SeqEnd, + ], + ); +} + +#[test] +fn newtype_newtype() { + assert_tokens( + &InternallyTagged::NewtypeNewtype(Newtype(BTreeMap::new())), + &[ + Token::Map { len: Some(1) }, + Token::Str("tag"), + Token::Str("NewtypeNewtype"), + Token::MapEnd, + ], + ); +} + +#[test] +fn newtype_map() { + let value = InternallyTagged::NewtypeMap(BTreeMap::new()); + + // Special case: empty map + assert_tokens( + &value, + &[ + Token::Map { len: Some(1) }, + Token::Str("tag"), + Token::Str("NewtypeMap"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(1) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeMap"), + Token::MapEnd, + ], + ); + + let value = InternallyTagged::NewtypeMap(BTreeMap::from_iter([( + "field".to_string(), + "value".to_string(), + )])); + + // Special case: tag field ("tag") is the first field + assert_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("NewtypeMap"), + Token::Str("field"), + Token::Str("value"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeMap"), + Token::BorrowedStr("field"), + Token::BorrowedStr("value"), + Token::MapEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("field"), + Token::Str("value"), + Token::Str("tag"), + Token::Str("NewtypeMap"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("field"), + Token::BorrowedStr("value"), + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeMap"), + Token::MapEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::Seq { len: Some(2) }, + Token::Str("NewtypeMap"), // tag + Token::Map { len: Some(0) }, + Token::MapEnd, + Token::SeqEnd, + ], + "invalid type: sequence, expected a map", + ); +} + +#[test] +fn newtype_struct() { + let value = InternallyTagged::NewtypeStruct(Struct { f: 6 }); + + // Special case: tag field ("tag") is the first field + assert_tokens( + &value, + &[ + Token::Struct { + name: "Struct", + len: 2, + }, + Token::Str("tag"), + Token::Str("NewtypeStruct"), + Token::Str("f"), + Token::U8(6), + Token::StructEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "Struct", + len: 2, + }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeStruct"), + Token::BorrowedStr("f"), + Token::U8(6), + Token::StructEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "Struct", + len: 2, + }, + Token::Str("f"), + Token::U8(6), + Token::Str("tag"), + Token::Str("NewtypeStruct"), + Token::StructEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "Struct", + len: 2, + }, + Token::BorrowedStr("f"), + Token::U8(6), + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeStruct"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::Str("NewtypeStruct"), // tag + Token::U8(6), + Token::SeqEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::BorrowedStr("NewtypeStruct"), // tag + Token::U8(6), + Token::SeqEnd, + ], + ); +} + +mod newtype_enum { + use super::*; + + #[test] + fn unit() { + let value = InternallyTagged::NewtypeEnum(Enum::Unit); + + // Special case: tag field ("tag") is the first field + assert_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::Str("Unit"), + Token::Unit, + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::BorrowedStr("Unit"), + Token::Unit, + Token::MapEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("Unit"), + Token::Unit, + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("Unit"), + Token::Unit, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::MapEnd, + ], + ); + } + + #[test] + fn newtype() { + let value = InternallyTagged::NewtypeEnum(Enum::Newtype(1)); + + // Special case: tag field ("tag") is the first field + assert_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::Str("Newtype"), + Token::U8(1), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::BorrowedStr("Newtype"), + Token::U8(1), + Token::MapEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("Newtype"), + Token::U8(1), + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("Newtype"), + Token::U8(1), + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::MapEnd, + ], + ); + } + + #[test] + fn tuple() { + let value = InternallyTagged::NewtypeEnum(Enum::Tuple(1, 1)); + + // Special case: tag field ("tag") is the first field + assert_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::Str("Tuple"), + Token::TupleStruct { + name: "Tuple", + len: 2, + }, + Token::U8(1), + Token::U8(1), + Token::TupleStructEnd, + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::BorrowedStr("Tuple"), + Token::TupleStruct { + name: "Tuple", + len: 2, + }, + Token::U8(1), + Token::U8(1), + Token::TupleStructEnd, + Token::MapEnd, + ], + ); + + // Special case: tag field ("tag") is not the first field + // Reaches crate::private::de::content::VariantDeserializer::tuple_variant + // Content::Seq case + // via ContentDeserializer::deserialize_enum + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("Tuple"), + Token::TupleStruct { + name: "Tuple", + len: 2, + }, + Token::U8(1), + Token::U8(1), + Token::TupleStructEnd, + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("Tuple"), + Token::TupleStruct { + name: "Tuple", + len: 2, + }, + Token::U8(1), + Token::U8(1), + Token::TupleStructEnd, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::MapEnd, + ], + ); + } + + #[test] + fn struct_() { + let value = InternallyTagged::NewtypeEnum(Enum::Struct { f: 1 }); + + // Special case: tag field ("tag") is the first field + assert_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::Str("Struct"), + Token::Struct { + name: "Struct", + len: 1, + }, + Token::Str("f"), + Token::U8(1), + Token::StructEnd, + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::BorrowedStr("Struct"), + Token::Struct { + name: "Struct", + len: 1, + }, + Token::BorrowedStr("f"), + Token::U8(1), + Token::StructEnd, + Token::MapEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + // Reaches crate::private::de::content::VariantDeserializer::struct_variant + // Content::Map case + // via ContentDeserializer::deserialize_enum + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("Struct"), + Token::Struct { + name: "Struct", + len: 1, + }, + Token::Str("f"), + Token::U8(1), + Token::StructEnd, + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("Struct"), + Token::Struct { + name: "Struct", + len: 1, + }, + Token::BorrowedStr("f"), + Token::U8(1), + Token::StructEnd, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::MapEnd, + ], + ); + + // Special case: tag field ("tag") is the first field + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::Str("Struct"), + Token::Seq { len: Some(1) }, + Token::U8(1), // f + Token::SeqEnd, + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::BorrowedStr("Struct"), + Token::Seq { len: Some(1) }, + Token::U8(1), // f + Token::SeqEnd, + Token::MapEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + // Reaches crate::private::de::content::VariantDeserializer::struct_variant + // Content::Seq case + // via ContentDeserializer::deserialize_enum + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("Struct"), + Token::Seq { len: Some(1) }, + Token::U8(1), // f + Token::SeqEnd, + Token::Str("tag"), + Token::Str("NewtypeEnum"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("Struct"), + Token::Seq { len: Some(1) }, + Token::U8(1), // f + Token::SeqEnd, + Token::BorrowedStr("tag"), + Token::BorrowedStr("NewtypeEnum"), + Token::MapEnd, + ], + ); + } +} + +#[test] +fn struct_() { + let value = InternallyTagged::Struct { a: 1 }; + + // Special case: tag field ("tag") is the first field + assert_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::Str("tag"), + Token::Str("Struct"), + Token::Str("a"), + Token::U8(1), + Token::StructEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("Struct"), + Token::BorrowedStr("a"), + Token::U8(1), + Token::StructEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::Str("a"), + Token::U8(1), + Token::Str("tag"), + Token::Str("Struct"), + Token::StructEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::BorrowedStr("a"), + Token::U8(1), + Token::BorrowedStr("tag"), + Token::BorrowedStr("Struct"), + Token::StructEnd, + ], + ); + + // Special case: tag field ("tag") is the first field + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("Struct"), + Token::Str("a"), + Token::U8(1), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("Struct"), + Token::BorrowedStr("a"), + Token::U8(1), + Token::MapEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("a"), + Token::U8(1), + Token::Str("tag"), + Token::Str("Struct"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("a"), + Token::U8(1), + Token::BorrowedStr("tag"), + Token::BorrowedStr("Struct"), + Token::MapEnd, + ], + ); + + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::Str("Struct"), // tag + Token::U8(1), + Token::SeqEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::BorrowedStr("Struct"), // tag + Token::U8(1), + Token::SeqEnd, + ], + ); +} + +mod struct_enum { + use super::*; + + #[test] + fn unit() { + assert_de_tokens( + &Enum::Unit, + &[ + Token::Enum { name: "Enum" }, + Token::BorrowedStr("Unit"), + Token::Unit, + ], + ); + + let value = InternallyTagged::StructEnum { enum_: Enum::Unit }; + + // Special case: tag field ("tag") is the first field + assert_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::Str("tag"), + Token::Str("StructEnum"), + Token::Str("enum_"), + Token::Enum { name: "Enum" }, + Token::Str("Unit"), + Token::Unit, + Token::StructEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("StructEnum"), + Token::BorrowedStr("enum_"), + Token::Enum { name: "Enum" }, + Token::BorrowedStr("Unit"), + Token::Unit, + Token::StructEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::Str("enum_"), + Token::Enum { name: "Enum" }, + Token::Str("Unit"), + Token::Unit, + Token::Str("tag"), + Token::Str("StructEnum"), + Token::StructEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::BorrowedStr("enum_"), + Token::Enum { name: "Enum" }, + Token::BorrowedStr("Unit"), + Token::Unit, + Token::BorrowedStr("tag"), + Token::BorrowedStr("StructEnum"), + Token::StructEnd, + ], + ); + + // Special case: tag field ("tag") is the first field + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("StructEnum"), + Token::Str("enum_"), + Token::Enum { name: "Enum" }, + Token::Str("Unit"), + Token::Unit, + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("StructEnum"), + Token::BorrowedStr("enum_"), + Token::Enum { name: "Enum" }, + Token::BorrowedStr("Unit"), + Token::Unit, + Token::MapEnd, + ], + ); + + // General case: tag field ("tag") is not the first field + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::Str("enum_"), + Token::Enum { name: "Enum" }, + Token::Str("Unit"), + Token::Unit, + Token::Str("tag"), + Token::Str("StructEnum"), + Token::MapEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Map { len: Some(2) }, + Token::BorrowedStr("enum_"), + Token::Enum { name: "Enum" }, + Token::BorrowedStr("Unit"), + Token::Unit, + Token::BorrowedStr("tag"), + Token::BorrowedStr("StructEnum"), + Token::MapEnd, + ], + ); + + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::Str("StructEnum"), // tag + Token::Enum { name: "Enum" }, // enum_ + Token::Str("Unit"), + Token::Unit, + Token::SeqEnd, + ], + ); + assert_de_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::BorrowedStr("StructEnum"), // tag + Token::Enum { name: "Enum" }, // enum_ + Token::BorrowedStr("Unit"), + Token::Unit, + Token::SeqEnd, + ], + ); + } +} + +#[test] +fn wrong_tag() { + assert_de_tokens_error::( + &[Token::Map { len: Some(0) }, Token::MapEnd], + "missing field `tag`", + ); + + assert_de_tokens_error::( + &[ + Token::Map { len: Some(1) }, + Token::Str("tag"), + Token::Str("Z"), + Token::MapEnd, + ], + "unknown variant `Z`, expected one of \ + `Unit`, \ + `NewtypeUnit`, \ + `NewtypeUnitStruct`, \ + `NewtypeNewtype`, \ + `NewtypeMap`, \ + `NewtypeStruct`, \ + `NewtypeEnum`, \ + `Struct`, \ + `StructEnum`", + ); +} + +#[test] +fn untagged_variant() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(tag = "tag")] + enum InternallyTagged { + Tagged { + a: u8, + }, + #[serde(untagged)] + Untagged { + tag: String, + b: u8, + }, + } + + assert_de_tokens( + &InternallyTagged::Tagged { a: 1 }, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("Tagged"), + Token::Str("a"), + Token::U8(1), + Token::MapEnd, + ], + ); + + assert_tokens( + &InternallyTagged::Tagged { a: 1 }, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::Str("tag"), + Token::Str("Tagged"), + Token::Str("a"), + Token::U8(1), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &InternallyTagged::Untagged { + tag: "Foo".to_owned(), + b: 2, + }, + &[ + Token::Map { len: Some(2) }, + Token::Str("tag"), + Token::Str("Foo"), + Token::Str("b"), + Token::U8(2), + Token::MapEnd, + ], + ); + + assert_tokens( + &InternallyTagged::Untagged { + tag: "Foo".to_owned(), + b: 2, + }, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::Str("tag"), + Token::Str("Foo"), + Token::Str("b"), + Token::U8(2), + Token::StructEnd, + ], + ); + + assert_tokens( + &InternallyTagged::Untagged { + tag: "Tagged".to_owned(), + b: 2, + }, + &[ + Token::Struct { + name: "InternallyTagged", + len: 2, + }, + Token::Str("tag"), + Token::Str("Tagged"), + Token::Str("b"), + Token::U8(2), + Token::StructEnd, + ], + ); +} + +mod string_and_bytes { + use super::*; + + #[derive(Debug, PartialEq, Deserialize)] + #[serde(tag = "tag")] + enum InternallyTagged { + String { + string: String, + }, + Bytes { + #[serde(with = "bytes")] + bytes: Vec, + }, + } + + #[test] + fn string_from_string() { + assert_de_tokens( + &InternallyTagged::String { + string: "\0".to_owned(), + }, + &[ + Token::Struct { + name: "String", + len: 2, + }, + Token::Str("tag"), + Token::Str("String"), + Token::Str("string"), + Token::Str("\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &InternallyTagged::String { + string: "\0".to_owned(), + }, + &[ + Token::Struct { + name: "String", + len: 2, + }, + Token::Str("tag"), + Token::Str("String"), + Token::Str("string"), + Token::String("\0"), + Token::StructEnd, + ], + ); + } + + #[test] + fn string_from_bytes() { + assert_de_tokens( + &InternallyTagged::String { + string: "\0".to_owned(), + }, + &[ + Token::Struct { + name: "String", + len: 2, + }, + Token::Str("tag"), + Token::Str("String"), + Token::Str("string"), + Token::Bytes(b"\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &InternallyTagged::String { + string: "\0".to_owned(), + }, + &[ + Token::Struct { + name: "String", + len: 2, + }, + Token::Str("tag"), + Token::Str("String"), + Token::Str("string"), + Token::ByteBuf(b"\0"), + Token::StructEnd, + ], + ); + } + + #[test] + fn bytes_from_string() { + assert_de_tokens( + &InternallyTagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Bytes", + len: 2, + }, + Token::Str("tag"), + Token::Str("Bytes"), + Token::Str("bytes"), + Token::Str("\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &InternallyTagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Bytes", + len: 2, + }, + Token::Str("tag"), + Token::Str("Bytes"), + Token::Str("bytes"), + Token::String("\0"), + Token::StructEnd, + ], + ); + } + + #[test] + fn bytes_from_bytes() { + assert_de_tokens( + &InternallyTagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Bytes", + len: 2, + }, + Token::Str("tag"), + Token::Str("Bytes"), + Token::Str("bytes"), + Token::Bytes(b"\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &InternallyTagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Bytes", + len: 2, + }, + Token::Str("tag"), + Token::Str("Bytes"), + Token::Str("bytes"), + Token::ByteBuf(b"\0"), + Token::StructEnd, + ], + ); + } + + #[test] + fn bytes_from_seq() { + assert_de_tokens( + &InternallyTagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Bytes", + len: 2, + }, + Token::Str("tag"), + Token::Str("Bytes"), + Token::Str("bytes"), + Token::Seq { len: Some(1) }, + Token::U8(0), + Token::SeqEnd, + Token::StructEnd, + ], + ); + } +} + +#[test] +fn borrow() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(tag = "tag")] + enum Input<'a> { + Package { name: &'a str }, + } + + assert_tokens( + &Input::Package { name: "borrowed" }, + &[ + Token::Struct { + name: "Input", + len: 2, + }, + Token::BorrowedStr("tag"), + Token::BorrowedStr("Package"), + Token::BorrowedStr("name"), + Token::BorrowedStr("borrowed"), + Token::StructEnd, + ], + ); +} + +#[test] +fn with_skipped_conflict() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(tag = "tag")] + enum Data { + A, + #[serde(skip)] + #[allow(dead_code)] + B { + t: String, + }, + C { + #[serde(default, skip)] + t: String, + }, + } + + let data = Data::C { t: String::new() }; + + assert_tokens( + &data, + &[ + Token::Struct { + name: "Data", + len: 1, + }, + Token::Str("tag"), + Token::Str("C"), + Token::StructEnd, + ], + ); +} + +#[test] +fn containing_flatten() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(tag = "tag")] + enum Data { + A { + a: i32, + #[serde(flatten)] + flat: Flat, + }, + } + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Flat { + b: i32, + } + + let data = Data::A { + a: 0, + flat: Flat { b: 0 }, + }; + + assert_tokens( + &data, + &[ + Token::Map { len: None }, + Token::Str("tag"), + Token::Str("A"), + Token::Str("a"), + Token::I32(0), + Token::Str("b"), + Token::I32(0), + Token::MapEnd, + ], + ); +} + +#[test] +fn unit_variant_with_unknown_fields() { + let value = InternallyTagged::Unit; + + assert_de_tokens( + &value, + &[ + Token::Map { len: None }, + Token::Str("tag"), + Token::Str("Unit"), + Token::Str("b"), + Token::I32(0), + Token::MapEnd, + ], + ); + + // Unknown elements are not allowed in sequences + assert_de_tokens_error::( + &[ + Token::Seq { len: None }, + Token::Str("Unit"), // tag + Token::I32(0), + Token::SeqEnd, + ], + "invalid length 1, expected 0 elements in sequence", + ); +} + +#[test] +fn expecting_message() { + #[derive(Deserialize)] + #[serde(tag = "tag")] + #[serde(expecting = "something strange...")] + enum Enum { + InternallyTagged, + } + + assert_de_tokens_error::( + &[Token::Str("InternallyTagged")], + r#"invalid type: string "InternallyTagged", expected something strange..."#, + ); + + // Check that #[serde(expecting = "...")] doesn't affect variant identifier error message + assert_de_tokens_error::( + &[Token::Map { len: None }, Token::Str("tag"), Token::Unit], + "invalid type: unit value, expected variant identifier", + ); +} diff --git a/test_suite/tests/test_enum_untagged.rs b/test_suite/tests/test_enum_untagged.rs new file mode 100644 index 000000000..48f50c9c2 --- /dev/null +++ b/test_suite/tests/test_enum_untagged.rs @@ -0,0 +1,583 @@ +#![deny(trivial_numeric_casts)] +#![allow( + clippy::derive_partial_eq_without_eq, + clippy::enum_variant_names, + clippy::redundant_field_names, + clippy::too_many_lines +)] + +mod bytes; + +use serde_derive::{Deserialize, Serialize}; +use serde_test::{assert_de_tokens, assert_de_tokens_error, assert_tokens, Token}; +use std::collections::BTreeMap; + +#[test] +fn complex() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(untagged)] + enum Untagged { + A { a: u8 }, + B { b: u8 }, + C, + D(u8), + E(String), + F(u8, u8), + } + + assert_tokens( + &Untagged::A { a: 1 }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("a"), + Token::U8(1), + Token::StructEnd, + ], + ); + + assert_tokens( + &Untagged::B { b: 2 }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("b"), + Token::U8(2), + Token::StructEnd, + ], + ); + + // Serializes to unit, deserializes from either depending on format's + // preference. + assert_tokens(&Untagged::C, &[Token::Unit]); + assert_de_tokens(&Untagged::C, &[Token::None]); + + assert_tokens(&Untagged::D(4), &[Token::U8(4)]); + assert_tokens(&Untagged::E("e".to_owned()), &[Token::Str("e")]); + + assert_tokens( + &Untagged::F(1, 2), + &[ + Token::Tuple { len: 2 }, + Token::U8(1), + Token::U8(2), + Token::TupleEnd, + ], + ); + + assert_de_tokens_error::( + &[Token::Tuple { len: 1 }, Token::U8(1), Token::TupleEnd], + "data did not match any variant of untagged enum Untagged", + ); + + assert_de_tokens_error::( + &[ + Token::Tuple { len: 3 }, + Token::U8(1), + Token::U8(2), + Token::U8(3), + Token::TupleEnd, + ], + "data did not match any variant of untagged enum Untagged", + ); +} + +#[test] +fn newtype_unit_and_empty_map() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Unit; + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(untagged)] + enum Message { + Unit(Unit), + Map(BTreeMap), + } + + assert_tokens( + &Message::Map(BTreeMap::new()), + &[Token::Map { len: Some(0) }, Token::MapEnd], + ); +} + +// Reaches crate::private::de::content::ContentRefDeserializer::deserialize_newtype_struct +#[test] +fn newtype_struct() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct NewtypeStruct(u32); + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(untagged)] + enum E { + Newtype(NewtypeStruct), + Null, + } + + let value = E::Newtype(NewtypeStruct(5)); + + // Content::Newtype case + assert_tokens( + &value, + &[ + Token::NewtypeStruct { + name: "NewtypeStruct", + }, + Token::U32(5), + ], + ); + + // _ case + assert_de_tokens(&value, &[Token::U32(5)]); +} + +mod newtype_enum { + use super::*; + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(untagged)] + enum Outer { + Inner(Inner), + } + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + enum Inner { + Unit, + Newtype(u8), + Tuple0(), + Tuple2(u8, u8), + Struct { f: u8 }, + EmptyStruct {}, + } + + // Reaches crate::private::de::content::VariantRefDeserializer::unit_variant + #[test] + fn unit() { + assert_tokens( + &Outer::Inner(Inner::Unit), + &[Token::UnitVariant { + name: "Inner", + variant: "Unit", + }], + ); + } + + // Reaches crate::private::de::content::VariantRefDeserializer::newtype_variant_seed + #[test] + fn newtype() { + assert_tokens( + &Outer::Inner(Inner::Newtype(1)), + &[ + Token::NewtypeVariant { + name: "Inner", + variant: "Newtype", + }, + Token::U8(1), + ], + ); + } + + // Reaches crate::private::de::content::VariantRefDeserializer::tuple_variant + #[test] + fn tuple0() { + assert_tokens( + &Outer::Inner(Inner::Tuple0()), + &[ + Token::TupleVariant { + name: "Inner", + variant: "Tuple0", + len: 0, + }, + Token::TupleVariantEnd, + ], + ); + } + + // Reaches crate::private::de::content::VariantRefDeserializer::tuple_variant + #[test] + fn tuple2() { + assert_tokens( + &Outer::Inner(Inner::Tuple2(1, 1)), + &[ + Token::TupleVariant { + name: "Inner", + variant: "Tuple2", + len: 2, + }, + Token::U8(1), + Token::U8(1), + Token::TupleVariantEnd, + ], + ); + } + + // Reaches crate::private::de::content::VariantRefDeserializer::struct_variant + // Content::Map case + #[test] + fn struct_from_map() { + assert_tokens( + &Outer::Inner(Inner::Struct { f: 1 }), + &[ + Token::StructVariant { + name: "Inner", + variant: "Struct", + len: 1, + }, + Token::Str("f"), + Token::U8(1), + Token::StructVariantEnd, + ], + ); + } + + // Reaches crate::private::de::content::VariantRefDeserializer::struct_variant + // Content::Seq case + #[test] + fn struct_from_seq() { + assert_de_tokens( + &Outer::Inner(Inner::Struct { f: 1 }), + &[ + Token::Map { len: Some(1) }, + // tag + Token::Str("Struct"), + // content + Token::Seq { len: Some(1) }, + Token::U8(1), + Token::SeqEnd, + Token::MapEnd, + ], + ); + } + + // Reaches crate::private::de::content::VariantRefDeserializer::struct_variant + // Content::Map case + // Special case - empty map + #[test] + fn empty_struct_from_map() { + assert_de_tokens( + &Outer::Inner(Inner::EmptyStruct {}), + &[ + Token::Map { len: Some(1) }, + // tag + Token::Str("EmptyStruct"), + // content + Token::Map { len: Some(0) }, + Token::MapEnd, + Token::MapEnd, + ], + ); + } + + // Reaches crate::private::de::content::VariantRefDeserializer::struct_variant + // Content::Seq case + // Special case - empty seq + #[test] + fn empty_struct_from_seq() { + assert_de_tokens( + &Outer::Inner(Inner::EmptyStruct {}), + &[ + Token::Map { len: Some(1) }, + // tag + Token::Str("EmptyStruct"), + // content + Token::Seq { len: Some(0) }, + Token::SeqEnd, + Token::MapEnd, + ], + ); + } +} + +// Reaches crate::private::de::content::ContentRefDeserializer::deserialize_option +mod with_optional_field { + use super::*; + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(untagged)] + enum Enum { + Struct { optional: Option }, + Null, + } + + #[test] + fn some() { + assert_tokens( + &Enum::Struct { optional: Some(42) }, + &[ + Token::Struct { + name: "Enum", + len: 1, + }, + Token::Str("optional"), + Token::Some, + Token::U32(42), + Token::StructEnd, + ], + ); + } + + #[test] + fn some_without_marker() { + assert_de_tokens( + &Enum::Struct { optional: Some(42) }, + &[ + Token::Struct { + name: "Enum", + len: 1, + }, + Token::Str("optional"), + Token::U32(42), + Token::StructEnd, + ], + ); + } + + #[test] + fn none() { + assert_tokens( + &Enum::Struct { optional: None }, + &[ + Token::Struct { + name: "Enum", + len: 1, + }, + Token::Str("optional"), + Token::None, + Token::StructEnd, + ], + ); + } + + #[test] + fn unit() { + assert_de_tokens( + &Enum::Struct { optional: None }, + &[ + Token::Map { len: None }, + Token::Str("optional"), + Token::Unit, + Token::MapEnd, + ], + ); + } +} + +#[test] +fn string_and_bytes() { + #[derive(Debug, PartialEq, Deserialize)] + #[serde(untagged)] + enum Untagged { + String { + string: String, + }, + Bytes { + #[serde(with = "bytes")] + bytes: Vec, + }, + } + + assert_de_tokens( + &Untagged::String { + string: "\0".to_owned(), + }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("string"), + Token::Str("\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &Untagged::String { + string: "\0".to_owned(), + }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("string"), + Token::String("\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &Untagged::String { + string: "\0".to_owned(), + }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("string"), + Token::Bytes(b"\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &Untagged::String { + string: "\0".to_owned(), + }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("string"), + Token::ByteBuf(b"\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &Untagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("bytes"), + Token::Str("\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &Untagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("bytes"), + Token::String("\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &Untagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("bytes"), + Token::Bytes(b"\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &Untagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("bytes"), + Token::ByteBuf(b"\0"), + Token::StructEnd, + ], + ); + + assert_de_tokens( + &Untagged::Bytes { bytes: vec![0] }, + &[ + Token::Struct { + name: "Untagged", + len: 1, + }, + Token::Str("bytes"), + Token::Seq { len: Some(1) }, + Token::U8(0), + Token::SeqEnd, + Token::StructEnd, + ], + ); +} + +#[test] +fn contains_flatten() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + #[serde(untagged)] + enum Data { + A { + a: i32, + #[serde(flatten)] + flat: Flat, + }, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Flat { + b: i32, + } + + let data = Data::A { + a: 0, + flat: Flat { b: 0 }, + }; + + assert_tokens( + &data, + &[ + Token::Map { len: None }, + Token::Str("a"), + Token::I32(0), + Token::Str("b"), + Token::I32(0), + Token::MapEnd, + ], + ); +} + +#[test] +fn contains_flatten_with_integer_key() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(untagged)] + pub enum Untagged { + Variant { + #[serde(flatten)] + map: BTreeMap, + }, + } + + assert_tokens( + &Untagged::Variant { + map: { + let mut map = BTreeMap::new(); + map.insert(100, "BTreeMap".to_owned()); + map + }, + }, + &[ + Token::Map { len: None }, + Token::U64(100), + Token::Str("BTreeMap"), + Token::MapEnd, + ], + ); +} + +#[test] +fn expecting_message() { + #[derive(Deserialize)] + #[serde(untagged)] + #[serde(expecting = "something strange...")] + enum Enum { + Untagged, + } + + assert_de_tokens_error::(&[Token::Str("Untagged")], "something strange..."); +} diff --git a/test_suite/tests/test_gen.rs b/test_suite/tests/test_gen.rs index 4aaf06a8b..ed9dc725e 100644 --- a/test_suite/tests/test_gen.rs +++ b/test_suite/tests/test_gen.rs @@ -4,6 +4,7 @@ #![deny(warnings)] #![allow( + confusable_idents, unknown_lints, mixed_script_confusables, clippy::derive_partial_eq_without_eq, @@ -19,6 +20,7 @@ clippy::trivially_copy_pass_by_ref, clippy::type_repetition_in_bounds )] +#![deny(clippy::collection_is_never_read)] use serde::de::{Deserialize, DeserializeOwned, Deserializer}; use serde::ser::{Serialize, Serializer}; @@ -287,60 +289,60 @@ fn test_gen() { assert::(); #[derive(Serialize, Deserialize)] - struct NonAsciiIdents { + pub struct NonAsciiIdents { σ: f64, } #[derive(Serialize, Deserialize)] - struct EmptyBraced {} + pub struct EmptyBraced {} #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] - struct EmptyBracedDenyUnknown {} + pub struct EmptyBracedDenyUnknown {} #[derive(Serialize, Deserialize)] - struct BracedSkipAll { + pub struct BracedSkipAll { #[serde(skip_deserializing)] f: u8, } #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] - struct BracedSkipAllDenyUnknown { + pub struct BracedSkipAllDenyUnknown { #[serde(skip_deserializing)] f: u8, } #[derive(Serialize, Deserialize)] - struct EmptyTuple(); + pub struct EmptyTuple(); #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] - struct EmptyTupleDenyUnknown(); + pub struct EmptyTupleDenyUnknown(); #[derive(Serialize, Deserialize)] - struct TupleSkipAll(#[serde(skip_deserializing)] u8); + pub struct TupleSkipAll(#[serde(skip_deserializing)] u8); #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] - struct TupleSkipAllDenyUnknown(#[serde(skip_deserializing)] u8); + pub struct TupleSkipAllDenyUnknown(#[serde(skip_deserializing)] u8); #[derive(Serialize, Deserialize)] - enum EmptyEnum {} + pub enum EmptyEnum {} #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] - enum EmptyEnumDenyUnknown {} + pub enum EmptyEnumDenyUnknown {} #[derive(Serialize, Deserialize)] - enum EnumSkipAll { + pub enum EnumSkipAll { #[serde(skip_deserializing)] #[allow(dead_code)] Variant, } #[derive(Serialize, Deserialize)] - enum EmptyVariants { + pub enum EmptyVariants { Braced {}, Tuple(), BracedSkip { @@ -352,7 +354,7 @@ fn test_gen() { #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] - enum EmptyVariantsDenyUnknown { + pub enum EmptyVariantsDenyUnknown { Braced {}, Tuple(), BracedSkip { @@ -364,21 +366,21 @@ fn test_gen() { #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] - struct UnitDenyUnknown; + pub struct UnitDenyUnknown; #[derive(Serialize, Deserialize)] - struct EmptyArray { + pub struct EmptyArray { empty: [X; 0], } - enum Or { + pub enum Or { A(A), B(B), } #[derive(Serialize, Deserialize)] #[serde(untagged, remote = "Or")] - enum OrDef { + pub enum OrDef { A(A), B(B), } @@ -390,7 +392,7 @@ fn test_gen() { struct StrDef<'a>(&'a str); #[derive(Serialize, Deserialize)] - struct Remote<'a> { + pub struct Remote<'a> { #[serde(with = "OrDef")] or: Or, #[serde(borrow, with = "StrDef")] @@ -398,7 +400,7 @@ fn test_gen() { } #[derive(Serialize, Deserialize)] - enum BorrowVariant<'a> { + pub enum BorrowVariant<'a> { #[serde(borrow, with = "StrDef")] S(Str<'a>), } @@ -415,14 +417,14 @@ fn test_gen() { // This would not work if SDef::serialize / deserialize are private. #[derive(Serialize, Deserialize)] - struct RemoteVisibility { + pub struct RemoteVisibility { #[serde(with = "vis::SDef")] s: vis::S, } #[derive(Serialize, Deserialize)] #[serde(remote = "Self")] - struct RemoteSelf; + pub struct RemoteSelf; #[derive(Serialize, Deserialize)] enum ExternallyTaggedVariantWith { @@ -545,27 +547,46 @@ fn test_gen() { } assert::(); + #[derive(Serialize, Deserialize)] + pub struct Flatten { + #[serde(flatten)] + t: T, + } + #[derive(Serialize, Deserialize)] #[serde(deny_unknown_fields)] - struct FlattenDenyUnknown { + pub struct FlattenDenyUnknown { #[serde(flatten)] t: T, } #[derive(Serialize, Deserialize)] - struct StaticStrStruct<'a> { + pub struct SkipDeserializing { + #[serde(skip_deserializing)] + flat: T, + } + + #[derive(Serialize, Deserialize)] + #[serde(deny_unknown_fields)] + pub struct SkipDeserializingDenyUnknown { + #[serde(skip_deserializing)] + flat: T, + } + + #[derive(Serialize, Deserialize)] + pub struct StaticStrStruct<'a> { a: &'a str, b: &'static str, } #[derive(Serialize, Deserialize)] - struct StaticStrTupleStruct<'a>(&'a str, &'static str); + pub struct StaticStrTupleStruct<'a>(&'a str, &'static str); #[derive(Serialize, Deserialize)] - struct StaticStrNewtypeStruct(&'static str); + pub struct StaticStrNewtypeStruct(&'static str); #[derive(Serialize, Deserialize)] - enum StaticStrEnum<'a> { + pub enum StaticStrEnum<'a> { Struct { a: &'a str, b: &'static str }, Tuple(&'a str, &'static str), Newtype(&'static str), @@ -639,6 +660,7 @@ fn test_gen() { use serde_derive::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] + #[allow(dead_code)] struct Restricted { pub(super) a: usize, pub(in super::inner) b: usize, @@ -648,7 +670,7 @@ fn test_gen() { #[derive(Deserialize)] #[serde(tag = "t", content = "c")] - enum AdjacentlyTaggedVoid {} + pub enum AdjacentlyTaggedVoid {} #[derive(Serialize, Deserialize)] enum SkippedVariant { @@ -661,14 +683,13 @@ fn test_gen() { assert::>(); #[derive(Deserialize)] - struct ImplicitlyBorrowedOption<'a> { - #[allow(dead_code)] + pub struct ImplicitlyBorrowedOption<'a> { option: std::option::Option<&'a str>, } #[derive(Serialize, Deserialize)] #[serde(untagged)] - enum UntaggedNewtypeVariantWith { + pub enum UntaggedNewtypeVariantWith { Newtype( #[serde(serialize_with = "ser_x")] #[serde(deserialize_with = "de_x")] @@ -678,7 +699,7 @@ fn test_gen() { #[derive(Serialize, Deserialize)] #[serde(transparent)] - struct TransparentWith { + pub struct TransparentWith { #[serde(serialize_with = "ser_x")] #[serde(deserialize_with = "de_x")] x: X, @@ -700,35 +721,46 @@ fn test_gen() { } #[derive(Deserialize)] - struct RelObject<'a> { - #[allow(dead_code)] + pub struct RelObject<'a> { ty: &'a str, - #[allow(dead_code)] id: String, } #[derive(Serialize, Deserialize)] - struct FlattenSkipSerializing { + pub struct FlattenSkipSerializing { #[serde(flatten, skip_serializing)] #[allow(dead_code)] flat: T, } #[derive(Serialize, Deserialize)] - struct FlattenSkipSerializingIf { + pub struct FlattenSkipSerializingIf { #[serde(flatten, skip_serializing_if = "StdOption::is_none")] flat: StdOption, } #[derive(Serialize, Deserialize)] - struct FlattenSkipDeserializing { + pub struct FlattenSkipDeserializing { #[serde(flatten, skip_deserializing)] flat: T, } + #[derive(Serialize, Deserialize)] + #[serde(untagged)] + pub enum Inner { + Builder { + s: T, + #[serde(flatten)] + o: T, + }, + Default { + s: T, + }, + } + // https://github.com/serde-rs/serde/issues/1804 #[derive(Serialize, Deserialize)] - enum Message { + pub enum Message { #[serde(skip)] #[allow(dead_code)] String(String), @@ -747,8 +779,7 @@ fn test_gen() { macro_rules! deriving { ($field:ty) => { #[derive(Deserialize)] - struct MacroRules<'a> { - #[allow(dead_code)] + pub struct MacroRules<'a> { field: $field, } }; @@ -763,22 +794,20 @@ fn test_gen() { } #[derive(Deserialize)] - struct BorrowLifetimeInsideMacro<'a> { + pub struct BorrowLifetimeInsideMacro<'a> { #[serde(borrow = "'a")] - #[allow(dead_code)] - f: mac!(Cow<'a, str>), + pub f: mac!(Cow<'a, str>), } #[derive(Serialize)] - #[allow(dead_code)] - struct Struct { + pub struct Struct { #[serde(serialize_with = "vec_first_element")] - vec: Vec, + pub vec: Vec, } #[derive(Deserialize)] #[serde(bound(deserialize = "[&'de str; N]: Copy"))] - struct GenericUnitStruct; + pub struct GenericUnitStruct; } ////////////////////////////////////////////////////////////////////////// @@ -865,7 +894,7 @@ where #[derive(Debug, PartialEq, Deserialize)] #[serde(tag = "tag")] -enum InternallyTagged { +pub enum InternallyTagged { #[serde(deserialize_with = "deserialize_generic")] Unit, diff --git a/test_suite/tests/test_macros.rs b/test_suite/tests/test_macros.rs index 7bd7a94e7..6b2fc4c5d 100644 --- a/test_suite/tests/test_macros.rs +++ b/test_suite/tests/test_macros.rs @@ -6,13 +6,8 @@ clippy::too_many_lines )] -mod bytes; - use serde_derive::{Deserialize, Serialize}; -use serde_test::{ - assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, Token, -}; -use std::collections::BTreeMap; +use serde_test::{assert_de_tokens, assert_ser_tokens, assert_tokens, Token}; use std::marker::PhantomData; // That tests that the derived Serialize implementation doesn't trigger @@ -433,54 +428,6 @@ fn test_generic_newtype_struct() { ); } -#[test] -fn test_untagged_newtype_struct() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(untagged)] - enum E { - Newtype(GenericNewTypeStruct), - Null, - } - - assert_tokens( - &E::Newtype(GenericNewTypeStruct(5u32)), - &[ - Token::NewtypeStruct { - name: "GenericNewTypeStruct", - }, - Token::U32(5), - ], - ); -} - -#[test] -fn test_adjacently_tagged_newtype_struct() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "t", content = "c")] - enum E { - Newtype(GenericNewTypeStruct), - Null, - } - - assert_de_tokens( - &E::Newtype(GenericNewTypeStruct(5u32)), - &[ - Token::Struct { name: "E", len: 2 }, - Token::Str("c"), - Token::NewtypeStruct { - name: "GenericNewTypeStruct", - }, - Token::U32(5), - Token::Str("t"), - Token::UnitVariant { - name: "E", - variant: "Newtype", - }, - Token::StructEnd, - ], - ); -} - #[test] fn test_generic_tuple_struct() { assert_tokens( @@ -606,1528 +553,247 @@ fn test_enum_state_field() { } #[test] -fn test_untagged_enum() { +fn test_internally_tagged_struct() { #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(untagged)] - enum Untagged { - A { a: u8 }, - B { b: u8 }, - C, - D(u8), - E(String), - F(u8, u8), + #[serde(tag = "type")] + pub struct Struct { + a: u8, } assert_tokens( - &Untagged::A { a: 1 }, + &Struct { a: 1 }, &[ Token::Struct { - name: "Untagged", - len: 1, + name: "Struct", + len: 2, }, + Token::Str("type"), + Token::Str("Struct"), Token::Str("a"), Token::U8(1), Token::StructEnd, ], ); - assert_tokens( - &Untagged::B { b: 2 }, + assert_de_tokens( + &Struct { a: 1 }, &[ Token::Struct { - name: "Untagged", + name: "Struct", len: 1, }, - Token::Str("b"), - Token::U8(2), + Token::Str("a"), + Token::U8(1), Token::StructEnd, ], ); +} - // Serializes to unit, deserializes from either depending on format's - // preference. - assert_tokens(&Untagged::C, &[Token::Unit]); - assert_de_tokens(&Untagged::C, &[Token::None]); - - assert_tokens(&Untagged::D(4), &[Token::U8(4)]); - assert_tokens(&Untagged::E("e".to_owned()), &[Token::Str("e")]); +#[test] +fn test_internally_tagged_braced_struct_with_zero_fields() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + #[serde(tag = "type")] + struct S {} assert_tokens( - &Untagged::F(1, 2), - &[ - Token::Tuple { len: 2 }, - Token::U8(1), - Token::U8(2), - Token::TupleEnd, - ], - ); - - assert_de_tokens_error::( - &[Token::Tuple { len: 1 }, Token::U8(1), Token::TupleEnd], - "data did not match any variant of untagged enum Untagged", - ); - - assert_de_tokens_error::( + &S {}, &[ - Token::Tuple { len: 3 }, - Token::U8(1), - Token::U8(2), - Token::U8(3), - Token::TupleEnd, + Token::Struct { name: "S", len: 1 }, + Token::Str("type"), + Token::Str("S"), + Token::StructEnd, ], - "data did not match any variant of untagged enum Untagged", ); } #[test] -fn test_internally_tagged_enum() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Newtype(BTreeMap); - +fn test_internally_tagged_struct_with_flattened_field() { #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Struct { - f: u8, + #[serde(tag = "tag_struct")] + pub struct Struct { + #[serde(flatten)] + pub flat: Enum, } #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "type")] - enum InternallyTagged { - A { a: u8 }, - B, - C(BTreeMap), - D(Newtype), - E(Struct), + #[serde(tag = "tag_enum", content = "content")] + pub enum Enum { + A(u64), } assert_tokens( - &InternallyTagged::A { a: 1 }, + &Struct { flat: Enum::A(0) }, &[ - Token::Struct { - name: "InternallyTagged", - len: 2, + Token::Map { len: None }, + Token::Str("tag_struct"), + Token::Str("Struct"), + Token::Str("tag_enum"), + Token::UnitVariant { + name: "Enum", + variant: "A", }, - Token::Str("type"), - Token::Str("A"), - Token::Str("a"), - Token::U8(1), - Token::StructEnd, + Token::Str("content"), + Token::U64(0), + Token::MapEnd, ], ); assert_de_tokens( - &InternallyTagged::A { a: 1 }, + &Struct { flat: Enum::A(0) }, &[ - Token::Seq { len: Some(2) }, + Token::Map { len: None }, + Token::Str("tag_enum"), Token::Str("A"), - Token::U8(1), - Token::SeqEnd, + Token::Str("content"), + Token::U64(0), + Token::MapEnd, ], ); +} + +#[test] +fn test_rename_all() { + #[derive(Serialize, Deserialize, Debug, PartialEq)] + #[serde(rename_all = "snake_case")] + enum E { + #[serde(rename_all = "camelCase")] + Serialize { + serialize: bool, + serialize_seq: bool, + }, + #[serde(rename_all = "kebab-case")] + SerializeSeq { + serialize: bool, + serialize_seq: bool, + }, + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] + SerializeMap { + serialize: bool, + serialize_seq: bool, + }, + } + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + #[serde(rename_all = "PascalCase")] + struct S { + serialize: bool, + serialize_seq: bool, + } + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + #[serde(rename_all = "SCREAMING-KEBAB-CASE")] + struct ScreamingKebab { + serialize: bool, + serialize_seq: bool, + } assert_tokens( - &InternallyTagged::B, + &E::Serialize { + serialize: true, + serialize_seq: true, + }, &[ - Token::Struct { - name: "InternallyTagged", - len: 1, + Token::StructVariant { + name: "E", + variant: "serialize", + len: 2, }, - Token::Str("type"), - Token::Str("B"), - Token::StructEnd, + Token::Str("serialize"), + Token::Bool(true), + Token::Str("serializeSeq"), + Token::Bool(true), + Token::StructVariantEnd, ], ); - assert_de_tokens( - &InternallyTagged::B, - &[Token::Seq { len: Some(1) }, Token::Str("B"), Token::SeqEnd], - ); - assert_tokens( - &InternallyTagged::C(BTreeMap::new()), + &E::SerializeSeq { + serialize: true, + serialize_seq: true, + }, &[ - Token::Map { len: Some(1) }, - Token::Str("type"), - Token::Str("C"), - Token::MapEnd, + Token::StructVariant { + name: "E", + variant: "serialize_seq", + len: 2, + }, + Token::Str("serialize"), + Token::Bool(true), + Token::Str("serialize-seq"), + Token::Bool(true), + Token::StructVariantEnd, ], ); - assert_de_tokens_error::( + assert_tokens( + &E::SerializeMap { + serialize: true, + serialize_seq: true, + }, &[ - Token::Seq { len: Some(2) }, - Token::Str("C"), - Token::Map { len: Some(0) }, - Token::MapEnd, - Token::SeqEnd, + Token::StructVariant { + name: "E", + variant: "serialize_map", + len: 2, + }, + Token::Str("SERIALIZE"), + Token::Bool(true), + Token::Str("SERIALIZE_SEQ"), + Token::Bool(true), + Token::StructVariantEnd, ], - "invalid type: sequence, expected a map", ); assert_tokens( - &InternallyTagged::D(Newtype(BTreeMap::new())), + &S { + serialize: true, + serialize_seq: true, + }, &[ - Token::Map { len: Some(1) }, - Token::Str("type"), - Token::Str("D"), - Token::MapEnd, + Token::Struct { name: "S", len: 2 }, + Token::Str("Serialize"), + Token::Bool(true), + Token::Str("SerializeSeq"), + Token::Bool(true), + Token::StructEnd, ], ); assert_tokens( - &InternallyTagged::E(Struct { f: 6 }), + &ScreamingKebab { + serialize: true, + serialize_seq: true, + }, &[ Token::Struct { - name: "Struct", + name: "ScreamingKebab", len: 2, }, - Token::Str("type"), - Token::Str("E"), - Token::Str("f"), - Token::U8(6), + Token::Str("SERIALIZE"), + Token::Bool(true), + Token::Str("SERIALIZE-SEQ"), + Token::Bool(true), Token::StructEnd, ], ); - - assert_de_tokens( - &InternallyTagged::E(Struct { f: 6 }), - &[ - Token::Seq { len: Some(2) }, - Token::Str("E"), - Token::U8(6), - Token::SeqEnd, - ], - ); - - assert_de_tokens_error::( - &[Token::Map { len: Some(0) }, Token::MapEnd], - "missing field `type`", - ); - - assert_de_tokens_error::( - &[ - Token::Map { len: Some(1) }, - Token::Str("type"), - Token::Str("Z"), - Token::MapEnd, - ], - "unknown variant `Z`, expected one of `A`, `B`, `C`, `D`, `E`", - ); } #[test] -fn test_internally_tagged_enum_with_untagged_variant() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "kind")] - enum InternallyTagged { - Tagged { - a: u8, +fn test_rename_all_fields() { + #[derive(Serialize, Deserialize, Debug, PartialEq)] + #[serde(rename_all_fields = "kebab-case")] + enum E { + V1, + V2(bool), + V3 { + a_field: bool, + another_field: bool, + #[serde(rename = "last-field")] + yet_another_field: bool, }, - #[serde(untagged)] - Untagged { - kind: String, - b: u8, - }, - } - - assert_de_tokens( - &InternallyTagged::Tagged { a: 1 }, - &[ - Token::Map { len: Some(2) }, - Token::Str("kind"), - Token::Str("Tagged"), - Token::Str("a"), - Token::U8(1), - Token::MapEnd, - ], - ); - - assert_tokens( - &InternallyTagged::Tagged { a: 1 }, - &[ - Token::Struct { - name: "InternallyTagged", - len: 2, - }, - Token::Str("kind"), - Token::Str("Tagged"), - Token::Str("a"), - Token::U8(1), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &InternallyTagged::Untagged { - kind: "Foo".to_owned(), - b: 2, - }, - &[ - Token::Map { len: Some(2) }, - Token::Str("kind"), - Token::Str("Foo"), - Token::Str("b"), - Token::U8(2), - Token::MapEnd, - ], - ); - - assert_tokens( - &InternallyTagged::Untagged { - kind: "Foo".to_owned(), - b: 2, - }, - &[ - Token::Struct { - name: "InternallyTagged", - len: 2, - }, - Token::Str("kind"), - Token::Str("Foo"), - Token::Str("b"), - Token::U8(2), - Token::StructEnd, - ], - ); - - assert_tokens( - &InternallyTagged::Untagged { - kind: "Tagged".to_owned(), - b: 2, - }, - &[ - Token::Struct { - name: "InternallyTagged", - len: 2, - }, - Token::Str("kind"), - Token::Str("Tagged"), - Token::Str("b"), - Token::U8(2), - Token::StructEnd, - ], - ); -} - -#[test] -fn test_internally_tagged_bytes() { - #[derive(Debug, PartialEq, Deserialize)] - #[serde(tag = "type")] - enum InternallyTagged { - String { - string: String, - }, - Bytes { - #[serde(with = "bytes")] - bytes: Vec, - }, - } - - assert_de_tokens( - &InternallyTagged::String { - string: "\0".to_owned(), - }, - &[ - Token::Struct { - name: "String", - len: 2, - }, - Token::Str("type"), - Token::Str("String"), - Token::Str("string"), - Token::Str("\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &InternallyTagged::String { - string: "\0".to_owned(), - }, - &[ - Token::Struct { - name: "String", - len: 2, - }, - Token::Str("type"), - Token::Str("String"), - Token::Str("string"), - Token::String("\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &InternallyTagged::String { - string: "\0".to_owned(), - }, - &[ - Token::Struct { - name: "String", - len: 2, - }, - Token::Str("type"), - Token::Str("String"), - Token::Str("string"), - Token::Bytes(b"\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &InternallyTagged::String { - string: "\0".to_owned(), - }, - &[ - Token::Struct { - name: "String", - len: 2, - }, - Token::Str("type"), - Token::Str("String"), - Token::Str("string"), - Token::ByteBuf(b"\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &InternallyTagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Bytes", - len: 2, - }, - Token::Str("type"), - Token::Str("Bytes"), - Token::Str("bytes"), - Token::Str("\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &InternallyTagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Bytes", - len: 2, - }, - Token::Str("type"), - Token::Str("Bytes"), - Token::Str("bytes"), - Token::String("\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &InternallyTagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Bytes", - len: 2, - }, - Token::Str("type"), - Token::Str("Bytes"), - Token::Str("bytes"), - Token::Bytes(b"\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &InternallyTagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Bytes", - len: 2, - }, - Token::Str("type"), - Token::Str("Bytes"), - Token::Str("bytes"), - Token::ByteBuf(b"\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &InternallyTagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Bytes", - len: 2, - }, - Token::Str("type"), - Token::Str("Bytes"), - Token::Str("bytes"), - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd, - Token::StructEnd, - ], - ); -} - -#[test] -fn test_internally_tagged_struct_variant_containing_unit_variant() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - pub enum Level { - Info, - } - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "action")] - pub enum Message { - Log { level: Level }, - } - - assert_de_tokens( - &Level::Info, - &[ - Token::Enum { name: "Level" }, - Token::BorrowedStr("Info"), - Token::Unit, - ], - ); - - assert_de_tokens( - &Message::Log { level: Level::Info }, - &[ - Token::Struct { - name: "Message", - len: 2, - }, - Token::Str("action"), - Token::Str("Log"), - Token::Str("level"), - Token::Enum { name: "Level" }, - Token::BorrowedStr("Info"), - Token::Unit, - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Message::Log { level: Level::Info }, - &[ - Token::Map { len: Some(2) }, - Token::Str("action"), - Token::Str("Log"), - Token::Str("level"), - Token::Enum { name: "Level" }, - Token::BorrowedStr("Info"), - Token::Unit, - Token::MapEnd, - ], - ); - - assert_de_tokens( - &Message::Log { level: Level::Info }, - &[ - Token::Seq { len: Some(2) }, - Token::Str("Log"), - Token::Enum { name: "Level" }, - Token::BorrowedStr("Info"), - Token::Unit, - Token::SeqEnd, - ], - ); -} - -#[test] -fn test_internally_tagged_borrow() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "type")] - pub enum Input<'a> { - Package { name: &'a str }, - } - - assert_tokens( - &Input::Package { name: "borrowed" }, - &[ - Token::Struct { - name: "Input", - len: 2, - }, - Token::BorrowedStr("type"), - Token::BorrowedStr("Package"), - Token::BorrowedStr("name"), - Token::BorrowedStr("borrowed"), - Token::StructEnd, - ], - ); -} - -#[test] -fn test_adjacently_tagged_enum() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "t", content = "c")] - enum AdjacentlyTagged { - Unit, - Newtype(T), - Tuple(u8, u8), - Struct { f: u8 }, - } - - // unit with no content - assert_ser_tokens( - &AdjacentlyTagged::Unit::, - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 1, - }, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Unit", - }, - Token::StructEnd, - ], - ); - - // unit with no content - assert_de_tokens( - &AdjacentlyTagged::Unit::, - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Unit", - }, - Token::StructEnd, - ], - ); - - // unit with tag first - assert_de_tokens( - &AdjacentlyTagged::Unit::, - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Unit", - }, - Token::Str("c"), - Token::Unit, - Token::StructEnd, - ], - ); - - // unit with content first - assert_de_tokens( - &AdjacentlyTagged::Unit::, - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("c"), - Token::Unit, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Unit", - }, - Token::StructEnd, - ], - ); - - // unit with excess content (f, g, h) - assert_de_tokens( - &AdjacentlyTagged::Unit::, - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("f"), - Token::Unit, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Unit", - }, - Token::Str("g"), - Token::Unit, - Token::Str("c"), - Token::Unit, - Token::Str("h"), - Token::Unit, - Token::StructEnd, - ], - ); - - // newtype with tag first - assert_tokens( - &AdjacentlyTagged::Newtype::(1), - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Newtype", - }, - Token::Str("c"), - Token::U8(1), - Token::StructEnd, - ], - ); - - // newtype with content first - assert_de_tokens( - &AdjacentlyTagged::Newtype::(1), - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("c"), - Token::U8(1), - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Newtype", - }, - Token::StructEnd, - ], - ); - - // optional newtype with no content field - assert_de_tokens( - &AdjacentlyTagged::Newtype::>(None), - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 1, - }, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Newtype", - }, - Token::StructEnd, - ], - ); - - // tuple with tag first - assert_tokens( - &AdjacentlyTagged::Tuple::(1, 1), - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Tuple", - }, - Token::Str("c"), - Token::Tuple { len: 2 }, - Token::U8(1), - Token::U8(1), - Token::TupleEnd, - Token::StructEnd, - ], - ); - - // tuple with content first - assert_de_tokens( - &AdjacentlyTagged::Tuple::(1, 1), - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("c"), - Token::Tuple { len: 2 }, - Token::U8(1), - Token::U8(1), - Token::TupleEnd, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Tuple", - }, - Token::StructEnd, - ], - ); - - // struct with tag first - assert_tokens( - &AdjacentlyTagged::Struct:: { f: 1 }, - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Struct", - }, - Token::Str("c"), - Token::Struct { - name: "Struct", - len: 1, - }, - Token::Str("f"), - Token::U8(1), - Token::StructEnd, - Token::StructEnd, - ], - ); - - // struct with content first - assert_de_tokens( - &AdjacentlyTagged::Struct:: { f: 1 }, - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("c"), - Token::Struct { - name: "Struct", - len: 1, - }, - Token::Str("f"), - Token::U8(1), - Token::StructEnd, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Struct", - }, - Token::StructEnd, - ], - ); - - // integer field keys - assert_de_tokens( - &AdjacentlyTagged::Newtype::(1), - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::U64(1), // content field - Token::U8(1), - Token::U64(0), // tag field - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Newtype", - }, - Token::StructEnd, - ], - ); - - // byte-array field keys - assert_de_tokens( - &AdjacentlyTagged::Newtype::(1), - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Bytes(b"c"), - Token::U8(1), - Token::Bytes(b"t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Newtype", - }, - Token::StructEnd, - ], - ); -} - -#[test] -fn test_adjacently_tagged_enum_deny_unknown_fields() { - #[derive(Debug, PartialEq, Deserialize)] - #[serde(tag = "t", content = "c", deny_unknown_fields)] - enum AdjacentlyTagged { - Unit, - } - - assert_de_tokens( - &AdjacentlyTagged::Unit, - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Unit", - }, - Token::Str("c"), - Token::Unit, - Token::StructEnd, - ], - ); - - assert_de_tokens_error::( - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("t"), - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Unit", - }, - Token::Str("c"), - Token::Unit, - Token::Str("h"), - ], - r#"invalid value: string "h", expected "t" or "c""#, - ); - - assert_de_tokens_error::( - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("h"), - ], - r#"invalid value: string "h", expected "t" or "c""#, - ); - - assert_de_tokens_error::( - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Str("c"), - Token::Unit, - Token::Str("h"), - ], - r#"invalid value: string "h", expected "t" or "c""#, - ); - - assert_de_tokens_error::( - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::U64(0), // tag field - Token::UnitVariant { - name: "AdjacentlyTagged", - variant: "Unit", - }, - Token::U64(3), - ], - r#"invalid value: integer `3`, expected "t" or "c""#, - ); - - assert_de_tokens_error::( - &[ - Token::Struct { - name: "AdjacentlyTagged", - len: 2, - }, - Token::Bytes(b"c"), - Token::Unit, - Token::Bytes(b"h"), - ], - r#"invalid value: byte array, expected "t" or "c""#, - ); -} - -#[test] -fn test_enum_in_internally_tagged_enum() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "type")] - enum Outer { - Inner(Inner), - } - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - enum Inner { - Unit, - Newtype(u8), - Tuple(u8, u8), - Struct { f: u8 }, - } - - assert_tokens( - &Outer::Inner(Inner::Unit), - &[ - Token::Map { len: Some(2) }, - Token::Str("type"), - Token::Str("Inner"), - Token::Str("Unit"), - Token::Unit, - Token::MapEnd, - ], - ); - - assert_tokens( - &Outer::Inner(Inner::Newtype(1)), - &[ - Token::Map { len: Some(2) }, - Token::Str("type"), - Token::Str("Inner"), - Token::Str("Newtype"), - Token::U8(1), - Token::MapEnd, - ], - ); - - // Reaches crate::private::de::content::VariantDeserializer::tuple_variant - // Content::Seq case - // via ContentDeserializer::deserialize_enum - assert_tokens( - &Outer::Inner(Inner::Tuple(1, 1)), - &[ - Token::Map { len: Some(2) }, - Token::Str("type"), - Token::Str("Inner"), - Token::Str("Tuple"), - Token::TupleStruct { - name: "Tuple", - len: 2, - }, - Token::U8(1), - Token::U8(1), - Token::TupleStructEnd, - Token::MapEnd, - ], - ); - - // Reaches crate::private::de::content::VariantDeserializer::struct_variant - // Content::Map case - // via ContentDeserializer::deserialize_enum - assert_tokens( - &Outer::Inner(Inner::Struct { f: 1 }), - &[ - Token::Map { len: Some(2) }, - Token::Str("type"), - Token::Str("Inner"), - Token::Str("Struct"), - Token::Struct { - name: "Struct", - len: 1, - }, - Token::Str("f"), - Token::U8(1), - Token::StructEnd, - Token::MapEnd, - ], - ); - - // Reaches crate::private::de::content::VariantDeserializer::struct_variant - // Content::Seq case - // via ContentDeserializer::deserialize_enum - assert_de_tokens( - &Outer::Inner(Inner::Struct { f: 1 }), - &[ - Token::Map { len: Some(2) }, - Token::Str("type"), - Token::Str("Inner"), - Token::Str("Struct"), - Token::Seq { len: Some(1) }, - Token::U8(1), // f - Token::SeqEnd, - Token::MapEnd, - ], - ); -} - -#[test] -fn test_internally_tagged_struct() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "type")] - pub struct Struct { - a: u8, - } - - assert_tokens( - &Struct { a: 1 }, - &[ - Token::Struct { - name: "Struct", - len: 2, - }, - Token::Str("type"), - Token::Str("Struct"), - Token::Str("a"), - Token::U8(1), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Struct { a: 1 }, - &[ - Token::Struct { - name: "Struct", - len: 1, - }, - Token::Str("a"), - Token::U8(1), - Token::StructEnd, - ], - ); -} - -#[test] -fn test_internally_tagged_braced_struct_with_zero_fields() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "type")] - struct S {} - - assert_tokens( - &S {}, - &[ - Token::Struct { name: "S", len: 1 }, - Token::Str("type"), - Token::Str("S"), - Token::StructEnd, - ], - ); -} - -#[test] -fn test_internally_tagged_struct_with_flattened_field() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "tag_struct")] - pub struct Struct { - #[serde(flatten)] - pub flat: Enum, - } - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "tag_enum", content = "content")] - pub enum Enum { - A(u64), - } - - assert_tokens( - &Struct { flat: Enum::A(0) }, - &[ - Token::Map { len: None }, - Token::Str("tag_struct"), - Token::Str("Struct"), - Token::Str("tag_enum"), - Token::UnitVariant { - name: "Enum", - variant: "A", - }, - Token::Str("content"), - Token::U64(0), - Token::MapEnd, - ], - ); - - assert_de_tokens( - &Struct { flat: Enum::A(0) }, - &[ - Token::Map { len: None }, - Token::Str("tag_enum"), - Token::Str("A"), - Token::Str("content"), - Token::U64(0), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_untagged_enum_with_flattened_integer_key() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(untagged)] - pub enum Untagged { - Variant { - #[serde(flatten)] - map: BTreeMap, - }, - } - - assert_tokens( - &Untagged::Variant { - map: { - let mut map = BTreeMap::new(); - map.insert(100, "BTreeMap".to_owned()); - map - }, - }, - &[ - Token::Map { len: None }, - Token::U64(100), - Token::Str("BTreeMap"), - Token::MapEnd, - ], - ); -} - -#[test] -fn test_enum_in_untagged_enum() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(untagged)] - enum Outer { - Inner(Inner), - } - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - enum Inner { - Unit, - Newtype(u8), - Tuple(u8, u8), - Struct { f: u8 }, - } - - assert_tokens( - &Outer::Inner(Inner::Unit), - &[Token::UnitVariant { - name: "Inner", - variant: "Unit", - }], - ); - - assert_tokens( - &Outer::Inner(Inner::Newtype(1)), - &[ - Token::NewtypeVariant { - name: "Inner", - variant: "Newtype", - }, - Token::U8(1), - ], - ); - - assert_tokens( - &Outer::Inner(Inner::Tuple(1, 1)), - &[ - Token::TupleVariant { - name: "Inner", - variant: "Tuple", - len: 2, - }, - Token::U8(1), - Token::U8(1), - Token::TupleVariantEnd, - ], - ); - - assert_tokens( - &Outer::Inner(Inner::Struct { f: 1 }), - &[ - Token::StructVariant { - name: "Inner", - variant: "Struct", - len: 1, - }, - Token::Str("f"), - Token::U8(1), - Token::StructVariantEnd, - ], - ); -} - -#[test] -fn test_untagged_bytes() { - #[derive(Debug, PartialEq, Deserialize)] - #[serde(untagged)] - enum Untagged { - String { - string: String, - }, - Bytes { - #[serde(with = "bytes")] - bytes: Vec, - }, - } - - assert_de_tokens( - &Untagged::String { - string: "\0".to_owned(), - }, - &[ - Token::Struct { - name: "Untagged", - len: 1, - }, - Token::Str("string"), - Token::Str("\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Untagged::String { - string: "\0".to_owned(), - }, - &[ - Token::Struct { - name: "Untagged", - len: 1, - }, - Token::Str("string"), - Token::String("\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Untagged::String { - string: "\0".to_owned(), - }, - &[ - Token::Struct { - name: "Untagged", - len: 1, - }, - Token::Str("string"), - Token::Bytes(b"\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Untagged::String { - string: "\0".to_owned(), - }, - &[ - Token::Struct { - name: "Untagged", - len: 1, - }, - Token::Str("string"), - Token::ByteBuf(b"\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Untagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Untagged", - len: 1, - }, - Token::Str("bytes"), - Token::Str("\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Untagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Untagged", - len: 1, - }, - Token::Str("bytes"), - Token::String("\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Untagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Untagged", - len: 1, - }, - Token::Str("bytes"), - Token::Bytes(b"\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Untagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Untagged", - len: 1, - }, - Token::Str("bytes"), - Token::ByteBuf(b"\0"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Untagged::Bytes { bytes: vec![0] }, - &[ - Token::Struct { - name: "Untagged", - len: 1, - }, - Token::Str("bytes"), - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd, - Token::StructEnd, - ], - ); -} - -#[test] -fn test_rename_all() { - #[derive(Serialize, Deserialize, Debug, PartialEq)] - #[serde(rename_all = "snake_case")] - enum E { - #[serde(rename_all = "camelCase")] - Serialize { - serialize: bool, - serialize_seq: bool, - }, - #[serde(rename_all = "kebab-case")] - SerializeSeq { - serialize: bool, - serialize_seq: bool, - }, - #[serde(rename_all = "SCREAMING_SNAKE_CASE")] - SerializeMap { - serialize: bool, - serialize_seq: bool, - }, - } - - #[derive(Serialize, Deserialize, Debug, PartialEq)] - #[serde(rename_all = "PascalCase")] - struct S { - serialize: bool, - serialize_seq: bool, - } - - #[derive(Serialize, Deserialize, Debug, PartialEq)] - #[serde(rename_all = "SCREAMING-KEBAB-CASE")] - struct ScreamingKebab { - serialize: bool, - serialize_seq: bool, - } - - assert_tokens( - &E::Serialize { - serialize: true, - serialize_seq: true, - }, - &[ - Token::StructVariant { - name: "E", - variant: "serialize", - len: 2, - }, - Token::Str("serialize"), - Token::Bool(true), - Token::Str("serializeSeq"), - Token::Bool(true), - Token::StructVariantEnd, - ], - ); - - assert_tokens( - &E::SerializeSeq { - serialize: true, - serialize_seq: true, - }, - &[ - Token::StructVariant { - name: "E", - variant: "serialize_seq", - len: 2, - }, - Token::Str("serialize"), - Token::Bool(true), - Token::Str("serialize-seq"), - Token::Bool(true), - Token::StructVariantEnd, - ], - ); - - assert_tokens( - &E::SerializeMap { - serialize: true, - serialize_seq: true, - }, - &[ - Token::StructVariant { - name: "E", - variant: "serialize_map", - len: 2, - }, - Token::Str("SERIALIZE"), - Token::Bool(true), - Token::Str("SERIALIZE_SEQ"), - Token::Bool(true), - Token::StructVariantEnd, - ], - ); - - assert_tokens( - &S { - serialize: true, - serialize_seq: true, - }, - &[ - Token::Struct { name: "S", len: 2 }, - Token::Str("Serialize"), - Token::Bool(true), - Token::Str("SerializeSeq"), - Token::Bool(true), - Token::StructEnd, - ], - ); - - assert_tokens( - &ScreamingKebab { - serialize: true, - serialize_seq: true, - }, - &[ - Token::Struct { - name: "ScreamingKebab", - len: 2, - }, - Token::Str("SERIALIZE"), - Token::Bool(true), - Token::Str("SERIALIZE-SEQ"), - Token::Bool(true), - Token::StructEnd, - ], - ); -} - -#[test] -fn test_rename_all_fields() { - #[derive(Serialize, Deserialize, Debug, PartialEq)] - #[serde(rename_all_fields = "kebab-case")] - enum E { - V1, - V2(bool), - V3 { - a_field: bool, - another_field: bool, - #[serde(rename = "last-field")] - yet_another_field: bool, - }, - #[serde(rename_all = "snake_case")] - V4 { - a_field: bool, + #[serde(rename_all = "snake_case")] + V4 { + a_field: bool, }, } @@ -2168,68 +834,6 @@ fn test_rename_all_fields() { ); } -#[test] -fn test_untagged_newtype_variant_containing_unit_struct_not_map() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Unit; - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(untagged)] - enum Message { - Unit(Unit), - Map(BTreeMap), - } - - assert_tokens( - &Message::Map(BTreeMap::new()), - &[Token::Map { len: Some(0) }, Token::MapEnd], - ); -} - -#[test] -fn test_internally_tagged_newtype_variant_containing_unit_struct() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Info; - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - #[serde(tag = "topic")] - enum Message { - Info(Info), - } - - assert_tokens( - &Message::Info(Info), - &[ - Token::Map { len: Some(1) }, - Token::Str("topic"), - Token::Str("Info"), - Token::MapEnd, - ], - ); - - assert_de_tokens( - &Message::Info(Info), - &[ - Token::Struct { - name: "Message", - len: 1, - }, - Token::Str("topic"), - Token::Str("Info"), - Token::StructEnd, - ], - ); - - assert_de_tokens( - &Message::Info(Info), - &[ - Token::Seq { len: Some(1) }, - Token::Str("Info"), - Token::SeqEnd, - ], - ); -} - #[test] fn test_packed_struct_can_derive_serialize() { #[derive(Copy, Clone, Serialize)] diff --git a/test_suite/tests/test_remote.rs b/test_suite/tests/test_remote.rs index c1f152eb8..d550af427 100644 --- a/test_suite/tests/test_remote.rs +++ b/test_suite/tests/test_remote.rs @@ -7,14 +7,17 @@ mod remote { pub struct PrimitivePriv(u8); + #[allow(dead_code)] pub struct PrimitivePub(pub u8); pub struct NewtypePriv(Unit); + #[allow(dead_code)] pub struct NewtypePub(pub Unit); pub struct TuplePriv(u8, Unit); + #[allow(dead_code)] pub struct TuplePub(pub u8, pub Unit); pub struct StructPriv { @@ -22,6 +25,7 @@ mod remote { b: Unit, } + #[allow(dead_code)] pub struct StructPub { pub a: u8, pub b: Unit, @@ -86,12 +90,14 @@ mod remote { } } + #[allow(dead_code)] pub enum EnumGeneric { Variant(T), } } #[derive(Serialize, Deserialize)] +#[allow(dead_code)] struct Test { #[serde(with = "UnitDef")] unit: remote::Unit, @@ -132,6 +138,7 @@ struct Test { #[derive(Serialize, Deserialize)] #[serde(remote = "remote::Unit")] +#[allow(dead_code)] struct UnitDef; #[derive(Serialize, Deserialize)] @@ -140,6 +147,7 @@ struct PrimitivePrivDef(#[serde(getter = "remote::PrimitivePriv::get")] u8); #[derive(Serialize, Deserialize)] #[serde(remote = "remote::PrimitivePub")] +#[allow(dead_code)] struct PrimitivePubDef(u8); #[derive(Serialize, Deserialize)] @@ -148,6 +156,7 @@ struct NewtypePrivDef(#[serde(getter = "remote::NewtypePriv::get", with = "UnitD #[derive(Serialize, Deserialize)] #[serde(remote = "remote::NewtypePub")] +#[allow(dead_code)] struct NewtypePubDef(#[serde(with = "UnitDef")] remote::Unit); #[derive(Serialize, Deserialize)] @@ -159,6 +168,7 @@ struct TuplePrivDef( #[derive(Serialize, Deserialize)] #[serde(remote = "remote::TuplePub")] +#[allow(dead_code)] struct TuplePubDef(u8, #[serde(with = "UnitDef")] remote::Unit); #[derive(Serialize, Deserialize)] @@ -174,6 +184,7 @@ struct StructPrivDef { #[derive(Serialize, Deserialize)] #[serde(remote = "remote::StructPub")] +#[allow(dead_code)] struct StructPubDef { a: u8, @@ -190,17 +201,20 @@ struct StructGenericWithGetterDef { #[derive(Serialize, Deserialize)] #[serde(remote = "remote::StructGeneric")] +#[allow(dead_code)] struct StructConcrete { value: u8, } #[derive(Serialize, Deserialize)] #[serde(remote = "remote::EnumGeneric")] +#[allow(dead_code)] enum EnumConcrete { Variant(u8), } #[derive(Debug)] +#[allow(dead_code)] enum ErrorKind { NotFound, PermissionDenied, @@ -211,6 +225,7 @@ enum ErrorKind { #[derive(Serialize, Deserialize)] #[serde(remote = "ErrorKind")] #[non_exhaustive] +#[allow(dead_code)] enum ErrorKindDef { NotFound, PermissionDenied, diff --git a/test_suite/tests/test_self.rs b/test_suite/tests/test_self.rs index 516bdeaec..2813c4854 100644 --- a/test_suite/tests/test_self.rs +++ b/test_suite/tests/test_self.rs @@ -41,7 +41,7 @@ fn test_self() { } #[derive(Deserialize, Serialize)] - struct Tuple( + pub struct Tuple( Box, Box<::Assoc>, [(); Self::ASSOC], @@ -60,7 +60,7 @@ fn test_self() { } #[derive(Deserialize, Serialize)] - enum Enum { + pub enum Enum { Struct { _f1: Box, _f2: Box<::Assoc>, diff --git a/test_suite/tests/ui/on_unimplemented.rs b/test_suite/tests/ui/on_unimplemented.rs new file mode 100644 index 000000000..fab56751b --- /dev/null +++ b/test_suite/tests/ui/on_unimplemented.rs @@ -0,0 +1,23 @@ +use serde::de::Deserialize; +use serde::ser::Serialize; + +fn to_string(_: &T) -> String +where + T: Serialize, +{ + unimplemented!() +} + +fn from_str<'de, T>(_: &'de str) -> T +where + T: Deserialize<'de>, +{ + unimplemented!() +} + +struct MyStruct; + +fn main() { + to_string(&MyStruct); + let _: MyStruct = from_str(""); +} diff --git a/test_suite/tests/ui/on_unimplemented.stderr b/test_suite/tests/ui/on_unimplemented.stderr new file mode 100644 index 000000000..1b0318190 --- /dev/null +++ b/test_suite/tests/ui/on_unimplemented.stderr @@ -0,0 +1,55 @@ +error[E0277]: the trait bound `MyStruct: Serialize` is not satisfied + --> tests/ui/on_unimplemented.rs:21:15 + | +21 | to_string(&MyStruct); + | --------- ^^^^^^^^^ the trait `Serialize` is not implemented for `MyStruct` + | | + | required by a bound introduced by this call + | + = note: for local types consider adding `#[derive(serde::Serialize)]` to your `MyStruct` type + = note: for types from other crates check whether the crate offers a `serde` feature flag + = help: the following other types implement trait `Serialize`: + &'a T + &'a mut T + () + (T,) + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + (T0, T1, T2, T3, T4) + and $N others +note: required by a bound in `to_string` + --> tests/ui/on_unimplemented.rs:6:8 + | +4 | fn to_string(_: &T) -> String + | --------- required by a bound in this function +5 | where +6 | T: Serialize, + | ^^^^^^^^^ required by this bound in `to_string` + +error[E0277]: the trait bound `MyStruct: Deserialize<'_>` is not satisfied + --> tests/ui/on_unimplemented.rs:22:23 + | +22 | let _: MyStruct = from_str(""); + | ^^^^^^^^^^^^ the trait `Deserialize<'_>` is not implemented for `MyStruct` + | + = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `MyStruct` type + = note: for types from other crates check whether the crate offers a `serde` feature flag + = help: the following other types implement trait `Deserialize<'de>`: + &'a Path + &'a [u8] + &'a str + () + (T,) + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + and $N others +note: required by a bound in `from_str` + --> tests/ui/on_unimplemented.rs:13:8 + | +11 | fn from_str<'de, T>(_: &'de str) -> T + | -------- required by a bound in this function +12 | where +13 | T: Deserialize<'de>, + | ^^^^^^^^^^^^^^^^ required by this bound in `from_str`