diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23da81d40..fd0d38c6e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,13 @@ cargo install cargo-binstall then all of the other crates can be installed with `cargo binstall`. ```bash -cargo binstall just cargo-llvm-cov cargo-nextest cargo-insta cargo-hakari miniserve +cargo binstall just cargo-llvm-cov cargo-nextest cargo-insta cargo-hakari miniserve taplo +``` + +Note that for `cargo-sync-rdme` we use Troy's custom fork: + +```bash +cargo binstall cargo-sync-rdme --git https://github.com/TroyKomodo/cargo-sync-rdme.git --force -y ``` diff --git a/Cargo.lock b/Cargo.lock index e65a927cb..38719a80c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3164,6 +3164,8 @@ dependencies = [ "bytes", "bytestring", "document-features", + "indexmap", + "insta", "num-derive", "num-traits", "scuffle-bytes-util", diff --git a/Justfile b/Justfile index 7b3659fb7..58a6a28b9 100644 --- a/Justfile +++ b/Justfile @@ -7,9 +7,10 @@ export RUST_TOOLCHAIN := env_var_or_default('RUST_TOOLCHAIN', 'nightly') powerset *args: cargo +{{RUST_TOOLCHAIN}} xtask powerset {{args}} -# An alias for cargo fmt --all +# An alias for cargo fmt --all and taplo fmt for TOML files. fmt *args: cargo +{{RUST_TOOLCHAIN}} fmt --all {{args}} + taplo fmt lint *args: cargo +{{RUST_TOOLCHAIN}} clippy --fix --allow-dirty --allow-staged --all-features --all-targets {{args}} -- -Aclippy::collapsible_if diff --git a/changes.d/pr-441.toml b/changes.d/pr-441.toml new file mode 100644 index 000000000..983bfce7b --- /dev/null +++ b/changes.d/pr-441.toml @@ -0,0 +1,24 @@ +[[scuffle-amf0]] +category = "fix" +description = "Fix amf0 deserialization to abide by better deserialization standards" +authors = ["@philipch07", "@TroyKomodo"] + +[[scuffle-amf0]] +category = "fix" +description = "Fix `Amf0Object` to be represented by a BTreeMap instead of a hashmap to guarantee consistency when iterating through the map" +authors = ["@TroyKomodo"] + +[[scuffle-amf0]] +category = "fix" +description = "Fix `Amf0Array` to be represented by a vec instead of a cow which is more appropriate for the type" +authors = ["@TroyKomodo"] + +[[scuffle-amf0]] +category = "feat" +description = "Add an Amf0Value deserializer" +authors = ["@philipch07", "@TroyKomodo"] + +[[scuffle-bytes-util]] +category = "feat" +description = "Add an `IntoDeserializer` implementation for a `&StringCow`" +authors = ["@TroyKomodo"] diff --git a/crates/amf0/Cargo.toml b/crates/amf0/Cargo.toml index b7e0260f7..176275de4 100644 --- a/crates/amf0/Cargo.toml +++ b/crates/amf0/Cargo.toml @@ -17,15 +17,17 @@ byteorder = "1.5" bytes = "1.10.1" bytestring = "1.4.0" document-features = { optional = true, version = "0.2" } +indexmap = { optional = true, version = "2.4.0" } num-derive = "0.4" num-traits = "0.2" -scuffle-bytes-util = { path = "../bytes-util", version = "0.1.3" } +scuffle-bytes-util = { path = "../bytes-util", version = ">=0.1.4" } scuffle-changelog = { optional = true, path = "../changelog", version = "0.1.0" } scuffle-workspace-hack.workspace = true serde = { optional = true, version = "1" } thiserror = "2.0" [dev-dependencies] +insta = "1.42" serde_derive = "1" [features] @@ -33,6 +35,8 @@ serde_derive = "1" serde = ["dep:serde", "scuffle-bytes-util/serde"] ## Enables changelog and documentation of feature flags docs = ["dep:scuffle-changelog", "dep:document-features"] +## Use an index map instead of a btree-map for Amf0Objects +preserve_order = ["dep:indexmap"] [package.metadata.docs.rs] all-features = true diff --git a/crates/amf0/README.md b/crates/amf0/README.md index f4b923b2a..022922080 100644 --- a/crates/amf0/README.md +++ b/crates/amf0/README.md @@ -26,6 +26,7 @@ See the [changelog](./CHANGELOG.md) for a full release history. * **`serde`** — Enables serde support * **`docs`** — Enables changelog and documentation of feature flags +* **`preserve_order`** — Use an index map instead of a btree-map for Amf0Objects ### Specification diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index d10a96995..87c5b696b 100644 --- a/crates/amf0/src/de/mod.rs +++ b/crates/amf0/src/de/mod.rs @@ -6,7 +6,7 @@ use scuffle_bytes_util::zero_copy::ZeroCopyReader; use serde::de::{EnumAccess, IntoDeserializer, MapAccess, SeqAccess, VariantAccess}; use crate::decoder::{Amf0Decoder, ObjectHeader}; -use crate::{Amf0Error, Amf0Marker}; +use crate::{Amf0Error, Amf0Marker, Amf0Value}; mod stream; @@ -42,177 +42,148 @@ where Ok(value) } +macro_rules! impl_de_number { + ($deserializser_fn:ident, $visit_fn:ident) => { + fn $deserializser_fn(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if let Amf0Marker::Number = self.peek_marker()? { + // must make sure the marker is a number so we don't error out + let value = self.decode_number()?; + if let Some(value) = ::num_traits::cast(value) { + visitor.$visit_fn(value) + } else { + visitor.visit_f64(value) + } + } else { + self.deserialize_any(visitor) + } + } + }; +} + impl<'de, R> serde::de::Deserializer<'de> for &mut Amf0Decoder where R: ZeroCopyReader<'de>, { type Error = Amf0Error; - fn deserialize_any(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let marker = self.peek_marker()?; - - match marker { - Amf0Marker::Boolean => self.deserialize_bool(visitor), - Amf0Marker::Number | Amf0Marker::Date => self.deserialize_f64(visitor), - Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument => self.deserialize_str(visitor), - Amf0Marker::Null | Amf0Marker::Undefined => self.deserialize_unit(visitor), - Amf0Marker::Object | Amf0Marker::TypedObject | Amf0Marker::EcmaArray => self.deserialize_map(visitor), - Amf0Marker::StrictArray => self.deserialize_seq(visitor), - _ => Err(Amf0Error::UnsupportedMarker(marker)), - } + serde::forward_to_deserialize_any! { + tuple tuple_struct ignored_any identifier + str string seq map unit unit_struct bool + f64 struct } - fn deserialize_bool(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let value = self.decode_boolean()?; - visitor.visit_bool(value) - } + impl_de_number!(deserialize_i8, visit_i8); - fn deserialize_i8(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_i64(visitor) - } + impl_de_number!(deserialize_i16, visit_i16); - fn deserialize_i16(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_i64(visitor) - } + impl_de_number!(deserialize_i32, visit_i32); - fn deserialize_i32(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_i64(visitor) - } + impl_de_number!(deserialize_i64, visit_i64); - fn deserialize_i64(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let value = self.decode_number()?; - visitor.visit_i64(value as i64) - } + impl_de_number!(deserialize_u8, visit_u8); - fn deserialize_u8(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_u64(visitor) - } + impl_de_number!(deserialize_u16, visit_u16); - fn deserialize_u16(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_u64(visitor) - } + impl_de_number!(deserialize_u32, visit_u32); - fn deserialize_u32(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_u64(visitor) - } + impl_de_number!(deserialize_u64, visit_u64); - fn deserialize_u64(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let value = self.decode_number()?; - visitor.visit_u64(value as u64) - } - - fn deserialize_f32(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_f64(visitor) - } + impl_de_number!(deserialize_f32, visit_f32); - fn deserialize_f64(self, visitor: V) -> Result + fn deserialize_byte_buf(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - let value = self.decode_number()?; - visitor.visit_f64(value) + if let Amf0Marker::StrictArray = self.peek_marker()? { + let array = self.decode_strict_array()?; + match array + .iter() + .map(|a| match a { + Amf0Value::Number(n) => num_traits::cast(*n).ok_or(()), + _ => Err(()), + }) + .collect::>() + { + Ok(buf) => visitor.visit_byte_buf(buf), + Err(()) => Amf0Value::Array(array).deserialize_any(visitor), + } + } else { + self.deserialize_any(visitor) + } } - fn deserialize_char(self, _visitor: V) -> Result + fn deserialize_bytes(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - Err(Amf0Error::CharNotSupported) + self.deserialize_byte_buf(visitor) } - fn deserialize_string(self, visitor: V) -> Result + fn deserialize_char(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - let value = self.decode_string()?; - value.into_deserializer().deserialize_string(visitor) + if let Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument = self.peek_marker()? { + let value = self.decode_string()?; + value.into_deserializer().deserialize_any(visitor) + } else { + self.deserialize_any(visitor) + } } - fn deserialize_str(self, visitor: V) -> Result + fn deserialize_any(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - let value = self.decode_string()?; - value.into_deserializer().deserialize_str(visitor) - } + let marker = self.peek_marker()?; - fn deserialize_bytes(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_seq(visitor) - } + match marker { + Amf0Marker::Boolean => visitor.visit_bool(self.decode_boolean()?), + Amf0Marker::Number | Amf0Marker::Date => visitor.visit_f64(self.decode_number()?), + Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument => { + self.decode_string()?.into_deserializer().deserialize_any(visitor) + } + Amf0Marker::Null | Amf0Marker::Undefined => { + self.decode_null()?; + visitor.visit_unit() + }, + Amf0Marker::Object | Amf0Marker::TypedObject | Amf0Marker::EcmaArray => { + let header = self.decode_object_header()?; + match header { + ObjectHeader::Object | ObjectHeader::TypedObject { .. } => visitor.visit_map(Object { de: self }), + ObjectHeader::EcmaArray { size } => visitor.visit_map(EcmaArray { + de: self, + remaining: size as usize, + }), + } + } + Amf0Marker::StrictArray => { + let size = self.decode_strict_array_header()? as usize; - fn deserialize_byte_buf(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_seq(visitor) + visitor.visit_seq(StrictArray { + de: self, + remaining: size, + }) + } + _ => Err(Amf0Error::UnsupportedMarker(marker)), + } } fn deserialize_option(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - let marker = self.peek_marker()?; - - if marker == Amf0Marker::Null || marker == Amf0Marker::Undefined { - self.next_marker = None; // clear the marker buffer - + if let Amf0Marker::Null | Amf0Marker::Undefined = self.peek_marker()? { + self.decode_null()?; visitor.visit_none() } else { visitor.visit_some(self) } } - fn deserialize_unit(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.decode_null()?; - visitor.visit_unit() - } - - fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_unit(visitor) - } - fn deserialize_newtype_struct(self, name: &'static str, visitor: V) -> Result where V: serde::de::Visitor<'de>, @@ -224,71 +195,6 @@ where } } - fn deserialize_seq(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let size = self.decode_strict_array_header()? as usize; - - visitor.visit_seq(StrictArray { - de: self, - remaining: size, - }) - } - - fn deserialize_tuple(self, len: usize, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let size = self.decode_strict_array_header()? as usize; - - if len != size { - return Err(Amf0Error::WrongArrayLength { - expected: len, - got: size, - }); - } - - visitor.visit_seq(StrictArray { - de: self, - remaining: size, - }) - } - - fn deserialize_tuple_struct(self, _name: &'static str, len: usize, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_tuple(len, visitor) - } - - fn deserialize_map(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let header = self.decode_object_header()?; - - match header { - ObjectHeader::Object | ObjectHeader::TypedObject { .. } => visitor.visit_map(Object { de: self }), - ObjectHeader::EcmaArray { size } => visitor.visit_map(EcmaArray { - de: self, - remaining: size as usize, - }), - } - } - - fn deserialize_struct( - self, - _name: &'static str, - _fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_map(visitor) - } - fn deserialize_enum( self, _name: &'static str, @@ -300,21 +206,6 @@ where { visitor.visit_enum(Enum { de: self }) } - - fn deserialize_identifier(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let s = self.decode_string()?; - s.into_deserializer().deserialize_identifier(visitor) - } - - fn deserialize_ignored_any(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_any(visitor) - } } struct StrictArray<'a, R> { @@ -512,15 +403,9 @@ mod tests { let value: String = from_buf(Bytes::from_owner(bytes)).unwrap(); assert_eq!(value, "hello"); - let bytes = [Amf0Marker::Boolean as u8]; + let bytes = [Amf0Marker::Boolean as u8, 0]; let err = from_buf::(Bytes::from_owner(bytes)).unwrap_err(); - assert!(matches!( - err, - Amf0Error::UnexpectedType { - expected: [Amf0Marker::String, Amf0Marker::LongString, Amf0Marker::XmlDocument], - got: Amf0Marker::Boolean - } - )); + assert_eq!(err.to_string(), "invalid type: boolean `false`, expected a string"); } #[test] @@ -528,16 +413,6 @@ mod tests { let bytes = [Amf0Marker::Boolean as u8, 1]; let value: bool = from_buf(Bytes::from_owner(bytes)).unwrap(); assert!(value); - - let bytes = [Amf0Marker::String as u8]; - let err = from_buf::(Bytes::from_owner(bytes)).unwrap_err(); - assert!(matches!( - err, - Amf0Error::UnexpectedType { - expected: [Amf0Marker::Boolean], - got: Amf0Marker::String - } - )); } fn number_test<'de, T>(one: T) @@ -565,6 +440,7 @@ mod tests { #[test] fn numbers() { + number_test(1f64); number_test(1u8); number_test(1u16); number_test(1u32); @@ -574,29 +450,22 @@ mod tests { number_test(1i32); number_test(1i64); number_test(1f32); - number_test(1f64); let mut bytes = vec![Amf0Marker::Date as u8]; bytes.extend_from_slice(&f64::consts::PI.to_be_bytes()); bytes.extend_from_slice(&0u16.to_be_bytes()); // timezone let value: f64 = from_buf(Bytes::from_owner(bytes)).unwrap(); assert_eq!(value, f64::consts::PI); - - let bytes = [Amf0Marker::Boolean as u8]; - let err = from_buf::(Bytes::from_owner(bytes)).unwrap_err(); - assert!(matches!( - err, - Amf0Error::UnexpectedType { - expected: [Amf0Marker::Number, Amf0Marker::Date], - got: Amf0Marker::Boolean - } - )); } #[test] fn char() { let err = from_buf::(Bytes::from_owner([])).unwrap_err(); - assert!(matches!(err, Amf0Error::CharNotSupported)); + + assert!(matches!( + err, + Amf0Error::Io(ref io_err) if io_err.kind() == std::io::ErrorKind::UnexpectedEof && io_err.to_string().contains("failed to fill whole buffer") + )); } #[test] @@ -608,16 +477,6 @@ mod tests { let bytes = [Amf0Marker::Null as u8]; from_buf::<()>(Bytes::from_owner(bytes)).unwrap(); - let bytes = [Amf0Marker::String as u8]; - let err = from_buf::<()>(Bytes::from_owner(bytes)).unwrap_err(); - assert!(matches!( - err, - Amf0Error::UnexpectedType { - expected: [Amf0Marker::Null, Amf0Marker::Undefined], - got: Amf0Marker::String - } - )); - let bytes = [Amf0Marker::Undefined as u8]; let value: Option = from_buf(Bytes::from_owner(bytes)).unwrap(); assert_eq!(value, None); @@ -675,7 +534,10 @@ mod tests { 1, ]; let err = from_buf::(Bytes::from_owner(bytes)).unwrap_err(); - assert!(matches!(err, Amf0Error::WrongArrayLength { expected: 2, got: 1 })); + assert_eq!( + err.to_string(), + "invalid length 1, expected tuple struct Test with 2 elements" + ); } #[test] @@ -780,14 +642,8 @@ mod tests { } ); - let err = from_buf::(Bytes::from_owner([Amf0Marker::String as u8])).unwrap_err(); - assert!(matches!( - err, - Amf0Error::UnexpectedType { - expected: [Amf0Marker::Object, Amf0Marker::TypedObject, Amf0Marker::EcmaArray], - got: Amf0Marker::String - } - )); + let err = from_buf::(Bytes::from_owner([Amf0Marker::String as u8, 0, 0])).unwrap_err(); + assert_eq!(err.to_string(), "invalid type: string \"\", expected struct Test"); } #[test] @@ -1004,6 +860,7 @@ mod tests { ]; let mut de = Amf0Decoder::from_buf(Bytes::from_owner(bytes)); + // also this is breaking: `Result::unwrap()` on an `Err` value: Custom("invalid type: newtype struct, expected a series of values") let values: MultiValue<(String, bool, Amf0Object)> = de.deserialize().unwrap(); assert_eq!(values.0.0, "hello"); assert!(values.0.1); diff --git a/crates/amf0/src/decoder.rs b/crates/amf0/src/decoder.rs index 537c09b8a..31679e60c 100644 --- a/crates/amf0/src/decoder.rs +++ b/crates/amf0/src/decoder.rs @@ -7,7 +7,7 @@ use num_traits::FromPrimitive; use scuffle_bytes_util::StringCow; use scuffle_bytes_util::zero_copy::ZeroCopyReader; -use crate::{Amf0Array, Amf0Error, Amf0Marker, Amf0Object, Amf0Value}; +use crate::{Amf0Error, Amf0Marker, Amf0Object, Amf0Value}; /// AMF0 decoder. /// @@ -268,7 +268,7 @@ where Ok(object) } ObjectHeader::EcmaArray { size } => { - let mut object = Amf0Object::with_capacity(size as usize); + let mut object = Amf0Object::new(); for _ in 0..size { // Object keys are not preceeded with a marker and are always normal strings @@ -298,7 +298,7 @@ where } /// Decode a strict array from the buffer. - pub fn decode_strict_array(&mut self) -> Result, Amf0Error> { + pub fn decode_strict_array(&mut self) -> Result>, Amf0Error> { let size = self.decode_strict_array_header()? as usize; let mut array = Vec::with_capacity(size); @@ -308,7 +308,7 @@ where array.push(value); } - Ok(Amf0Array::from(array)) + Ok(array) } } @@ -338,6 +338,8 @@ impl<'de, R> std::iter::FusedIterator for Amf0DecoderStream<'_, 'de, R> where R: #[cfg(test)] #[cfg_attr(all(test, coverage_nightly), coverage(off))] mod tests { + use scuffle_bytes_util::StringCow; + use super::Amf0Decoder; use crate::{Amf0Marker, Amf0Value}; @@ -377,8 +379,14 @@ mod tests { let mut decoder = Amf0Decoder::from_slice(&bytes); let object = decoder.decode_object().unwrap(); assert_eq!(object.len(), 2); - assert_eq!(*object.get(&"abc".into()).unwrap(), Amf0Value::String("val".into())); - assert_eq!(*object.get(&"defg".into()).unwrap(), Amf0Value::Boolean(true)); + assert_eq!( + *object.get(&StringCow::from_static("abc")).unwrap(), + Amf0Value::String("val".into()) + ); + assert_eq!( + *object.get(&StringCow::from_static("defg")).unwrap(), + Amf0Value::Boolean(true) + ); } #[test] diff --git a/crates/amf0/src/encoder.rs b/crates/amf0/src/encoder.rs index d0add67a1..26031b562 100644 --- a/crates/amf0/src/encoder.rs +++ b/crates/amf0/src/encoder.rs @@ -1,10 +1,11 @@ //! AMF0 encoder +use std::borrow::Borrow; use std::io; use byteorder::{BigEndian, WriteBytesExt}; -use crate::{Amf0Array, Amf0Error, Amf0Marker, Amf0Object}; +use crate::{Amf0Error, Amf0Marker, Amf0Object, Amf0Value}; /// AMF0 encoder. /// @@ -82,12 +83,18 @@ where Ok(()) } - /// Encode an [`Amf0Array`] as an AMF0 StrictArray value. - pub fn encode_array(&mut self, values: &Amf0Array) -> Result<(), Amf0Error> { - self.encode_array_header(values.len().try_into()?)?; + /// Encode an Amf0Array as an AMF0 StrictArray value. + pub fn encode_array<'a, I, B>(&mut self, values: I) -> Result<(), Amf0Error> + where + B: Borrow>, + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + { + let iter = values.into_iter(); + self.encode_array_header(iter.len().try_into()?)?; - for value in values.iter() { - value.encode(self)?; + for value in iter { + value.borrow().encode(self)?; } Ok(()) @@ -105,7 +112,10 @@ where } pub(crate) fn encode_object_trailer(&mut self) -> Result<(), Amf0Error> { - self.writer.write_u24::(Amf0Marker::ObjectEnd as u32)?; + // Final object key length is 0 + self.writer.write_u16::(0)?; + // Followed by object end + self.writer.write_u8(Amf0Marker::ObjectEnd as u8)?; Ok(()) } diff --git a/crates/amf0/src/lib.rs b/crates/amf0/src/lib.rs index 48f07e3ae..c21fc5b7d 100644 --- a/crates/amf0/src/lib.rs +++ b/crates/amf0/src/lib.rs @@ -46,11 +46,15 @@ #![deny(unsafe_code)] #![deny(unreachable_pub)] +#[cfg(not(feature = "preserve_order"))] +extern crate alloc; + #[cfg(feature = "serde")] pub mod de; pub mod decoder; pub mod encoder; pub mod error; +pub mod object; #[cfg(feature = "serde")] pub mod ser; pub mod value; @@ -60,9 +64,10 @@ pub use de::{from_buf, from_reader, from_slice}; pub use decoder::Amf0Decoder; pub use encoder::Amf0Encoder; pub use error::{Amf0Error, Result}; +pub use object::Amf0Object; #[cfg(feature = "serde")] pub use ser::{to_bytes, to_writer}; -pub use value::{Amf0Array, Amf0Object, Amf0Value}; +pub use value::Amf0Value; /// AMF0 marker types. /// diff --git a/crates/amf0/src/object.rs b/crates/amf0/src/object.rs new file mode 100644 index 000000000..df902d74d --- /dev/null +++ b/crates/amf0/src/object.rs @@ -0,0 +1,2478 @@ +//! This code is modified from +//! +//! A map of scuffle_bytes_util::StringCow to crate::Amf0Value. +//! +//! By default the map is backed by a [`BTreeMap`]. Enable the `preserve_order` +//! feature of scuffle_amf0 to use [`IndexMap`] instead. +//! +//! [`BTreeMap`]: std::collections::BTreeMap +//! [`IndexMap`]: indexmap::IndexMap + +#[cfg(not(feature = "preserve_order"))] +use alloc::collections::{BTreeMap, btree_map}; +use core::borrow::Borrow; +use core::fmt::{self, Debug}; +use core::hash::Hash; +use core::iter::FusedIterator; +use core::ops; +use std::marker::PhantomData; + +#[cfg(feature = "preserve_order")] +use indexmap::IndexMap; +use scuffle_bytes_util::StringCow; +use serde::de; + +use crate::{Amf0Error, Amf0Value}; + +/// Represents a JSON key/value type. +#[derive(Clone, PartialEq)] +pub struct Amf0Object<'a> { + map: MapImpl, Amf0Value<'a>>, +} + +#[cfg(not(feature = "preserve_order"))] +type MapImpl = BTreeMap; +#[cfg(feature = "preserve_order")] +type MapImpl = IndexMap; + +impl<'a> Default for Amf0Object<'a> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> Amf0Object<'a> { + /// Makes a new empty Map. + #[inline] + pub fn new() -> Self { + Amf0Object { map: MapImpl::new() } + } + + /// Makes a new empty Map with the given initial capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Amf0Object { + #[cfg(not(feature = "preserve_order"))] + map: { + // does not support with_capacity + let _ = capacity; + BTreeMap::new() + }, + #[cfg(feature = "preserve_order")] + map: IndexMap::with_capacity(capacity), + } + } + + /// Clears the map, removing all values. + #[inline] + pub fn clear(&mut self) { + self.map.clear(); + } + + /// Returns a reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + #[inline] + pub fn get(&self, key: &Q) -> Option<&Amf0Value<'a>> + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + self.map.get(key) + } + + /// Returns true if the map contains a value for the specified key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + #[inline] + pub fn contains_key(&self, key: &Q) -> bool + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + self.map.contains_key(key) + } + + /// Returns a mutable reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + #[inline] + pub fn get_mut(&mut self, key: &Q) -> Option<&mut Amf0Value<'a>> + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + self.map.get_mut(key) + } + + /// Returns the key-value pair matching the given key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + #[inline] + pub fn get_key_value(&self, key: &Q) -> Option<(&StringCow<'a>, &Amf0Value<'a>)> + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + self.map.get_key_value(key) + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not have this key present, `None` is returned. + /// + /// If the map did have this key present, the value is updated, and the old + /// value is returned. + #[inline] + pub fn insert(&mut self, k: StringCow<'a>, v: Amf0Value<'a>) -> Option> { + self.map.insert(k, v) + } + + /// Insert a key-value pair in the map at the given index. + /// + /// If the map did not have this key present, `None` is returned. + /// + /// If the map did have this key present, the key is moved to the new + /// position, the value is updated, and the old value is returned. + #[cfg(feature = "preserve_order")] + #[inline] + pub fn shift_insert(&mut self, index: usize, k: StringCow<'a>, v: Amf0Value<'a>) -> Option> { + self.map.shift_insert(index, k, v) + } + + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + /// + /// If scuffle_amf0's "preserve_order" is enabled, `.remove(key)` is + /// equivalent to [`.swap_remove(key)`][Self::swap_remove], replacing this + /// entry's position with the last element. If you need to preserve the + /// relative order of the keys in the map, use + /// [`.shift_remove(key)`][Self::shift_remove] instead. + #[inline] + pub fn remove(&mut self, key: &Q) -> Option> + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + #[cfg(feature = "preserve_order")] + return self.swap_remove(key); + #[cfg(not(feature = "preserve_order"))] + return self.map.remove(key); + } + + /// Removes a key from the map, returning the stored key and value if the + /// key was previously in the map. + /// + /// The key may be any borrowed form of the map's key type, but the ordering + /// on the borrowed form *must* match the ordering on the key type. + /// + /// If scuffle_amf0's "preserve_order" is enabled, `.remove_entry(key)` is + /// equivalent to [`.swap_remove_entry(key)`][Self::swap_remove_entry], + /// replacing this entry's position with the last element. If you need to + /// preserve the relative order of the keys in the map, use + /// [`.shift_remove_entry(key)`][Self::shift_remove_entry] instead. + #[inline] + pub fn remove_entry(&mut self, key: &Q) -> Option<(StringCow<'a>, Amf0Value<'a>)> + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + #[cfg(feature = "preserve_order")] + return self.swap_remove_entry(key); + #[cfg(not(feature = "preserve_order"))] + return self.map.remove_entry(key); + } + + /// Removes and returns the value corresponding to the key from the map. + /// + /// Like [`Vec::swap_remove`], the entry is removed by swapping it with the + /// last element of the map and popping it off. This perturbs the position + /// of what used to be the last element! + /// + /// [`Vec::swap_remove`]: std::vec::Vec::swap_remove + #[cfg(feature = "preserve_order")] + #[inline] + pub fn swap_remove(&mut self, key: &Q) -> Option> + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + self.map.swap_remove(key) + } + + /// Remove and return the key-value pair. + /// + /// Like [`Vec::swap_remove`], the entry is removed by swapping it with the + /// last element of the map and popping it off. This perturbs the position + /// of what used to be the last element! + /// + /// [`Vec::swap_remove`]: std::vec::Vec::swap_remove + #[cfg(feature = "preserve_order")] + #[inline] + pub fn swap_remove_entry(&mut self, key: &Q) -> Option<(StringCow<'a>, Amf0Value<'a>)> + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + self.map.swap_remove_entry(key) + } + + /// Removes and returns the value corresponding to the key from the map. + /// + /// Like [`Vec::remove`], the entry is removed by shifting all of the + /// elements that follow it, preserving their relative order. This perturbs + /// the index of all of those elements! + /// + /// [`Vec::remove`]: std::vec::Vec::remove + #[cfg(feature = "preserve_order")] + #[inline] + pub fn shift_remove(&mut self, key: &Q) -> Option> + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + self.map.shift_remove(key) + } + + /// Remove and return the key-value pair. + /// + /// Like [`Vec::remove`], the entry is removed by shifting all of the + /// elements that follow it, preserving their relative order. This perturbs + /// the index of all of those elements! + /// + /// [`Vec::remove`]: std::vec::Vec::remove + #[cfg(feature = "preserve_order")] + #[inline] + pub fn shift_remove_entry(&mut self, key: &Q) -> Option<(StringCow<'a>, Amf0Value<'a>)> + where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, + { + self.map.shift_remove_entry(key) + } + + /// Moves all elements from other into self, leaving other empty. + #[inline] + pub fn append(&mut self, other: &mut Self) { + #[cfg(feature = "preserve_order")] + self.map.extend(std::mem::take(&mut other.map)); + #[cfg(not(feature = "preserve_order"))] + self.map.append(&mut other.map); + } + + /// Gets the given key's corresponding entry in the map for in-place + /// manipulation. + pub fn entry(&mut self, key: impl Into>) -> Entry<'_, 'a> { + #[cfg(not(feature = "preserve_order"))] + use alloc::collections::btree_map::Entry as EntryImpl; + + #[cfg(feature = "preserve_order")] + use indexmap::map::Entry as EntryImpl; + + match self.map.entry(key.into()) { + EntryImpl::Vacant(vacant) => Entry::Vacant(VacantEntry { vacant }), + EntryImpl::Occupied(occupied) => Entry::Occupied(OccupiedEntry { occupied }), + } + } + + /// Returns the number of elements in the map. + #[inline] + pub fn len(&self) -> usize { + self.map.len() + } + + /// Returns true if the map contains no elements. + #[inline] + pub fn is_empty(&self) -> bool { + self.map.is_empty() + } + + /// Gets an iterator over the entries of the map. + #[inline] + pub fn iter(&self) -> Iter<'_, 'a> { + Iter { iter: self.map.iter() } + } + + /// Gets a mutable iterator over the entries of the map. + #[inline] + pub fn iter_mut(&mut self) -> IterMut<'_, 'a> { + IterMut { + iter: self.map.iter_mut(), + } + } + + /// Gets an iterator over the keys of the map. + #[inline] + pub fn keys(&self) -> Keys<'_, 'a> { + Keys { iter: self.map.keys() } + } + + /// Gets an iterator over the values of the map. + #[inline] + pub fn values(&self) -> Values<'_, 'a> { + Values { iter: self.map.values() } + } + + /// Gets an iterator over mutable values of the map. + #[inline] + pub fn values_mut(&mut self) -> ValuesMut<'_, 'a> { + ValuesMut { + iter: self.map.values_mut(), + } + } + + /// Gets an iterator over the values of the map. + #[inline] + pub fn into_values(self) -> IntoValues<'a> { + IntoValues { + iter: self.map.into_values(), + } + } + + /// Retains only the elements specified by the predicate. + /// + /// In other words, remove all pairs `(k, v)` such that `f(&k, &mut v)` + /// returns `false`. + #[inline] + pub fn retain(&mut self, f: F) + where + F: FnMut(&StringCow<'a>, &mut Amf0Value<'a>) -> bool, + { + self.map.retain(f); + } + + /// Sorts this map's entries in-place using `str`'s usual ordering. + /// + /// If scuffle_amf0's "preserve_order" feature is not enabled, this method + /// does no work because all JSON maps are always kept in a sorted state. + /// + /// If scuffle_amf0's "preserve_order" feature is enabled, this method + /// destroys the original source order or insertion order of this map in + /// favor of an alphanumerical order that matches how a BTreeMap with the + /// same contents would be ordered. This takes **O(n log n + c)** time where + /// _n_ is the length of the map and _c_ is the capacity. + #[inline] + pub fn sort_keys(&mut self) { + #[cfg(feature = "preserve_order")] + self.map.sort_unstable_keys(); + } +} + +/// Access an element of this map. Panics if the given key is not present in the +/// map. +/// +/// ``` +/// use scuffle_amf0::{Amf0Object, Amf0Value}; +/// use std::collections::BTreeMap; +/// use scuffle_bytes_util::StringCow; +/// +/// let mut map = scuffle_amf0::Amf0Object::new(); +/// map.insert(StringCow::from_ref("type"), Amf0Value::String("example".into())); +/// let obj = Amf0Object::from(map); +/// +/// assert_eq!(obj["type"], Amf0Value::String("example".into())); +/// ``` +impl<'a, Q> ops::Index<&Q> for Amf0Object<'a> +where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, +{ + type Output = Amf0Value<'a>; + + fn index(&self, index: &Q) -> &Self::Output { + self.map.index(index) + } +} + +/// Mutably access an element of this map. Panics if the given key is not +/// present in the map. +impl<'a, Q> ops::IndexMut<&Q> for Amf0Object<'a> +where + StringCow<'a>: Borrow, + Q: ?Sized + Ord + Eq + Hash, +{ + fn index_mut(&mut self, index: &Q) -> &mut Self::Output { + self.map.get_mut(index).expect("no entry found for key") + } +} + +impl Debug for Amf0Object<'_> { + #[inline] + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.map.fmt(formatter) + } +} + +#[cfg(feature = "serde")] +impl serde::ser::Serialize for Amf0Object<'_> { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + use serde::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(self.len()))?; + for (k, v) in self { + map.serialize_entry(k, v)?; + } + map.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> de::Deserialize<'de> for Amf0Object<'de> { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = Amf0Object<'de>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + #[inline] + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(Amf0Object::new()) + } + + #[inline] + fn visit_map(self, mut visitor: V) -> Result + where + V: de::MapAccess<'de>, + { + let mut values = Amf0Object::new(); + + while let Some((key, value)) = visitor.next_entry()? { + values.insert(key, value); + } + + Ok(values) + } + } + + deserializer.deserialize_map(Visitor) + } +} + +impl<'a> FromIterator<(StringCow<'a>, Amf0Value<'a>)> for Amf0Object<'a> { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, Amf0Value<'a>)>, + { + Amf0Object { + map: FromIterator::from_iter(iter), + } + } +} + +impl<'a> Extend<(StringCow<'a>, Amf0Value<'a>)> for Amf0Object<'a> { + fn extend(&mut self, iter: T) + where + T: IntoIterator, Amf0Value<'a>)>, + { + self.map.extend(iter); + } +} + +macro_rules! delegate_iterator { + (($name:ident $($generics:tt)*) => $item:ty) => { + impl $($generics)* Iterator for $name $($generics)* { + type Item = $item; + #[inline] + fn next(&mut self) -> Option { + self.iter.next() + } + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + } + + impl $($generics)* DoubleEndedIterator for $name $($generics)* { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back() + } + } + + impl $($generics)* ExactSizeIterator for $name $($generics)* { + #[inline] + fn len(&self) -> usize { + self.iter.len() + } + } + + impl $($generics)* FusedIterator for $name $($generics)* {} + } +} + +#[cfg(feature = "serde")] +impl<'de> de::IntoDeserializer<'de, Amf0Error> for Amf0Object<'de> { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +#[cfg(feature = "serde")] +impl<'de> de::IntoDeserializer<'de, Amf0Error> for &'de Amf0Object<'de> { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::de::Deserializer<'de> for Amf0Object<'de> { + type Error = Amf0Error; + + serde::forward_to_deserialize_any! { + bool f64 f32 char str string unit + i8 i16 i32 i64 u8 u16 u32 u64 + seq map newtype_struct tuple + struct enum ignored_any identifier + bytes byte_buf option unit_struct + tuple_struct + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_map(Amf0MapAccess { + iter: self.into_iter(), + _phantom: PhantomData, + value: None, + }) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::de::Deserializer<'de> for &'de Amf0Object<'de> { + type Error = Amf0Error; + + serde::forward_to_deserialize_any! { + bool f64 f32 char str string unit + i8 i16 i32 i64 u8 u16 u32 u64 + seq map newtype_struct tuple + struct enum ignored_any identifier + bytes byte_buf option unit_struct + tuple_struct + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_map(Amf0MapAccess { + iter: self.into_iter(), + _phantom: PhantomData, + value: None, + }) + } +} + +#[cfg(feature = "serde")] +struct Amf0MapAccess { + iter: I, + value: Option, + _phantom: PhantomData, +} + +#[cfg(feature = "serde")] +impl<'de, I, K, V> serde::de::MapAccess<'de> for Amf0MapAccess +where + I: Iterator, + K: serde::de::IntoDeserializer<'de, Amf0Error>, + V: serde::de::IntoDeserializer<'de, Amf0Error>, +{ + type Error = Amf0Error; + + fn next_key_seed(&mut self, seed: S) -> Result, Self::Error> + where + S: serde::de::DeserializeSeed<'de>, + { + match self.iter.next() { + Some((key, value)) => { + self.value = Some(value); + seed.deserialize(key.into_deserializer()).map(Some) + } + None => Ok(None), + } + } + + fn next_value_seed(&mut self, seed: S) -> Result + where + S: serde::de::DeserializeSeed<'de>, + { + seed.deserialize(self.value.take().unwrap().into_deserializer()) + } +} + +////////////////////////////////////////////////////////////////////////////// + +/// A view into a single entry in a map, which may either be vacant or occupied. +/// This enum is constructed from the [`entry`] method on [`Amf0Object`]. +/// +/// [`entry`]: Amf0Object::entry +pub enum Entry<'a, 'b> { + /// A vacant Entry. + Vacant(VacantEntry<'a, 'b>), + /// An occupied Entry. + Occupied(OccupiedEntry<'a, 'b>), +} + +/// A vacant Entry. It is part of the [`Entry`] enum. +pub struct VacantEntry<'a, 'b> { + vacant: VacantEntryImpl<'a, 'b>, +} + +/// An occupied Entry. It is part of the [`Entry`] enum. +pub struct OccupiedEntry<'a, 'b> { + occupied: OccupiedEntryImpl<'a, 'b>, +} + +#[cfg(not(feature = "preserve_order"))] +type VacantEntryImpl<'a, 'b> = btree_map::VacantEntry<'a, StringCow<'b>, Amf0Value<'b>>; +#[cfg(feature = "preserve_order")] +type VacantEntryImpl<'a, 'b> = indexmap::map::VacantEntry<'a, StringCow<'b>, Amf0Value<'b>>; + +#[cfg(not(feature = "preserve_order"))] +type OccupiedEntryImpl<'a, 'b> = btree_map::OccupiedEntry<'a, StringCow<'b>, Amf0Value<'b>>; +#[cfg(feature = "preserve_order")] +type OccupiedEntryImpl<'a, 'b> = indexmap::map::OccupiedEntry<'a, StringCow<'b>, Amf0Value<'b>>; + +impl<'a, 'b> Entry<'a, 'b> { + /// Returns a reference to this entry's key. + /// + /// # Examples + /// + /// ``` + /// let mut map = scuffle_amf0::Amf0Object::new(); + /// assert_eq!(map.entry("serde").key(), &"serde"); + /// ``` + pub fn key(&self) -> &StringCow<'b> { + match self { + Entry::Vacant(e) => e.key(), + Entry::Occupied(e) => e.key(), + } + } + + /// Ensures a value is in the entry by inserting the default if empty, and + /// returns a mutable reference to the value in the entry. + pub fn or_insert(self, default: Amf0Value<'b>) -> &'a mut Amf0Value<'b> { + match self { + Entry::Vacant(entry) => entry.insert(default), + Entry::Occupied(entry) => entry.into_mut(), + } + } + + /// Ensures a value is in the entry by inserting the result of the default + /// function if empty, and returns a mutable reference to the value in the + /// entry. + pub fn or_insert_with(self, default: F) -> &'a mut Amf0Value<'b> + where + F: FnOnce() -> Amf0Value<'b>, + { + match self { + Entry::Vacant(entry) => entry.insert(default()), + Entry::Occupied(entry) => entry.into_mut(), + } + } + + /// Provides in-place mutable access to an occupied entry before any + /// potential inserts into the map. + pub fn and_modify(self, f: F) -> Self + where + F: FnOnce(&mut Amf0Value<'b>), + { + match self { + Entry::Occupied(mut entry) => { + f(entry.get_mut()); + Entry::Occupied(entry) + } + Entry::Vacant(entry) => Entry::Vacant(entry), + } + } +} + +impl<'a, 'b> VacantEntry<'a, 'b> { + /// Gets a reference to the key that would be used when inserting a value + /// through the VacantEntry. + /// + /// # Examples + /// + /// ``` + /// use scuffle_amf0::object::Entry; + /// + /// let mut map = scuffle_amf0::Amf0Object::new(); + /// + /// match map.entry("serde") { + /// Entry::Vacant(vacant) => { + /// assert_eq!(vacant.key(), &"serde"); + /// } + /// Entry::Occupied(_) => unimplemented!(), + /// } + /// ``` + #[inline] + pub fn key(&self) -> &StringCow<'b> { + self.vacant.key() + } + + /// Sets the value of the entry with the VacantEntry's key, and returns a + /// mutable reference to it. + #[inline] + pub fn insert(self, value: Amf0Value<'b>) -> &'a mut Amf0Value<'b> { + self.vacant.insert(value) + } +} + +impl<'a, 'b> OccupiedEntry<'a, 'b> { + /// Gets a reference to the key in the entry. + #[inline] + pub fn key(&self) -> &StringCow<'b> { + self.occupied.key() + } + + /// Gets a reference to the value in the entry. + #[inline] + pub fn get(&self) -> &Amf0Value<'b> { + self.occupied.get() + } + + /// Gets a mutable reference to the value in the entry. + #[inline] + pub fn get_mut(&mut self) -> &mut Amf0Value<'b> { + self.occupied.get_mut() + } + + /// Converts the entry into a mutable reference to its value. + #[inline] + pub fn into_mut(self) -> &'a mut Amf0Value<'b> { + self.occupied.into_mut() + } + + /// Sets the value of the entry with the `OccupiedEntry`'s key, and returns + /// the entry's old value. + #[inline] + pub fn insert(&mut self, value: Amf0Value<'b>) -> Amf0Value<'b> { + self.occupied.insert(value) + } + + /// Takes the value of the entry out of the map, and returns it. + /// + /// If scuffle_amf0's "preserve_order" is enabled, `.remove()` is + /// equivalent to [`.swap_remove()`][Self::swap_remove], replacing this + /// entry's position with the last element. If you need to preserve the + /// relative order of the keys in the map, use + /// [`.shift_remove()`][Self::shift_remove] instead. + #[inline] + pub fn remove(self) -> Amf0Value<'b> { + #[cfg(feature = "preserve_order")] + return self.swap_remove(); + #[cfg(not(feature = "preserve_order"))] + return self.occupied.remove(); + } + + /// Takes the value of the entry out of the map, and returns it. + /// + /// Like [`Vec::swap_remove`], the entry is removed by swapping it with the + /// last element of the map and popping it off. This perturbs the position + /// of what used to be the last element! + /// + /// [`Vec::swap_remove`]: std::vec::Vec::swap_remove + #[cfg(feature = "preserve_order")] + #[inline] + pub fn swap_remove(self) -> Amf0Value<'b> { + self.occupied.swap_remove() + } + + /// Takes the value of the entry out of the map, and returns it. + /// + /// Like [`Vec::remove`], the entry is removed by shifting all of the + /// elements that follow it, preserving their relative order. This perturbs + /// the index of all of those elements! + /// + /// [`Vec::remove`]: std::vec::Vec::remove + #[cfg(feature = "preserve_order")] + #[inline] + pub fn shift_remove(self) -> Amf0Value<'b> { + self.occupied.shift_remove() + } + + /// Removes the entry from the map, returning the stored key and value. + /// + /// If scuffle_amf0's "preserve_order" is enabled, `.remove_entry()` is + /// equivalent to [`.swap_remove_entry()`][Self::swap_remove_entry], + /// replacing this entry's position with the last element. If you need to + /// preserve the relative order of the keys in the map, use + /// [`.shift_remove_entry()`][Self::shift_remove_entry] instead. + #[inline] + pub fn remove_entry(self) -> (StringCow<'b>, Amf0Value<'b>) { + #[cfg(feature = "preserve_order")] + return self.swap_remove_entry(); + #[cfg(not(feature = "preserve_order"))] + return self.occupied.remove_entry(); + } + + /// Removes the entry from the map, returning the stored key and value. + /// + /// Like [`Vec::swap_remove`], the entry is removed by swapping it with the + /// last element of the map and popping it off. This perturbs the position + /// of what used to be the last element! + /// + /// [`Vec::swap_remove`]: std::vec::Vec::swap_remove + #[cfg(feature = "preserve_order")] + #[inline] + pub fn swap_remove_entry(self) -> (StringCow<'b>, Amf0Value<'b>) { + self.occupied.swap_remove_entry() + } + + /// Removes the entry from the map, returning the stored key and value. + /// + /// Like [`Vec::remove`], the entry is removed by shifting all of the + /// elements that follow it, preserving their relative order. This perturbs + /// the index of all of those elements! + /// + /// [`Vec::remove`]: std::vec::Vec::remove + #[cfg(feature = "preserve_order")] + #[inline] + pub fn shift_remove_entry(self) -> (StringCow<'b>, Amf0Value<'b>) { + self.occupied.shift_remove_entry() + } +} + +////////////////////////////////////////////////////////////////////////////// + +impl<'a, 'b> IntoIterator for &'a Amf0Object<'b> { + type IntoIter = Iter<'a, 'b>; + type Item = (&'a StringCow<'b>, &'a Amf0Value<'b>); + + #[inline] + fn into_iter(self) -> Self::IntoIter { + Iter { iter: self.map.iter() } + } +} + +/// An iterator over a scuffle_amf0::Amf0Object's entries. +pub struct Iter<'a, 'b> { + iter: IterImpl<'a, 'b>, +} + +#[cfg(not(feature = "preserve_order"))] +type IterImpl<'a, 'b> = btree_map::Iter<'a, StringCow<'b>, Amf0Value<'b>>; +#[cfg(feature = "preserve_order")] +type IterImpl<'a, 'b> = indexmap::map::Iter<'a, StringCow<'b>, Amf0Value<'b>>; + +delegate_iterator!((Iter<'a, 'b>) => (&'a StringCow<'b>, &'a Amf0Value<'b>)); + +////////////////////////////////////////////////////////////////////////////// + +impl<'a, 'b> IntoIterator for &'a mut Amf0Object<'b> { + type IntoIter = IterMut<'a, 'b>; + type Item = (&'a StringCow<'b>, &'a mut Amf0Value<'b>); + + #[inline] + fn into_iter(self) -> Self::IntoIter { + IterMut { + iter: self.map.iter_mut(), + } + } +} + +/// A mutable iterator over a scuffle_amf0::Amf0Object's entries. +pub struct IterMut<'a, 'b> { + iter: IterMutImpl<'a, 'b>, +} + +#[cfg(not(feature = "preserve_order"))] +type IterMutImpl<'a, 'b> = btree_map::IterMut<'a, StringCow<'b>, Amf0Value<'b>>; +#[cfg(feature = "preserve_order")] +type IterMutImpl<'a, 'b> = indexmap::map::IterMut<'a, StringCow<'b>, Amf0Value<'b>>; + +delegate_iterator!((IterMut<'a, 'b>) => (&'a StringCow<'b>, &'a mut Amf0Value<'b>)); + +////////////////////////////////////////////////////////////////////////////// + +impl<'a> IntoIterator for Amf0Object<'a> { + type IntoIter = IntoIter<'a>; + type Item = (StringCow<'a>, Amf0Value<'a>); + + #[inline] + fn into_iter(self) -> Self::IntoIter { + IntoIter { + iter: self.map.into_iter(), + } + } +} + +/// An owning iterator over a scuffle_amf0::Amf0Object's entries. +pub struct IntoIter<'a> { + iter: IntoIterImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type IntoIterImpl<'a> = btree_map::IntoIter, Amf0Value<'a>>; +#[cfg(feature = "preserve_order")] +type IntoIterImpl<'a> = indexmap::map::IntoIter, Amf0Value<'a>>; + +delegate_iterator!((IntoIter<'a>) => (StringCow<'a>, Amf0Value<'a>)); + +////////////////////////////////////////////////////////////////////////////// + +/// An iterator over a scuffle_amf0::Amf0Object's keys. +pub struct Keys<'a, 'b> { + iter: KeysImpl<'a, 'b>, +} + +#[cfg(not(feature = "preserve_order"))] +type KeysImpl<'a, 'b> = btree_map::Keys<'a, StringCow<'b>, Amf0Value<'b>>; +#[cfg(feature = "preserve_order")] +type KeysImpl<'a, 'b> = indexmap::map::Keys<'a, StringCow<'b>, Amf0Value<'b>>; + +delegate_iterator!((Keys<'a, 'b>) => &'a StringCow<'b>); + +////////////////////////////////////////////////////////////////////////////// + +/// An iterator over a scuffle_amf0::Amf0Object's values. +pub struct Values<'a, 'b> { + iter: ValuesImpl<'a, 'b>, +} + +#[cfg(not(feature = "preserve_order"))] +type ValuesImpl<'a, 'b> = btree_map::Values<'a, StringCow<'b>, Amf0Value<'b>>; +#[cfg(feature = "preserve_order")] +type ValuesImpl<'a, 'b> = indexmap::map::Values<'a, StringCow<'b>, Amf0Value<'b>>; + +delegate_iterator!((Values<'a, 'b>) => &'a Amf0Value<'b>); + +////////////////////////////////////////////////////////////////////////////// + +/// A mutable iterator over a scuffle_amf0::Amf0Object's values. +pub struct ValuesMut<'a, 'b> { + iter: ValuesMutImpl<'a, 'b>, +} + +#[cfg(not(feature = "preserve_order"))] +type ValuesMutImpl<'a, 'b> = btree_map::ValuesMut<'a, StringCow<'b>, Amf0Value<'b>>; +#[cfg(feature = "preserve_order")] +type ValuesMutImpl<'a, 'b> = indexmap::map::ValuesMut<'a, StringCow<'b>, Amf0Value<'b>>; + +delegate_iterator!((ValuesMut<'a, 'b>) => &'a mut Amf0Value<'b>); + +////////////////////////////////////////////////////////////////////////////// + +/// An owning iterator over a scuffle_amf0::Amf0Object's values. +pub struct IntoValues<'a> { + iter: IntoValuesImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type IntoValuesImpl<'a> = btree_map::IntoValues, Amf0Value<'a>>; +#[cfg(feature = "preserve_order")] +type IntoValuesImpl<'a> = indexmap::map::IntoValues, Amf0Value<'a>>; + +delegate_iterator!((IntoValues<'a>) => Amf0Value<'a>); + +#[cfg(test)] +#[cfg_attr(all(test, coverage_nightly), coverage(off))] +mod tests { + use scuffle_bytes_util::StringCow; + use serde::Deserialize; + + use super::Entry; + use crate::{Amf0Object, Amf0Value}; + + #[test] + fn test_default_map_is_empty() { + let obj: Amf0Object = Amf0Object::default(); + assert!(obj.map.is_empty(), "Default Amf0Object should be empty"); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_capacity_reserves_space_in_preserve_order() { + let capacity = 16; + let obj: Amf0Object = Amf0Object::with_capacity(capacity); + + use indexmap::map::IndexMap; + let map: &IndexMap<_, _> = &obj.map; + assert!( + map.capacity() >= capacity, + "IndexMap should reserve at least the requested capacity" + ); + } + + #[cfg(not(feature = "preserve_order"))] + #[test] + fn test_capacity_ignored_without_preserve_order() { + let capacity = 16; + let obj: Amf0Object = Amf0Object::with_capacity(capacity); + + let key = StringCow::from("foo"); + let value = Amf0Value::String(StringCow::from("bar")); + assert!(obj.map.insert(key.clone(), value.clone()).is_none()); + assert_eq!(obj.map.get(&key), Some(&value)); + } + + #[test] + fn test_clear() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("key1"), Amf0Value::Number(1.0)); + obj.map + .insert(StringCow::from("key2"), Amf0Value::String(StringCow::from("value"))); + obj.map.insert(StringCow::from("key3"), Amf0Value::Boolean(true)); + + assert!(!obj.map.is_empty(), "Map should not be empty before clear"); + obj.clear(); + assert!(obj.map.is_empty(), "Map should be empty after clear"); + } + + #[test] + fn test_contains_key() { + let mut obj = Amf0Object::new(); + + let existing_key = StringCow::from("present"); + let missing_key = "missing"; + + obj.map.insert(existing_key.clone(), Amf0Value::Boolean(true)); + + assert!(obj.contains_key(&existing_key), "Expected key to be found using StringCow"); + assert!(obj.contains_key("present"), "Expected key to be found using &str"); + assert!(!obj.contains_key(missing_key), "Expected missing key to not be found"); + } + + #[test] + fn test_get_mut_valid() { + let mut obj = Amf0Object::new(); + obj.map + .insert(StringCow::from("username"), Amf0Value::String(StringCow::from("old_name"))); + + if let Some(value) = obj.get_mut("username") { + *value = Amf0Value::String(StringCow::from("new_name")); + } else { + panic!("Expected Some(&mut Amf0Value) for existing key"); + } + + assert_eq!(obj.map.get("username"), Some(&Amf0Value::String(StringCow::from("new_name")))); + } + + #[test] + fn test_get_mut_invalid() { + let mut obj = Amf0Object::new(); + assert!(obj.get_mut("nonexistent_key").is_none()); + } + + #[test] + fn test_get_key_value_valid() { + let mut obj = Amf0Object::new(); + let key = StringCow::from("foo"); + let value = Amf0Value::Number(3.21); + + obj.map.insert(key.clone(), value.clone()); + + if let Some((found_key, found_value)) = obj.get_key_value("foo") { + assert_eq!(found_key, &key); + assert_eq!(found_value, &value); + } else { + panic!("Expected Some((key, value)) for existing key"); + } + } + + #[test] + fn test_get_key_value_invalid() { + let obj = Amf0Object::new(); + assert!(obj.get_key_value("missing").is_none()); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_shift_insert_inserts_and_shifts_correctly() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let ret_new = obj.shift_insert(1, StringCow::from("x"), Amf0Value::Number(9.0)); + assert!(ret_new.is_none(), "Inserting a brand‐new key should return None"); + + let keys_after_insert: Vec<&str> = obj.map.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after_insert, + vec!["a", "x", "b", "c"], + "After shifting in 'x' at index 1, order should be [\"a\", \"x\", \"b\", \"c\"]" + ); + + let old_value = obj.shift_insert(0, StringCow::from("b"), Amf0Value::Number(7.0)); + assert_eq!( + old_value, + Some(Amf0Value::Number(2.0)), + "Reinserting 'b' must return its previous value" + ); + + let keys_after_reinsert: Vec<&str> = obj.map.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after_reinsert, + vec!["b", "a", "x", "c"], + "After shifting 'b' to index 0, order should be [\"b\", \"a\", \"x\", \"c\"]" + ); + + assert_eq!( + obj.map.get("b"), + Some(&Amf0Value::Number(7.0)), + "The value for key 'b' should now be updated to 7.0" + ); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_remove_non_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("key1"), Amf0Value::Number(10.0)); + obj.map + .insert(StringCow::from("key2"), Amf0Value::String(StringCow::from("value"))); + obj.map.insert(StringCow::from("key3"), Amf0Value::Boolean(false)); + + let removed = obj.remove("key2"); + assert_eq!( + removed, + Some(Amf0Value::String(StringCow::from("value"))), + "remove should return the value for an existing key" + ); + + assert!(!obj.map.contains_key("key2"), "key2 should no longer exist after removal"); + assert!( + obj.remove("missing").is_none(), + "remove on a nonexistent key should return None" + ); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_remove_preserve_order_swap_remove_behavior() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let removed = obj.remove("b"); + assert_eq!( + removed, + Some(Amf0Value::Number(2.0)), + "remove should return the old value for key 'b'" + ); + + let keys_after: Vec<&str> = obj.map.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after, + vec!["a", "c"], + "After removing 'b', the order should be [\"a\", \"c\"]" + ); + assert!(!obj.map.contains_key("b"), "key 'b' should be gone after removal"); + assert!(obj.map.contains_key("c"), "key 'c' should still be present"); + assert!( + obj.remove("missing").is_none(), + "remove on a nonexistent key should return None" + ); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_remove_entry_non_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("k1"), Amf0Value::Number(1.0)); + obj.map + .insert(StringCow::from("k2"), Amf0Value::String(StringCow::from("v2"))); + obj.map.insert(StringCow::from("k3"), Amf0Value::Boolean(true)); + + let entry = obj.remove_entry("k2"); + assert_eq!( + entry, + Some((StringCow::from("k2"), Amf0Value::String(StringCow::from("v2")))), + "Expected remove_entry to return the removed key and value" + ); + + assert!(!obj.map.contains_key("k2"), "Key 'k2' should be gone after remove_entry"); + assert!( + obj.remove_entry("missing").is_none(), + "remove_entry on nonexistent key should return None" + ); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_remove_entry_preserve_order_swap_remove_entry() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(10.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(20.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(30.0)); + obj.map.insert(StringCow::from("d"), Amf0Value::Number(40.0)); + + let entry = obj.remove_entry("b"); + assert_eq!( + entry, + Some((StringCow::from("b"), Amf0Value::Number(20.0))), + "Expected remove_entry to return the removed key 'b' and its value" + ); + + let keys_after: Vec<&str> = obj.map.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after, + vec!["a", "d", "c"], + "After swap_remove_entry on 'b', order should be [\"a\", \"d\", \"c\"]" + ); + assert!(!obj.map.contains_key("b"), "Key 'b' should be gone after remove_entry"); + assert!(obj.map.contains_key("d"), "Key 'd' should still be present"); + assert!(obj.map.contains_key("c"), "Key 'c' should still be present"); + assert!( + obj.remove_entry("missing").is_none(), + "remove_entry on nonexistent key should return None" + ); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_swap_remove_existing_and_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("one"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("two"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("three"), Amf0Value::Number(3.0)); + obj.map.insert(StringCow::from("four"), Amf0Value::Number(4.0)); + + let removed = obj.swap_remove("two"); + assert_eq!( + removed, + Some(Amf0Value::Number(2.0)), + "swap_remove should return the removed value for key 'two'" + ); + + let keys_after: Vec<&str> = obj.map.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after, + vec!["one", "four", "three"], + "After swap_remove on 'two', order should be [\"one\", \"four\", \"three\"]" + ); + assert!(!obj.map.contains_key("two"), "Key 'two' should no longer exist"); + assert!(obj.map.contains_key("four"), "Key 'four' should still be present"); + assert!( + obj.swap_remove("absent").is_none(), + "swap_remove on nonexistent key should return None" + ); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_swap_remove_entry_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(10.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(20.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(30.0)); + obj.map.insert(StringCow::from("d"), Amf0Value::Number(40.0)); + + let entry = obj.swap_remove_entry("b"); + assert_eq!( + entry, + Some((StringCow::from("b"), Amf0Value::Number(20.0))), + "Expected swap_remove_entry to return the removed key 'b' and its value" + ); + + let keys_after: Vec<&str> = obj.map.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after, + vec!["a", "d", "c"], + "After swap_remove_entry on 'b', order should be [\"a\", \"d\", \"c\"]" + ); + + assert!(!obj.map.contains_key("b"), "Key 'b' should no longer exist"); + assert!(obj.map.contains_key("d"), "Key 'd' should still be present"); + assert!(obj.map.contains_key("c"), "Key 'c' should still be present"); + assert!( + obj.swap_remove_entry("missing").is_none(), + "swap_remove_entry on nonexistent key should return None" + ); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_shift_remove_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + obj.map.insert(StringCow::from("d"), Amf0Value::Number(4.0)); + + let removed = obj.shift_remove("b"); + assert_eq!( + removed, + Some(Amf0Value::Number(2.0)), + "shift_remove should return the removed value for key 'b'" + ); + + let keys_after: Vec<&str> = obj.map.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after, + vec!["a", "c", "d"], + "After shift_remove on 'b', order should be [\"a\", \"c\", \"d\"]" + ); + assert!(!obj.map.contains_key("b"), "Key 'b' should no longer exist"); + assert!(obj.map.contains_key("c"), "Key 'c' should still be present"); + assert!(obj.map.contains_key("d"), "Key 'd' should still be present"); + assert!( + obj.shift_remove("missing").is_none(), + "shift_remove on nonexistent key should return None" + ); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_shift_remove_entry_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + obj.map.insert(StringCow::from("d"), Amf0Value::Number(4.0)); + + let entry = obj.shift_remove_entry("b"); + assert_eq!( + entry, + Some((StringCow::from("b"), Amf0Value::Number(2.0))), + "Expected shift_remove_entry to return removed key 'b' and its value" + ); + + let keys_after: Vec<&str> = obj.map.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after, + vec!["a", "c", "d"], + "After shift_remove_entry on 'b', order should be [\"a\", \"c\", \"d\"]" + ); + assert!(!obj.map.contains_key("b"), "Key 'b' should no longer exist"); + assert!(obj.map.contains_key("c"), "Key 'c' should still be present"); + assert!(obj.map.contains_key("d"), "Key 'd' should still be present"); + assert!( + obj.shift_remove_entry("missing").is_none(), + "shift_remove_entry on nonexistent key should return None" + ); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_append_non_preserve_order_merges_and_clears_other() { + let mut obj1 = Amf0Object::new(); + let mut obj2 = Amf0Object::new(); + + obj1.map.insert(StringCow::from("k1"), Amf0Value::Number(1.0)); + obj1.map.insert(StringCow::from("k2"), Amf0Value::Number(2.0)); + + obj2.map.insert(StringCow::from("k2"), Amf0Value::Number(22.0)); // collision + obj2.map.insert(StringCow::from("k3"), Amf0Value::Number(3.0)); + obj1.append(&mut obj2); + + assert_eq!(obj1.map.len(), 3); + assert_eq!(obj1.map.get("k1"), Some(&Amf0Value::Number(1.0))); + assert_eq!(obj1.map.get("k2"), Some(&Amf0Value::Number(22.0))); + assert_eq!(obj1.map.get("k3"), Some(&Amf0Value::Number(3.0))); + assert!(obj2.map.is_empty(), "obj2 should be emptied after append"); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_append_preserve_order_extends_and_clears_other() { + let mut obj1 = Amf0Object::new(); + let mut obj2 = Amf0Object::new(); + + obj1.map.insert(StringCow::from("a"), Amf0Value::Number(10.0)); + obj1.map.insert(StringCow::from("b"), Amf0Value::Number(20.0)); + + obj2.map.insert(StringCow::from("b"), Amf0Value::Number(200.0)); // collision + obj2.map.insert(StringCow::from("c"), Amf0Value::Number(30.0)); + obj1.append(&mut obj2); + + let keys_after: Vec<&str> = obj1.map.keys().map(|k| k.as_ref()).collect(); + let values_after: Vec<&Amf0Value> = obj1.map.values().collect(); + + assert_eq!( + keys_after, + vec!["a", "b", "c"], + "Order should preserve original keys, with duplicates replaced, then obj2's new keys" + ); + + assert_eq!( + values_after, + vec![&Amf0Value::Number(10.0), &Amf0Value::Number(200.0), &Amf0Value::Number(30.0),] + ); + + assert!(obj2.map.is_empty(), "obj2 should be emptied after append"); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_entry_vacant_inserts_value_non_preserve_order() { + let mut obj = Amf0Object::new(); + + match obj.entry("new_key") { + Entry::Vacant(mut vacant) => { + vacant.insert(Amf0Value::Number(42.0)); + } + Entry::Occupied(_) => panic!("Expected Vacant for a missing key"), + } + + assert_eq!( + obj.map.get("new_key"), + Some(&Amf0Value::Number(42.0)), + "Value inserted via entry should be retrievable" + ); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_entry_vacant_inserts_value_preserve_order() { + let mut obj = Amf0Object::new(); + match obj.entry("vacant_key") { + Entry::Vacant(vacant) => { + vacant.insert(Amf0Value::Number(123.0)); + } + Entry::Occupied(_) => panic!("Expected Vacant for a missing key"), + } + + let keys: Vec<&str> = obj.map.keys().map(|k| k.as_ref()).collect(); + assert_eq!(keys, vec!["vacant_key"]); + assert_eq!(obj.map.get("vacant_key"), Some(&Amf0Value::Number(123.0))); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_entry_occupied_modifies_existing_value_preserve_order() { + let mut obj = Amf0Object::new(); + obj.map.insert(StringCow::from("existing"), Amf0Value::Number(1.0)); + + match obj.entry("existing") { + Entry::Vacant(_) => panic!("Expected Occupied for an existing key"), + Entry::Occupied(mut occupied) => { + *occupied.get_mut() = Amf0Value::Number(99.0); + } + } + + assert_eq!( + obj.map.get("existing"), + Some(&Amf0Value::Number(99.0)), + "Occupied entry modification should update the stored value" + ); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_is_empty_non_preserve_order() { + let mut obj = Amf0Object::new(); + assert!(obj.is_empty(), "Newly created Amf0Object should be empty"); + + obj.map.insert(StringCow::from("key"), Amf0Value::Number(5.0)); + assert!(!obj.is_empty(), "Amf0Object should not be empty after insertion"); + + obj.clear(); + assert!(obj.is_empty(), "Amf0Object should be empty after clear"); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_is_empty_preserve_order() { + let mut obj = Amf0Object::new(); + assert!(obj.is_empty(), "Newly created Amf0Object should be empty"); + + obj.map.insert(StringCow::from("alpha"), Amf0Value::Number(1.0)); + assert!(!obj.is_empty(), "Amf0Object should not be empty after insertion"); + + obj.shift_remove("alpha"); + assert!(obj.is_empty(), "Amf0Object should be empty after removing the only key"); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_iter_mut_non_preserve_order() { + let mut obj = Amf0Object::new(); + obj.map.insert(StringCow::from("k1"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("k2"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("k3"), Amf0Value::Number(3.0)); + + for (_k, v) in obj.iter_mut() { + if let Amf0Value::Number(ref mut n) = *v { + *n += 10.0; + } + } + + assert_eq!(obj.map.get("k1"), Some(&Amf0Value::Number(11.0))); + assert_eq!(obj.map.get("k2"), Some(&Amf0Value::Number(12.0))); + assert_eq!(obj.map.get("k3"), Some(&Amf0Value::Number(13.0))); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_iter_mut_preserve_order() { + let mut obj = Amf0Object::new(); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(5.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(6.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(7.0)); + + let keys: Vec<&str> = obj.iter_mut().map(|(k, _)| k.as_ref()).collect(); + assert_eq!(keys, vec!["a", "b", "c"], "Iteration order should follow insertion order"); + + for (_k, v) in obj.iter_mut() { + *v = Amf0Value::Number(0.0); + } + + assert_eq!(obj.map.get("a"), Some(&Amf0Value::Number(0.0))); + assert_eq!(obj.map.get("b"), Some(&Amf0Value::Number(0.0))); + assert_eq!(obj.map.get("c"), Some(&Amf0Value::Number(0.0))); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_keys_non_preserve_order_sorted() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!(keys, vec!["a", "b", "c"], "Expected keys sorted lexicographically"); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_keys_preserve_order_insertion() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!(keys, vec!["b", "a", "c"], "Expected keys in insertion order"); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_values_non_preserve_order_sorted_by_key() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let values: Vec<&Amf0Value> = obj.values().collect(); + assert_eq!( + values, + vec![&Amf0Value::Number(1.0), &Amf0Value::Number(2.0), &Amf0Value::Number(3.0)], + "Expected values in order of sorted keys" + ); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_values_preserve_order_insertion() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let values: Vec<&Amf0Value> = obj.values().collect(); + assert_eq!( + values, + vec![&Amf0Value::Number(2.0), &Amf0Value::Number(1.0), &Amf0Value::Number(3.0)], + "Expected values in insertion order" + ); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_values_mut_non_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + for value in obj.values_mut() { + if let Amf0Value::Number(ref mut n) = *value { + *n *= 10.0; + } + } + + let sorted_keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!(sorted_keys, vec!["a", "b", "c"]); + + assert_eq!(obj.map.get("a"), Some(&Amf0Value::Number(10.0))); + assert_eq!(obj.map.get("b"), Some(&Amf0Value::Number(20.0))); + assert_eq!(obj.map.get("c"), Some(&Amf0Value::Number(30.0))); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_values_mut_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + for value in obj.values_mut() { + *value = Amf0Value::Number(0.0); + } + + let keys_in_order: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!(keys_in_order, vec!["b", "a", "c"]); + + assert_eq!(obj.map.get("b"), Some(&Amf0Value::Number(0.0))); + assert_eq!(obj.map.get("a"), Some(&Amf0Value::Number(0.0))); + assert_eq!(obj.map.get("c"), Some(&Amf0Value::Number(0.0))); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_into_values_consume_and_return_sorted_values() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let values: Vec = obj.into_values().collect(); + assert_eq!( + values, + vec![Amf0Value::Number(1.0), Amf0Value::Number(2.0), Amf0Value::Number(3.0)], + "into_values should yield values in key‐sorted order for BTreeMap" + ); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_into_values_consume_and_return_insertion_order_values() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let values: Vec = obj.into_values().collect(); + assert_eq!( + values, + vec![Amf0Value::Number(2.0), Amf0Value::Number(1.0), Amf0Value::Number(3.0)], + "into_values should yield values in insertion order for IndexMap" + ); + } + + #[test] + #[cfg(not(feature = "preserve_order"))] + fn test_retain_non_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + obj.map.insert(StringCow::from("d"), Amf0Value::Number(4.0)); + + obj.retain(|_key, value| if let Amf0Value::Number(n) = *value { n > 2.0 } else { false }); + + let remaining_keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!(remaining_keys, vec!["c", "d"]); + + assert_eq!(obj.map.get("c"), Some(&Amf0Value::Number(3.0))); + assert_eq!(obj.map.get("d"), Some(&Amf0Value::Number(4.0))); + + assert!(!obj.map.contains_key("a")); + assert!(!obj.map.contains_key("b")); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_retain_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("x1"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("x2"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("x3"), Amf0Value::Number(3.0)); + obj.map.insert(StringCow::from("x4"), Amf0Value::Number(4.0)); + + obj.retain(|_key, value| { + if let Amf0Value::Number(n) = *value { + (n as i64) % 2 == 0 + } else { + false + } + }); + + let remaining_keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!(remaining_keys, vec!["x2", "x4"]); + + assert_eq!(obj.map.get("x2"), Some(&Amf0Value::Number(2.0))); + assert_eq!(obj.map.get("x4"), Some(&Amf0Value::Number(4.0))); + + assert!(!obj.map.contains_key("x1")); + assert!(!obj.map.contains_key("x3")); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_sort_keys_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("d"), Amf0Value::Number(4.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let before: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!(before, vec!["b", "d", "a", "c"]); + + obj.sort_keys(); + + let after: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!(after, vec!["a", "b", "c", "d"]); + + assert_eq!(obj.map.get("a"), Some(&Amf0Value::Number(1.0))); + assert_eq!(obj.map.get("b"), Some(&Amf0Value::Number(2.0))); + assert_eq!(obj.map.get("c"), Some(&Amf0Value::Number(3.0))); + assert_eq!(obj.map.get("d"), Some(&Amf0Value::Number(4.0))); + } + + #[test] + fn test_index_returns_value_for_existing_key() { + let mut obj = Amf0Object::new(); + obj.map.insert(StringCow::from("alpha"), Amf0Value::Number(3.21)); + + let v: &Amf0Value = &obj["alpha"]; + assert_eq!(v, &Amf0Value::Number(3.21)); + } + + #[test] + #[should_panic] + fn test_index_panics_on_missing_key() { + let obj = Amf0Object::new(); + // Indexing a non‐existent key should panic + let _ = &obj["nonexistent"]; + } + + #[test] + fn test_index_mut_modifies_existing_value() { + let mut obj = Amf0Object::new(); + obj.map.insert(StringCow::from("key1"), Amf0Value::Number(5.0)); + + obj["key1"] = Amf0Value::Number(42.0); + + assert_eq!( + obj.map.get("key1"), + Some(&Amf0Value::Number(42.0)), + "IndexMut should allow updating the value" + ); + } + + #[test] + #[should_panic(expected = "no entry found for key")] + fn test_index_mut_panics_on_missing_key() { + let mut obj = Amf0Object::new(); + obj["nonexistent"] = Amf0Value::Boolean(true); + } + + #[cfg(not(feature = "preserve_order"))] + #[test] + fn test_debug_formats_sorted_map_non_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + + insta::assert_debug_snapshot!(obj, @r#" + { + Ref( + "b", + ): Number( + 2.0, + ), + Ref( + "a", + ): Number( + 1.0, + ), + } + "#); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_debug_formats_insertion_order_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + + insta::assert_debug_snapshot!(obj, @r#" + { + Ref( + "b", + ): Number( + 2.0, + ), + Ref( + "a", + ): Number( + 1.0, + ), + } + "#); + } + + #[cfg(feature = "serde")] + #[test] + fn test_visit_unit_returns_empty_object() { + use serde::de::value::{Error as DeError, UnitDeserializer}; + + // Using Serde's built-in UnitDeserializer, which directly calls visit_unit() + let deser: UnitDeserializer = UnitDeserializer::new(); + let obj = Amf0Object::deserialize(deser).unwrap(); + + assert!(obj.is_empty(), "visit_unit should produce an empty Amf0Object"); + } + + #[cfg(feature = "serde")] + #[test] + fn test_expecting_error_message() { + use serde::de::value::Error as DeError; + use serde::de::{self}; + + struct WrongTypeDeserializer; + + impl<'de> de::Deserializer<'de> for WrongTypeDeserializer { + type Error = DeError; + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf + option unit unit_struct newtype_struct seq tuple tuple_struct map struct + enum identifier ignored_any + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + Err(de::Error::invalid_type(de::Unexpected::Bool(true), &visitor)) + } + } + + let err = Amf0Object::deserialize(WrongTypeDeserializer).unwrap_err(); + let msg = err.to_string(); + + assert!( + msg.contains("a map"), + "expecting() should include 'a map' in the error message, got: '{}'", + msg + ); + } + + #[cfg(not(feature = "preserve_order"))] + #[test] + fn test_extend_non_preserve_order_inserts_and_sorts() { + let mut obj = Amf0Object::new(); + + let items = vec![ + (StringCow::from("b"), Amf0Value::Number(2.0)), + (StringCow::from("a"), Amf0Value::Number(1.0)), + (StringCow::from("a"), Amf0Value::Number(5.0)), + (StringCow::from("c"), Amf0Value::Number(3.0)), + ]; + + obj.extend(items); + + let keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys, + vec!["a", "b", "c"], + "Non-preserve_order: keys should be sorted lexicographically" + ); + + assert_eq!(obj.map.get("a"), Some(&Amf0Value::Number(5.0))); + assert_eq!(obj.map.get("b"), Some(&Amf0Value::Number(2.0))); + assert_eq!(obj.map.get("c"), Some(&Amf0Value::Number(3.0))); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_extend_preserve_order_inserts_and_overwrites() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + + let items = vec![ + (StringCow::from("a"), Amf0Value::Number(5.0)), + (StringCow::from("b"), Amf0Value::Number(2.0)), + (StringCow::from("a"), Amf0Value::Number(7.0)), + (StringCow::from("c"), Amf0Value::Number(3.0)), + ]; + + obj.extend(items); + + let keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys, + vec!["a", "b", "c"], + "Preserve_order: key 'a' remains at front, then 'b', then 'c'" + ); + + assert_eq!(obj.map.get("a"), Some(&Amf0Value::Number(7.0))); + assert_eq!(obj.map.get("b"), Some(&Amf0Value::Number(2.0))); + assert_eq!(obj.map.get("c"), Some(&Amf0Value::Number(3.0))); + } + + #[cfg(not(feature = "preserve_order"))] + #[test] + fn test_keys_iterator_next_back_and_len_non_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let mut keys_iter = obj.keys(); + + assert_eq!(keys_iter.len(), 3); + + let last = keys_iter.next_back().expect("Expected a last key"); + assert_eq!(last.as_ref(), "c"); + + assert_eq!(keys_iter.len(), 2); + + let first = keys_iter.next().expect("Expected a first key"); + assert_eq!(first.as_ref(), "a"); + + assert_eq!(keys_iter.len(), 1); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_keys_iterator_next_back_and_len_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + let mut keys_iter = obj.keys(); + + assert_eq!(keys_iter.len(), 3); + + let last = keys_iter.next_back().expect("Expected a last key"); + assert_eq!(last.as_ref(), "c"); + + assert_eq!(keys_iter.len(), 2); + + let first = keys_iter.next().expect("Expected a first key"); + assert_eq!(first.as_ref(), "b"); + assert_eq!(keys_iter.len(), 1); + } + + #[cfg(feature = "serde")] + #[test] + fn test_into_deserializer_returns_self() { + use serde::de::IntoDeserializer; + + use crate::Amf0Error; + + let obj = Amf0Object::new(); + + let deser: &Amf0Object = <&Amf0Object as IntoDeserializer>::into_deserializer(&obj); + assert!( + std::ptr::eq(deser, &obj), + "into_deserializer should return the exact same &Amf0Object reference" + ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_into_deserializer_as_deserializer_errors_on_primitive() { + use serde::Deserialize; + use serde::de::IntoDeserializer; + + let obj = Amf0Object::new(); + let deser = obj.into_deserializer(); + + let result = ::deserialize(&deser); + assert!(result.is_err(), "Deserializing a bool from an Amf0Object should error"); + } + + #[derive(Debug, Deserialize, PartialEq)] + struct TestObject { + string: String, + number: u32, + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_any_for_struct() { + let mut obj = Amf0Object::new(); + obj.map + .insert(StringCow::from("string"), Amf0Value::String(StringCow::from("apple"))); + obj.map.insert(StringCow::from("number"), Amf0Value::Number(30.0)); + + let testobj: TestObject = Deserialize::deserialize(&obj).unwrap(); + assert_eq!( + testobj, + TestObject { + string: "apple".into(), + number: 30, + } + ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_any_errors_on_missing_field() { + let mut obj = Amf0Object::new(); + obj.map + .insert(StringCow::from("string"), Amf0Value::String(StringCow::from("banana"))); + + let result: Result = Deserialize::deserialize(&obj); + assert!(result.is_err(), "Missing required field should cause an error"); + } + + #[test] + fn test_entry_key_occupied() { + let mut obj = Amf0Object::new(); + obj.map.insert(StringCow::from("serde"), Amf0Value::Number(42.0)); + let entry = obj.entry("serde"); + + assert_eq!(entry.key(), &StringCow::from("serde")); + } + + #[test] + fn test_or_insert_vacant_and_occupied() { + let mut obj = Amf0Object::new(); + + let v = obj.entry("k").or_insert(Amf0Value::Number(10.0)); + assert_eq!(*v, Amf0Value::Number(10.0)); + assert_eq!( + obj.map.get("k"), + Some(&Amf0Value::Number(10.0)), + "Value should be inserted for vacant entry" + ); + + let v2 = obj.entry("k").or_insert(Amf0Value::Number(20.0)); + assert_eq!(*v2, Amf0Value::Number(10.0)); + + *v2 = Amf0Value::Number(30.0); + assert_eq!( + obj.map.get("k"), + Some(&Amf0Value::Number(30.0)), + "Value should be updated to 30.0 via the occupied branch" + ); + } + + #[test] + fn test_or_insert_with_vacant_and_occupied() { + use std::cell::Cell; + + let mut obj = Amf0Object::new(); + + let called = Cell::new(false); + let default_closure = || { + called.set(true); + Amf0Value::Number(5.0) + }; + + let v = obj.entry("key1").or_insert_with(default_closure); + assert!(called.get(), "Closure should have been called for vacant entry"); + assert_eq!(*v, Amf0Value::Number(5.0)); + assert_eq!( + obj.map.get("key1"), + Some(&Amf0Value::Number(5.0)), + "Value for 'key1' should be inserted as 5.0" + ); + + called.set(false); + let v2 = obj.entry("key1").or_insert_with(|| { + called.set(true); + Amf0Value::Number(10.0) + }); + assert!(!called.get(), "Closure should not be called for occupied entry"); + assert_eq!(*v2, Amf0Value::Number(5.0)); + + *v2 = Amf0Value::Number(7.0); + assert_eq!( + obj.map.get("key1"), + Some(&Amf0Value::Number(7.0)), + "Value for 'key1' should be updated to 7.0 via occupied branch" + ); + } + + #[test] + fn test_and_modify_on_occupied_entry() { + let mut obj = Amf0Object::new(); + obj.map.insert(StringCow::from("key1"), Amf0Value::Number(1.0)); + + obj.entry("key1").and_modify(|v| { + if let Amf0Value::Number(n) = v { + *n = 5.0; + } + }); + + assert_eq!( + obj.map.get("key1"), + Some(&Amf0Value::Number(5.0)), + "and_modify should change the existing value from 1.0 to 5.0" + ); + } + + #[test] + fn test_and_modify_on_vacant_entry() { + let mut obj = Amf0Object::new(); + assert!(!obj.map.contains_key("missing")); + + obj.entry("missing").and_modify(|v| { + if let Amf0Value::Number(n) = v { + *n = 100.0; + } + }); + + assert!( + !obj.map.contains_key("missing"), + "and_modify on a vacant entry should not insert anything" + ); + } + + #[test] + fn test_occupied_entry_key_and_get() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("my_key"), Amf0Value::Number(123.0)); + + match obj.entry("my_key") { + Entry::Occupied(occupied) => { + let key_ref: &StringCow = occupied.key(); + assert_eq!(key_ref.as_ref(), "my_key"); + + let value_ref: &Amf0Value = occupied.get(); + assert_eq!(value_ref, &Amf0Value::Number(123.0)); + } + Entry::Vacant(_) => panic!("Expected Occupied variant for an existing key"), + } + } + + #[test] + fn test_occupied_entry_into_mut_and_insert() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("key1"), Amf0Value::Number(10.0)); + + if let Entry::Occupied(occ) = obj.entry("key1") { + let value_ref: &mut Amf0Value = occ.into_mut(); + if let Amf0Value::Number(n) = value_ref { + *n = 20.0; + } + } else { + panic!("Expected Occupied variant"); + } + + assert_eq!( + obj.map.get("key1"), + Some(&Amf0Value::Number(20.0)), + "into_mut should allow modifying the existing value" + ); + + if let Entry::Occupied(mut occ2) = obj.entry("key1") { + let old = occ2.insert(Amf0Value::Number(30.0)); + assert_eq!(old, Amf0Value::Number(20.0), "insert should return the old value"); + } else { + panic!("Expected Occupied variant for insert test"); + } + + assert_eq!( + obj.map.get("key1"), + Some(&Amf0Value::Number(30.0)), + "insert should replace with the new value" + ); + } + + #[cfg(not(feature = "preserve_order"))] + #[test] + fn test_occupied_entry_remove_non_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("key1"), Amf0Value::Number(1.0)); + + if let Entry::Occupied(occ) = obj.entry("key1") { + let removed_value = occ.remove(); + assert_eq!(removed_value, Amf0Value::Number(1.0), "remove should return the old value"); + } else { + panic!("Expected Occupied variant"); + } + + assert!(!obj.map.contains_key("key1"), "Key should be removed after calling remove()"); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_occupied_entry_remove_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(10.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(20.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(30.0)); + + if let Entry::Occupied(occ) = obj.entry("b") { + let removed_value = occ.remove(); + assert_eq!(removed_value, Amf0Value::Number(20.0), "remove should return the old value"); + } else { + panic!("Expected Occupied variant"); + } + + let keys_after: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after, + vec!["a", "c"], + "After remove, key 'b' should be gone and 'c' should occupy its position" + ); + assert!(!obj.map.contains_key("b"), "Key 'b' should be removed"); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_occupied_entry_swap_remove_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("x"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("y"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("z"), Amf0Value::Number(3.0)); + + if let Entry::Occupied(occ) = obj.entry("y") { + let removed = occ.swap_remove(); + assert_eq!( + removed, + Amf0Value::Number(2.0), + "swap_remove should return the old value for 'y'" + ); + } else { + panic!("Expected Occupied variant for key 'y'"); + } + + let keys_after: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after, + vec!["x", "z"], + "After swap_remove, 'y' should be gone and 'z' should occupy its slot" + ); + assert!(!obj.map.contains_key("y"), "Key 'y' should be removed"); + assert!(obj.map.contains_key("z"), "Key 'z' should still be present"); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_occupied_entry_shift_remove_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + obj.map.insert(StringCow::from("d"), Amf0Value::Number(4.0)); + + if let Entry::Occupied(occ) = obj.entry("b") { + let removed = occ.shift_remove(); + assert_eq!( + removed, + Amf0Value::Number(2.0), + "shift_remove should return the old value for 'b'" + ); + } else { + panic!("Expected Occupied variant for key 'b'"); + } + + let keys_after: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + keys_after, + vec!["a", "c", "d"], + "After shift_remove, 'b' should be gone and order should be [\"a\", \"c\", \"d\"]" + ); + assert!(!obj.map.contains_key("b"), "Key 'b' should have been removed"); + assert!(obj.map.contains_key("c"), "Key 'c' should still be present"); + assert!(obj.map.contains_key("d"), "Key 'd' should still be present"); + } + + #[cfg(not(feature = "preserve_order"))] + #[test] + fn test_occupied_entry_remove_entry_non_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("key1"), Amf0Value::Number(10.0)); + + if let Entry::Occupied(occ) = obj.entry("key1") { + let (removed_key, removed_value) = occ.remove_entry(); + assert_eq!(removed_key, StringCow::from("key1")); + assert_eq!(removed_value, Amf0Value::Number(10.0)); + } else { + panic!("Expected Occupied variant for 'key1'"); + } + + assert!(!obj.map.contains_key("key1")); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_occupied_entry_remove_entry_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + if let Entry::Occupied(occ) = obj.entry("b") { + let (removed_key, removed_value) = occ.remove_entry(); + assert_eq!(removed_key, StringCow::from("b")); + assert_eq!(removed_value, Amf0Value::Number(2.0)); + } else { + panic!("Expected Occupied variant for 'b'"); + } + + let remaining_keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + remaining_keys, + vec!["a", "c"], + "After remove_entry on 'b', keys should be [\"a\", \"c\"]" + ); + assert!(!obj.map.contains_key("b"), "Key 'b' should be gone"); + assert!(obj.map.contains_key("c"), "Key 'c' should still be present"); + } + + #[cfg(feature = "preserve_order")] + #[test] + fn test_swap_remove_entry_preserve_order_occupied_entry() { + let mut obj = Amf0Object::new(); + + // Insert multiple entries in insertion order: ["a", "b", "c", "d"] + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + obj.map.insert(StringCow::from("d"), Amf0Value::Number(4.0)); + + // Remove "b" via OccupiedEntry::swap_remove_entry() + if let Entry::Occupied(occ) = obj.entry("b") { + let (removed_key, removed_value) = occ.swap_remove_entry(); + assert_eq!(removed_key, StringCow::from("b")); + assert_eq!(removed_value, Amf0Value::Number(2.0)); + } else { + panic!("Expected Occupied variant for key 'b'"); + } + + // After swap_remove_entry, "d" should replace "b"'s position; remaining keys: ["a", "d", "c"] + let remaining_keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + remaining_keys, + vec!["a", "d", "c"], + "After swap_remove_entry on 'b', keys should be [\"a\", \"d\", \"c\"]" + ); + assert!(!obj.map.contains_key("b"), "Key 'b' should have been removed"); + assert!(obj.map.contains_key("d"), "Key 'd' should still be present"); + assert!(obj.map.contains_key("c"), "Key 'c' should still be present"); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_occupied_entry_shift_remove_entry_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + obj.map.insert(StringCow::from("d"), Amf0Value::Number(4.0)); + + if let Entry::Occupied(occ) = obj.entry("b") { + let (removed_key, removed_value) = occ.shift_remove_entry(); + assert_eq!( + removed_key, + StringCow::from("b"), + "shift_remove_entry should return the removed key 'b'" + ); + assert_eq!( + removed_value, + Amf0Value::Number(2.0), + "shift_remove_entry should return the correct value for 'b'" + ); + } else { + panic!("Expected Occupied variant for key 'b'"); + } + + let remaining_keys: Vec<&str> = obj.keys().map(|k| k.as_ref()).collect(); + assert_eq!( + remaining_keys, + vec!["a", "c", "d"], + "After shift_remove_entry on 'b', keys should be [\"a\", \"c\", \"d\"]" + ); + assert!(!obj.map.contains_key("b"), "Key 'b' should have been removed"); + assert!(obj.map.contains_key("c"), "Key 'c' should still be present"); + assert!(obj.map.contains_key("d"), "Key 'd' should still be present"); + } + + #[test] + fn test_into_iterator_mutates_values_non_preserve_order() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("b"), Amf0Value::Number(2.0)); + obj.map.insert(StringCow::from("a"), Amf0Value::Number(1.0)); + obj.map.insert(StringCow::from("c"), Amf0Value::Number(3.0)); + + for (_, value) in &mut obj { + if let Amf0Value::Number(n) = value { + *n += 10.0; + } + } + + assert_eq!(obj.map.get("a"), Some(&Amf0Value::Number(11.0))); + assert_eq!(obj.map.get("b"), Some(&Amf0Value::Number(12.0))); + assert_eq!(obj.map.get("c"), Some(&Amf0Value::Number(13.0))); + } + + #[test] + #[cfg(feature = "preserve_order")] + fn test_into_iterator_preserve_order_counts_and_mutates() { + let mut obj = Amf0Object::new(); + + obj.map.insert(StringCow::from("x"), Amf0Value::Number(5.0)); + obj.map.insert(StringCow::from("y"), Amf0Value::Number(6.0)); + obj.map.insert(StringCow::from("z"), Amf0Value::Number(7.0)); + + let mut seen_keys = Vec::new(); + for (key, value) in &mut obj { + seen_keys.push(key.as_ref().to_string()); + *value = Amf0Value::Number(0.0); + } + + assert_eq!(seen_keys, vec!["x", "y", "z"]); + assert_eq!(obj.map.get("x"), Some(&Amf0Value::Number(0.0))); + assert_eq!(obj.map.get("y"), Some(&Amf0Value::Number(0.0))); + assert_eq!(obj.map.get("z"), Some(&Amf0Value::Number(0.0))); + } +} diff --git a/crates/amf0/src/ser.rs b/crates/amf0/src/ser.rs index 75d357b90..9b2e14eea 100644 --- a/crates/amf0/src/ser.rs +++ b/crates/amf0/src/ser.rs @@ -86,7 +86,8 @@ where } fn serialize_char(self, v: char) -> Result { - self.serialize_u8(v as u8) + let mut buf: [u8; 4] = [0; 4]; + self.serialize_str(v.encode_utf8(&mut buf)) } fn serialize_str(self, v: &str) -> Result { @@ -582,9 +583,10 @@ mod tests { let value = 'a'; let bytes = to_bytes(&value).unwrap(); - let mut expected = vec![Amf0Marker::Number as u8]; - expected.extend((b'a' as f64).to_be_bytes()); - #[rustfmt::skip] + let mut expected = vec![Amf0Marker::String as u8]; + expected.extend((1u16).to_be_bytes()); + expected.extend(b"a"); + assert_eq!(bytes, expected); } diff --git a/crates/amf0/src/value.rs b/crates/amf0/src/value.rs index db09210af..15609544e 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -1,18 +1,13 @@ //! AMF0 value types. -use std::borrow::Cow; -use std::collections::HashMap; use std::io; use scuffle_bytes_util::StringCow; +use serde::de::IntoDeserializer; use crate::Amf0Error; use crate::encoder::Amf0Encoder; - -/// Represents any AMF0 object. -pub type Amf0Object<'a> = HashMap, Amf0Value<'a>>; -/// Represents any AMF0 array. -pub type Amf0Array<'a> = Cow<'a, [Amf0Value<'a>]>; +use crate::object::Amf0Object; /// Represents any AMF0 value. #[derive(Debug, PartialEq, Clone)] @@ -28,7 +23,7 @@ pub enum Amf0Value<'a> { /// AMF0 Null. Null, /// AMF0 Array. - Array(Amf0Array<'a>), + Array(Vec>), } impl Amf0Value<'_> { @@ -42,7 +37,7 @@ impl Amf0Value<'_> { Amf0Value::Object(v.into_iter().map(|(k, v)| (k.into_owned(), v.into_owned())).collect()) } Amf0Value::Null => Amf0Value::Null, - Amf0Value::Array(v) => Amf0Value::Array(v.into_owned().into_iter().map(|v| v.into_owned()).collect()), + Amf0Value::Array(v) => Amf0Value::Array(v.into_iter().map(|v| v.into_owned()).collect()), } } @@ -87,27 +82,13 @@ impl<'a> From> for Amf0Value<'a> { // owned array impl<'a> From>> for Amf0Value<'a> { fn from(value: Vec>) -> Self { - Amf0Value::Array(Cow::Owned(value)) - } -} - -// borrowed array -impl<'a> From<&'a [Amf0Value<'a>]> for Amf0Value<'a> { - fn from(value: &'a [Amf0Value<'a>]) -> Self { - Amf0Value::Array(Cow::Borrowed(value)) - } -} - -// cow array -impl<'a> From> for Amf0Value<'a> { - fn from(value: Amf0Array<'a>) -> Self { Amf0Value::Array(value) } } impl<'a> FromIterator> for Amf0Value<'a> { fn from_iter>>(iter: T) -> Self { - Amf0Value::Array(Cow::Owned(iter.into_iter().collect())) + Amf0Value::Array(iter.into_iter().collect()) } } @@ -223,7 +204,9 @@ impl<'de> serde::de::Deserialize<'de> for Amf0Value<'de> { where A: serde::de::MapAccess<'de>, { - let mut object = HashMap::new(); + use crate::object::Amf0Object; + + let mut object = Amf0Object::new(); while let Some((key, value)) = map.next_entry()? { object.insert(key, value); @@ -278,15 +261,183 @@ impl serde::ser::Serialize for Amf0Value<'_> { } } +#[cfg(feature = "serde")] +macro_rules! impl_de_number { + ($deserializser_fn:ident, $visit_fn:ident) => { + fn $deserializser_fn(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if let Amf0Value::Number(n) = self { + let n = *(&n as &f64); + if let Some(n) = ::num_traits::cast(n) { + visitor.$visit_fn(n) + } else { + visitor.visit_f64(n) + } + } else { + self.deserialize_any(visitor) + } + } + }; +} + +#[cfg(feature = "serde")] +macro_rules! impl_deserializer { + ($ty:ty) => { + impl<'de> serde::Deserializer<'de> for $ty { + type Error = Amf0Error; + + serde::forward_to_deserialize_any! { + bool f64 str string unit + seq map newtype_struct tuple + struct enum ignored_any identifier + unit_struct tuple_struct + } + + impl_de_number!(deserialize_i8, visit_i8); + + impl_de_number!(deserialize_i16, visit_i16); + + impl_de_number!(deserialize_i32, visit_i32); + + impl_de_number!(deserialize_i64, visit_i64); + + impl_de_number!(deserialize_u8, visit_u8); + + impl_de_number!(deserialize_u16, visit_u16); + + impl_de_number!(deserialize_u32, visit_u32); + + impl_de_number!(deserialize_u64, visit_u64); + + impl_de_number!(deserialize_f32, visit_f32); + + fn deserialize_char(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if let Amf0Value::String(s) = self { + s.into_deserializer().deserialize_any(visitor) + } else { + self.deserialize_any(visitor) + } + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self { + Amf0Value::Null => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if let Amf0Value::Array(a) = &self { + match a + .iter() + .map(|a| match a { + Amf0Value::Number(n) => num_traits::cast(*n).ok_or(()), + _ => Err(()), + }) + .collect::>() + { + Ok(buf) => visitor.visit_byte_buf(buf), + Err(()) => self.deserialize_any(visitor), + } + } else { + self.deserialize_any(visitor) + } + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + match self { + Amf0Value::Null => visitor.visit_unit(), + Amf0Value::Boolean(b) => visitor.visit_bool(*(&b as &bool)), + Amf0Value::Number(n) => visitor.visit_f64(*(&n as &f64)), + Amf0Value::String(s) => s.into_deserializer().deserialize_any(visitor), + Amf0Value::Array(a) => visitor.visit_seq(Amf0SeqAccess { iter: a.into_iter() }), + Amf0Value::Object(o) => o.into_deserializer().deserialize_any(visitor), + } + } + } + }; +} + +#[cfg(feature = "serde")] +impl_deserializer!(Amf0Value<'de>); +#[cfg(feature = "serde")] +impl_deserializer!(&'de Amf0Value<'de>); + +#[cfg(feature = "serde")] +impl<'de> serde::de::IntoDeserializer<'de, Amf0Error> for Amf0Value<'de> { + type Deserializer = Amf0Value<'de>; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::de::IntoDeserializer<'de, Amf0Error> for &'de Amf0Value<'de> { + type Deserializer = &'de Amf0Value<'de>; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +#[cfg(feature = "serde")] +struct Amf0SeqAccess { + iter: I, +} + +#[cfg(feature = "serde")] +impl<'de, I> serde::de::SeqAccess<'de> for Amf0SeqAccess +where + I: Iterator, + I::Item: serde::de::IntoDeserializer<'de, Amf0Error>, +{ + type Error = Amf0Error; + + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: serde::de::DeserializeSeed<'de>, + { + match self.iter.next() { + Some(value) => seed.deserialize(value.into_deserializer()).map(Some), + None => Ok(None), + } + } +} + #[cfg(test)] #[cfg_attr(all(test, coverage_nightly), coverage(off))] mod tests { - use std::borrow::Cow; + use std::collections::HashMap; use scuffle_bytes_util::StringCow; + #[cfg(feature = "serde")] + use serde::Deserialize; + use serde::Serialize; use super::Amf0Value; - use crate::{Amf0Array, Amf0Decoder, Amf0Encoder, Amf0Error, Amf0Marker, Amf0Object}; + use crate::{Amf0Decoder, Amf0Encoder, Amf0Error, Amf0Marker, Amf0Object}; #[test] fn from() { @@ -305,19 +456,11 @@ mod tests { let array: Vec = vec![Amf0Value::Boolean(true)]; let value: Amf0Value = array.clone().into(); - assert_eq!(value, Amf0Value::Array(Cow::Owned(array))); - - let array: &[Amf0Value] = &[Amf0Value::Boolean(true)]; - let value: Amf0Value = array.into(); - assert_eq!(value, Amf0Value::Array(Cow::Borrowed(array))); - - let array: Amf0Array = Cow::Borrowed(&[Amf0Value::Boolean(true)]); - let value: Amf0Value = array.clone().into(); assert_eq!(value, Amf0Value::Array(array)); let iter = std::iter::once(Amf0Value::Boolean(true)); let value: Amf0Value = iter.collect(); - assert_eq!(value, Amf0Value::Array(Cow::Owned(vec![Amf0Value::Boolean(true)]))); + assert_eq!(value, Amf0Value::Array(vec![Amf0Value::Boolean(true)])); } #[test] @@ -381,7 +524,7 @@ mod tests { ]; let value = Amf0Decoder::from_slice(&bytes).decode_value().unwrap(); - assert_eq!(value, Amf0Value::Array(Cow::Borrowed(&[Amf0Value::Boolean(true)]))); + assert_eq!(value, Amf0Value::Array(vec![Amf0Value::Boolean(true)])); let mut serialized = vec![]; value.encode(&mut Amf0Encoder::new(&mut serialized)).unwrap(); @@ -422,7 +565,7 @@ mod tests { let owned_value = value.clone().into_owned(); assert_eq!(owned_value, value); - let value = Amf0Value::Array(Cow::Borrowed(&[Amf0Value::Boolean(true)])); + let value = Amf0Value::Array(vec![Amf0Value::Boolean(true)]); let owned_value = value.clone().into_owned(); assert_eq!(owned_value, value); } @@ -568,20 +711,369 @@ mod tests { test_de(Mode::Unit, Amf0Value::Null); test_de(Mode::None, Amf0Value::Null); test_de(Mode::Some, Amf0Value::Number(1.0)); - test_de(Mode::Seq, Amf0Value::Array(Cow::Owned(vec![Amf0Value::Number(1.0)]))); + test_de(Mode::Seq, Amf0Value::Array(vec![Amf0Value::Number(1.0)])); test_de( Mode::Map, Amf0Value::Object([("hello".into(), Amf0Value::Number(1.0))].into_iter().collect()), ); test_de( Mode::Bytes, - Amf0Value::Array(Cow::Owned(vec![ + Amf0Value::Array(vec![ Amf0Value::Number(104.0), Amf0Value::Number(101.0), Amf0Value::Number(108.0), Amf0Value::Number(108.0), Amf0Value::Number(111.0), - ])), + ]), + ); + } + + #[cfg(feature = "serde")] + #[test] + fn deserialize_bool() { + let value = Amf0Value::Boolean(true); + let deserialized: bool = Deserialize::deserialize(value).unwrap(); + assert!(deserialized); + } + + #[cfg(feature = "serde")] + #[test] + fn deserialize_number() { + let value = Amf0Value::Number(42.0); + let deserialized: f64 = Deserialize::deserialize(value).unwrap(); + assert_eq!(deserialized, 42.0); + } + + #[cfg(feature = "serde")] + #[test] + fn deserialize_string() { + let value = Amf0Value::String(StringCow::from("hello")); + let deserialized: String = Deserialize::deserialize(value).unwrap(); + assert_eq!(deserialized, "hello"); + } + + #[cfg(feature = "serde")] + #[test] + fn deserialize_null() { + let value = Amf0Value::Null; + let deserialized: Option = Deserialize::deserialize(value).unwrap(); + assert_eq!(deserialized, None); + } + + #[cfg(feature = "serde")] + #[test] + fn deserialize_array() { + let value = Amf0Value::Array(vec![Amf0Value::Number(1.0), Amf0Value::Number(2.0), Amf0Value::Number(3.0)]); + + let deserialized: Vec = Deserialize::deserialize(value).unwrap(); + assert_eq!(deserialized, vec![1.0, 2.0, 3.0]); + } + + #[cfg(feature = "serde")] + #[test] + fn deserialize_object() { + use std::collections::HashMap; + + let mut map = Amf0Object::new(); + map.insert(StringCow::from("key"), Amf0Value::String(StringCow::from("value"))); + let value = Amf0Value::Object(map); + + let deserialized: HashMap = Deserialize::deserialize(value).unwrap(); + assert_eq!(deserialized.get("key"), Some(&"value".to_string())); + } + + #[cfg(feature = "serde")] + #[test] + fn deserialize_complex_structure() { + let value = Amf0Value::Object(Amf0Object::from_iter([ + ( + StringCow::from("numbers"), + Amf0Value::Array(vec![Amf0Value::Number(1.0), Amf0Value::Number(2.0)]), + ), + (StringCow::from("flag"), Amf0Value::Boolean(true)), + ])); + + #[derive(Debug, Deserialize, PartialEq)] + struct Complex { + numbers: Vec, + flag: bool, + } + + let deserialized: Complex = Deserialize::deserialize(value).unwrap(); + + assert_eq!( + deserialized, + Complex { + numbers: vec![1.0, 2.0], + flag: true + } ); } + + #[test] + fn roundtrip_number() { + let original: f64 = 3.22; + let mut buf = Vec::new(); + + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: f64 = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: f64 = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + + assert_eq!(buf, buf2); + } + + #[test] + fn roundtrip_boolean() { + let original: bool = true; + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: bool = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: bool = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[test] + fn roundtrip_string() { + let original: String = "hello".to_string(); + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: String = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: String = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[test] + fn roundtrip_null() { + let original: Option = None; + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: Option = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: Option = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[test] + fn roundtrip_array() { + let original: Vec = vec![1.0, 2.0, 3.0]; + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: Vec = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: Vec = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[test] + fn roundtrip_map() { + let mut original: HashMap = HashMap::new(); + original.insert("key".to_string(), "val".to_string()); + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: HashMap = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: HashMap = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestStruct { x: i32, y: String } + + #[test] + fn roundtrip_struct() { + let original = TestStruct { x: 42, y: "foo".to_string() }; + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: TestStruct = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: TestStruct = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + enum TestEnum { Unit, NewType(i16), Struct { name: String } } + + #[test] + fn roundtrip_enum_unit() { + let original = TestEnum::Unit; + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: TestEnum = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: TestEnum = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[test] + fn roundtrip_enum_newtype() { + let original = TestEnum::NewType(7); + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: TestEnum = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: TestEnum = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[test] + fn roundtrip_enum_struct() { + let original = TestEnum::Struct { name: "bar".to_string() }; + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: TestEnum = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: TestEnum = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct UnitStruct; + + #[test] + fn roundtrip_unit_struct() { + let original = UnitStruct; + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: UnitStruct = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: UnitStruct = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(&original, &decoded_native); + assert_eq!(&original, &decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } + + #[test] + fn roundtrip_enum_map() { + let mut original: HashMap = HashMap::new(); + original.insert("k1".to_string(), TestEnum::NewType(99)); + let mut buf = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf); + original.serialize(&mut encoder).unwrap(); + } + let decoded_value = Amf0Decoder::from_slice(&buf).decode_value().unwrap(); + let decoded_native: HashMap = Amf0Decoder::from_slice(&buf).deserialize().unwrap(); + let decoded_value_native: HashMap = Deserialize::deserialize(decoded_value.clone()).unwrap(); + assert_eq!(original, decoded_native); + assert_eq!(original, decoded_value_native); + let mut buf2 = Vec::new(); + { + let mut encoder = Amf0Encoder::new(&mut buf2); + decoded_value.serialize(&mut encoder).unwrap(); + } + assert_eq!(buf, buf2); + } } diff --git a/crates/bytes-util/src/cow/string/mod.rs b/crates/bytes-util/src/cow/string/mod.rs index 3baaaffba..794d97753 100644 --- a/crates/bytes-util/src/cow/string/mod.rs +++ b/crates/bytes-util/src/cow/string/mod.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use std::borrow::{Borrow, Cow}; use std::fmt::Display; use std::hash::Hash; @@ -165,6 +165,12 @@ impl From for StringCow<'_> { } } +impl Borrow for StringCow<'_> { + fn borrow(&self) -> &str { + self.as_str() + } +} + #[cfg(test)] #[cfg_attr(all(test, coverage_nightly), coverage(off))] mod tests { diff --git a/crates/bytes-util/src/cow/string/serde.rs b/crates/bytes-util/src/cow/string/serde.rs index 6606a5f73..5d56ea0e1 100644 --- a/crates/bytes-util/src/cow/string/serde.rs +++ b/crates/bytes-util/src/cow/string/serde.rs @@ -60,6 +60,22 @@ where } } +impl<'de, E> serde::de::IntoDeserializer<'de, E> for &'de StringCow<'de> +where + E: serde::de::Error, +{ + type Deserializer = StringCowDeserializer<'de, E>; + + fn into_deserializer(self) -> Self::Deserializer { + StringCowDeserializer::::new(match self { + StringCow::Bytes(b) => StringCow::Bytes(b.clone()), + StringCow::Ref(r) => StringCow::Ref(r), + StringCow::StaticRef(r) => StringCow::StaticRef(r), + StringCow::String(s) => StringCow::Ref(s), + }) + } +} + /// A deserializer for [`StringCow`]. pub struct StringCowDeserializer<'a, E> { cow: StringCow<'a>, diff --git a/dev-tools/xtask/src/cmd/dev_tools.rs b/dev-tools/xtask/src/cmd/dev_tools.rs index ff0452d75..5c886686d 100644 --- a/dev-tools/xtask/src/cmd/dev_tools.rs +++ b/dev-tools/xtask/src/cmd/dev_tools.rs @@ -48,12 +48,22 @@ impl DevTools { "cargo-insta", "cargo-hakari", "miniserve", + "taplo", ]; for package in BINSTALL_PACKAGES { debug_command(std::process::Command::new("cargo").arg("binstall").arg(package)); } + // special case for cargo-sync-rdme + debug_command( + std::process::Command::new("cargo") + .arg("binstall") + .arg("cargo-sync-rdme") + .arg("--git=https://github.com/TroyKomodo/cargo-sync-rdme.git") + .arg("--force=-y"), + ); + println!("Installation complete"); Ok(())