From c7f0c56c86dca3f684822cdce1c82cf28011049a Mon Sep 17 00:00:00 2001 From: philipch07 Date: Sun, 27 Apr 2025 18:18:17 -0400 Subject: [PATCH 01/24] add deserializer for amf0 --- changes.d/pr-441.toml | 9 + crates/amf0/src/de/mod.rs | 364 ++++++++++++++++++-------------------- crates/amf0/src/value.rs | 239 +++++++++++++++++++++++++ 3 files changed, 417 insertions(+), 195 deletions(-) create mode 100644 changes.d/pr-441.toml diff --git a/changes.d/pr-441.toml b/changes.d/pr-441.toml new file mode 100644 index 000000000..6b64f27ea --- /dev/null +++ b/changes.d/pr-441.toml @@ -0,0 +1,9 @@ +[[scuffle-amf0]] +category = "fix" +description = "Fix amf0 deserialization to abide by deserialization rules" +authors = ["@philipch07"] + +[[scuffle-amf0]] +category = "feat" +description = "Add amf0::value deserialization" +authors = ["@philipch07"] diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index d10a96995..0f8c01b77 100644 --- a/crates/amf0/src/de/mod.rs +++ b/crates/amf0/src/de/mod.rs @@ -42,12 +42,57 @@ where Ok(value) } +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(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 Some(Amf0Marker::Number) = self.next_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) { + return visitor.$visit_fn(value); + } + } + + self.deserialize_any(visitor) + } + }; +} + impl<'de, R> serde::de::Deserializer<'de> for &mut Amf0Decoder where R: ZeroCopyReader<'de>, { type Error = Amf0Error; + serde::forward_to_deserialize_any! { + char ignored_any + } + + 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); + + impl_de_number!(deserialize_f64, visit_f64); + fn deserialize_any(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, @@ -69,118 +114,53 @@ where where V: serde::de::Visitor<'de>, { - let value = self.decode_boolean()?; - visitor.visit_bool(value) - } - - fn deserialize_i8(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_i64(visitor) - } - - fn deserialize_i16(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_i64(visitor) - } - - fn deserialize_i32(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_i64(visitor) - } - - fn deserialize_i64(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let value = self.decode_number()?; - visitor.visit_i64(value as i64) - } - - fn deserialize_u8(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_u64(visitor) - } - - fn deserialize_u16(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_u64(visitor) - } - - fn deserialize_u32(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_u64(visitor) - } - - 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) - } - - fn deserialize_f64(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - let value = self.decode_number()?; - visitor.visit_f64(value) - } - - fn deserialize_char(self, _visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - Err(Amf0Error::CharNotSupported) + if let Some(Amf0Marker::Boolean) = self.next_marker { + let value = self.decode_boolean()?; + return visitor.visit_bool(value); + } + self.deserialize_any(visitor) } fn deserialize_string(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - let value = self.decode_string()?; - value.into_deserializer().deserialize_string(visitor) + if let Some(Amf0Marker::String) = self.next_marker { + let value = self.decode_string()?; + return value.into_deserializer().deserialize_string(visitor); + } + self.deserialize_any(visitor) } fn deserialize_str(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - let value = self.decode_string()?; - value.into_deserializer().deserialize_str(visitor) + if let Some(Amf0Marker::String) = self.next_marker { + let value = self.decode_string()?; + return visitor.visit_string(value.to_string()); + } + self.deserialize_any(visitor) } fn deserialize_bytes(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - self.deserialize_seq(visitor) + if let Some(Amf0Marker::StrictArray) = self.next_marker { + return self.deserialize_seq(visitor); + } + self.deserialize_any(visitor) } fn deserialize_byte_buf(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - self.deserialize_seq(visitor) + if let Some(Amf0Marker::StrictArray) = self.next_marker { + return self.deserialize_seq(visitor); + } + self.deserialize_any(visitor) } fn deserialize_option(self, visitor: V) -> Result @@ -202,25 +182,30 @@ where where V: serde::de::Visitor<'de>, { - self.decode_null()?; - visitor.visit_unit() + if let Some(Amf0Marker::Null | Amf0Marker::Undefined) = self.next_marker { + self.decode_null()?; + return visitor.visit_unit(); + } + self.deserialize_any(visitor) } fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - self.deserialize_unit(visitor) + if let Some(Amf0Marker::Null | Amf0Marker::Undefined) = self.next_marker { + return self.deserialize_unit(visitor); + } + self.deserialize_any(visitor) } fn deserialize_newtype_struct(self, name: &'static str, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if name == stream::MULTI_VALUE_NEW_TYPE { - visitor.visit_seq(MultiValueDe { de: self }) - } else { - visitor.visit_newtype_struct(self) + match (self.next_marker, name) { + (Some(Amf0Marker::StrictArray), stream::MULTI_VALUE_NEW_TYPE) => visitor.visit_seq(MultiValueDe { de: self }), + _ => visitor.visit_newtype_struct(self), } } @@ -228,53 +213,58 @@ where where V: serde::de::Visitor<'de>, { - let size = self.decode_strict_array_header()? as usize; + if let Some(Amf0Marker::StrictArray) = self.next_marker { + let size = self.decode_strict_array_header()? as usize; - visitor.visit_seq(StrictArray { - de: self, - remaining: size, - }) + return visitor.visit_seq(StrictArray { + de: self, + remaining: size, + }); + } + self.deserialize_any(visitor) } - fn deserialize_tuple(self, len: usize, visitor: V) -> Result + 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 let Some(Amf0Marker::StrictArray) = self.next_marker { + let size = self.decode_strict_array_header()? as usize; - if len != size { - return Err(Amf0Error::WrongArrayLength { - expected: len, - got: size, + return visitor.visit_seq(StrictArray { + de: self, + remaining: size, }); } - - visitor.visit_seq(StrictArray { - de: self, - remaining: size, - }) + self.deserialize_any(visitor) } fn deserialize_tuple_struct(self, _name: &'static str, len: usize, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - self.deserialize_tuple(len, visitor) + if let Some(Amf0Marker::StrictArray) = self.next_marker { + return self.deserialize_tuple(len, visitor); + } + self.deserialize_any(visitor) } fn deserialize_map(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - let header = self.decode_object_header()?; + if let Some(Amf0Marker::Object) = self.next_marker { + 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, - }), + return 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, + }), + }; } + self.deserialize_any(visitor) } fn deserialize_struct( @@ -286,7 +276,10 @@ where where V: serde::de::Visitor<'de>, { - self.deserialize_map(visitor) + if let Some(Amf0Marker::Object) = self.next_marker { + return self.deserialize_map(visitor); + } + self.deserialize_any(visitor) } fn deserialize_enum( @@ -305,14 +298,10 @@ where 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>, - { + if let Some(Amf0Marker::String | Amf0Marker::LongString) = self.next_marker { + let s = self.decode_string()?; + return s.into_deserializer().deserialize_identifier(visitor); + } self.deserialize_any(visitor) } } @@ -486,9 +475,8 @@ mod tests { use scuffle_bytes_util::StringCow; use serde_derive::Deserialize; - use crate::de::MultiValue; use crate::decoder::Amf0Decoder; - use crate::{Amf0Error, Amf0Marker, Amf0Object, Amf0Value, from_buf}; + use crate::{Amf0Error, Amf0Marker, Amf0Value, from_buf}; #[test] fn string() { @@ -528,16 +516,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) @@ -566,37 +544,31 @@ mod tests { #[test] fn numbers() { number_test(1u8); - number_test(1u16); - number_test(1u32); - number_test(1u64); - number_test(1i8); - number_test(1i16); - number_test(1i32); - number_test(1i64); - number_test(1f32); - number_test(1f64); + // number_test(1u16); + // number_test(1u32); + // number_test(1u64); + // number_test(1i8); + // number_test(1i16); + // 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,15 +580,16 @@ 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 - } - )); + // same as before about the string stuff + // 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(); @@ -986,32 +959,33 @@ mod tests { ); } - #[test] - fn multi_value() { - #[rustfmt::skip] - let bytes = [ - Amf0Marker::String as u8, - 0, 5, // length - b'h', b'e', b'l', b'l', b'o', - Amf0Marker::Boolean as u8, - 1, - Amf0Marker::Object as u8, - 0, 1, // length - b'a', - Amf0Marker::Boolean as u8, - 1, - 0, 0, Amf0Marker::ObjectEnd as u8, - ]; - - let mut de = Amf0Decoder::from_buf(Bytes::from_owner(bytes)); - let values: MultiValue<(String, bool, Amf0Object)> = de.deserialize().unwrap(); - assert_eq!(values.0.0, "hello"); - assert!(values.0.1); - assert_eq!( - values.0.2, - [("a".into(), Amf0Value::Boolean(true))].into_iter().collect::() - ); - } + // #[test] + // fn multi_value() { + // #[rustfmt::skip] + // let bytes = [ + // Amf0Marker::String as u8, + // 0, 5, // length + // b'h', b'e', b'l', b'l', b'o', + // Amf0Marker::Boolean as u8, + // 1, + // Amf0Marker::Object as u8, + // 0, 1, // length + // b'a', + // Amf0Marker::Boolean as u8, + // 1, + // 0, 0, Amf0Marker::ObjectEnd as u8, + // ]; + + // 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); + // assert_eq!( + // values.0.2, + // [("a".into(), Amf0Value::Boolean(true))].into_iter().collect::() + // ); + // } #[test] fn deserializer_stream() { diff --git a/crates/amf0/src/value.rs b/crates/amf0/src/value.rs index db09210af..67641be41 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -278,12 +278,167 @@ impl serde::ser::Serialize for Amf0Value<'_> { } } +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(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 { + if let Some(n) = ::num_traits::cast(*n) { + return visitor.$visit_fn(n); + } + } + + self.deserialize_any(visitor) + } + }; +} + +#[cfg(feature = "serde")] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] +impl<'de, 'a: 'de> serde::Deserializer<'de> for &'a Amf0Value<'de> { + type Error = Amf0Error; + + serde::forward_to_deserialize_any! { + bool f64 char str string unit + seq map newtype_struct tuple + struct enum ignored_any identifier + } + + 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_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>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + 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_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), + Amf0Value::Number(n) => visitor.visit_f64(*n), + Amf0Value::String(s) => visitor.visit_borrowed_str(s.as_str()), + Amf0Value::Array(a) => visitor.visit_seq(Amf0SeqAccess { iter: a.iter() }), + Amf0Value::Object(o) => visitor.visit_map(Amf0MapAccess { + iter: o.iter(), + value: None, + }), + } + } +} + +struct Amf0SeqAccess<'a, 'de> { + iter: std::slice::Iter<'a, Amf0Value<'de>>, +} + +impl<'de, 'a: 'de> serde::de::SeqAccess<'de> for Amf0SeqAccess<'a, 'de> { + 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).map(Some), + None => Ok(None), + } + } +} + +struct Amf0MapAccess<'a, 'de> { + iter: std::collections::hash_map::Iter<'a, StringCow<'de>, Amf0Value<'de>>, + value: Option<&'a Amf0Value<'de>>, +} + +impl<'de, 'a: 'de> serde::de::MapAccess<'de> for Amf0MapAccess<'a, 'de> { + type Error = Amf0Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: serde::de::DeserializeSeed<'de>, + { + match self.iter.next() { + Some((key, value)) => { + self.value = Some(value); + seed.deserialize(serde::de::IntoDeserializer::into_deserializer(key.as_str())) + .map(Some) + } + None => Ok(None), + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: serde::de::DeserializeSeed<'de>, + { + seed.deserialize(self.value.take().unwrap()) + } +} + #[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 super::Amf0Value; use crate::{Amf0Array, Amf0Decoder, Amf0Encoder, Amf0Error, Amf0Marker, Amf0Object}; @@ -584,4 +739,88 @@ mod tests { ])), ); } + + #[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(Cow::Owned(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() { + 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(HashMap::from([ + ( + StringCow::from("numbers"), + Amf0Value::Array(Cow::Owned(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 + } + ); + } } From 3f1142efb0219ea2d88276f0c760575412e29ebb Mon Sep 17 00:00:00 2001 From: philipch07 Date: Fri, 30 May 2025 19:21:40 -0400 Subject: [PATCH 02/24] address pr comments --- crates/amf0/src/de/mod.rs | 2 -- crates/amf0/src/value.rs | 4 ---- 2 files changed, 6 deletions(-) diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index 0f8c01b77..05e6d79a3 100644 --- a/crates/amf0/src/de/mod.rs +++ b/crates/amf0/src/de/mod.rs @@ -42,8 +42,6 @@ where Ok(value) } -#[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] macro_rules! impl_de_number { ($deserializser_fn:ident, $visit_fn:ident) => { fn $deserializser_fn(self, visitor: V) -> Result diff --git a/crates/amf0/src/value.rs b/crates/amf0/src/value.rs index 67641be41..9540d8646 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -278,8 +278,6 @@ impl serde::ser::Serialize for Amf0Value<'_> { } } -#[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] macro_rules! impl_de_number { ($deserializser_fn:ident, $visit_fn:ident) => { fn $deserializser_fn(self, visitor: V) -> Result @@ -297,8 +295,6 @@ macro_rules! impl_de_number { }; } -#[cfg(feature = "serde")] -#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] impl<'de, 'a: 'de> serde::Deserializer<'de> for &'a Amf0Value<'de> { type Error = Amf0Error; From 29fcb4aac8d46ab56a4afa4b1945515da1c10b8a Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sat, 31 May 2025 00:46:39 +0000 Subject: [PATCH 03/24] small fixes --- crates/amf0/src/de/mod.rs | 228 ++++++++--------- crates/amf0/src/decoder.rs | 2 +- crates/amf0/src/value.rs | 285 ++++++++++++---------- crates/bytes-util/src/cow/string/serde.rs | 16 ++ 4 files changed, 285 insertions(+), 246 deletions(-) diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index 05e6d79a3..e26a9517d 100644 --- a/crates/amf0/src/de/mod.rs +++ b/crates/amf0/src/de/mod.rs @@ -48,15 +48,17 @@ macro_rules! impl_de_number { where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::Number) = self.next_marker { + 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) { - return visitor.$visit_fn(value); + visitor.$visit_fn(value) + } else { + visitor.visit_f64(value) } + } else { + self.deserialize_any(visitor) } - - self.deserialize_any(visitor) } }; } @@ -89,7 +91,16 @@ where impl_de_number!(deserialize_f32, visit_f32); - impl_de_number!(deserialize_f64, visit_f64); + fn deserialize_f64(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if let Amf0Marker::Number | Amf0Marker::Date = self.peek_marker()? { + visitor.visit_f64(self.decode_number()?) + } else { + self.deserialize_any(visitor) + } + } fn deserialize_any(self, visitor: V) -> Result where @@ -112,64 +123,66 @@ where where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::Boolean) = self.next_marker { + if let Amf0Marker::Boolean = self.peek_marker()? { let value = self.decode_boolean()?; - return visitor.visit_bool(value); + visitor.visit_bool(value) + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } fn deserialize_string(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::String) = self.next_marker { + if let Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument = self.peek_marker()? { let value = self.decode_string()?; - return value.into_deserializer().deserialize_string(visitor); + value.into_deserializer().deserialize_string(visitor) + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } fn deserialize_str(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::String) = self.next_marker { + if let Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument = self.peek_marker()? { let value = self.decode_string()?; - return visitor.visit_string(value.to_string()); + visitor.visit_string(value.to_string()) + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } fn deserialize_bytes(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::StrictArray) = self.next_marker { - return self.deserialize_seq(visitor); + if let Amf0Marker::StrictArray = self.peek_marker()? { + self.deserialize_seq(visitor) + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } fn deserialize_byte_buf(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::StrictArray) = self.next_marker { - return self.deserialize_seq(visitor); + if let Amf0Marker::StrictArray = self.peek_marker()? { + self.deserialize_seq(visitor) + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } 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 matches!(self.peek_marker()?, Amf0Marker::Null | Amf0Marker::Undefined) { + self.decode_null()?; visitor.visit_none() } else { visitor.visit_some(self) @@ -180,30 +193,29 @@ where where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::Null | Amf0Marker::Undefined) = self.next_marker { + if let Amf0Marker::Null | Amf0Marker::Undefined = self.peek_marker()? { self.decode_null()?; - return visitor.visit_unit(); + visitor.visit_unit() + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::Null | Amf0Marker::Undefined) = self.next_marker { - return self.deserialize_unit(visitor); - } - self.deserialize_any(visitor) + self.deserialize_unit(visitor) } fn deserialize_newtype_struct(self, name: &'static str, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - match (self.next_marker, name) { - (Some(Amf0Marker::StrictArray), stream::MULTI_VALUE_NEW_TYPE) => visitor.visit_seq(MultiValueDe { de: self }), - _ => visitor.visit_newtype_struct(self), + if name == stream::MULTI_VALUE_NEW_TYPE { + visitor.visit_seq(MultiValueDe { de: self }) + } else { + visitor.visit_newtype_struct(self) } } @@ -211,58 +223,62 @@ where where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::StrictArray) = self.next_marker { + if let Amf0Marker::StrictArray = self.peek_marker()? { let size = self.decode_strict_array_header()? as usize; - return visitor.visit_seq(StrictArray { + visitor.visit_seq(StrictArray { de: self, remaining: size, - }); + }) + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } fn deserialize_tuple(self, _len: usize, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::StrictArray) = self.next_marker { + if let Amf0Marker::StrictArray = self.peek_marker()? { let size = self.decode_strict_array_header()? as usize; - return visitor.visit_seq(StrictArray { + visitor.visit_seq(StrictArray { de: self, remaining: size, - }); + }) + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } fn deserialize_tuple_struct(self, _name: &'static str, len: usize, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::StrictArray) = self.next_marker { - return self.deserialize_tuple(len, visitor); + if let Amf0Marker::StrictArray = self.peek_marker()? { + self.deserialize_tuple(len, visitor) + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } fn deserialize_map(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::Object) = self.next_marker { + if let Amf0Marker::Object | Amf0Marker::TypedObject | Amf0Marker::EcmaArray = self.peek_marker()? { let header = self.decode_object_header()?; - return match 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, }), - }; + } + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } fn deserialize_struct( @@ -274,10 +290,7 @@ where where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::Object) = self.next_marker { - return self.deserialize_map(visitor); - } - self.deserialize_any(visitor) + self.deserialize_map(visitor) } fn deserialize_enum( @@ -296,11 +309,12 @@ where where V: serde::de::Visitor<'de>, { - if let Some(Amf0Marker::String | Amf0Marker::LongString) = self.next_marker { + if let Amf0Marker::String | Amf0Marker::LongString = self.peek_marker()? { let s = self.decode_string()?; - return s.into_deserializer().deserialize_identifier(visitor); + s.into_deserializer().deserialize_identifier(visitor) + } else { + self.deserialize_any(visitor) } - self.deserialize_any(visitor) } } @@ -473,8 +487,9 @@ mod tests { use scuffle_bytes_util::StringCow; use serde_derive::Deserialize; + use crate::de::MultiValue; use crate::decoder::Amf0Decoder; - use crate::{Amf0Error, Amf0Marker, Amf0Value, from_buf}; + use crate::{Amf0Error, Amf0Marker, Amf0Object, Amf0Value, from_buf}; #[test] fn string() { @@ -498,15 +513,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] @@ -541,16 +550,16 @@ mod tests { #[test] fn numbers() { + number_test(1f64); number_test(1u8); - // number_test(1u16); - // number_test(1u32); - // number_test(1u64); - // number_test(1i8); - // number_test(1i16); - // number_test(1i32); - // number_test(1i64); - // number_test(1f32); - // number_test(1f64); + number_test(1u16); + number_test(1u32); + number_test(1u64); + number_test(1i8); + number_test(1i16); + number_test(1i32); + number_test(1i64); + number_test(1f32); let mut bytes = vec![Amf0Marker::Date as u8]; bytes.extend_from_slice(&f64::consts::PI.to_be_bytes()); @@ -646,7 +655,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] @@ -751,14 +763,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] @@ -957,33 +963,33 @@ mod tests { ); } - // #[test] - // fn multi_value() { - // #[rustfmt::skip] - // let bytes = [ - // Amf0Marker::String as u8, - // 0, 5, // length - // b'h', b'e', b'l', b'l', b'o', - // Amf0Marker::Boolean as u8, - // 1, - // Amf0Marker::Object as u8, - // 0, 1, // length - // b'a', - // Amf0Marker::Boolean as u8, - // 1, - // 0, 0, Amf0Marker::ObjectEnd as u8, - // ]; - - // 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); - // assert_eq!( - // values.0.2, - // [("a".into(), Amf0Value::Boolean(true))].into_iter().collect::() - // ); - // } + #[test] + fn multi_value() { + #[rustfmt::skip] + let bytes = [ + Amf0Marker::String as u8, + 0, 5, // length + b'h', b'e', b'l', b'l', b'o', + Amf0Marker::Boolean as u8, + 1, + Amf0Marker::Object as u8, + 0, 1, // length + b'a', + Amf0Marker::Boolean as u8, + 1, + 0, 0, Amf0Marker::ObjectEnd as u8, + ]; + + 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); + assert_eq!( + values.0.2, + [("a".into(), Amf0Value::Boolean(true))].into_iter().collect::() + ); + } #[test] fn deserializer_stream() { diff --git a/crates/amf0/src/decoder.rs b/crates/amf0/src/decoder.rs index 537c09b8a..6ccf96c78 100644 --- a/crates/amf0/src/decoder.rs +++ b/crates/amf0/src/decoder.rs @@ -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 diff --git a/crates/amf0/src/value.rs b/crates/amf0/src/value.rs index 9540d8646..05ec757f4 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -1,18 +1,19 @@ //! AMF0 value types. -use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::io; +use std::marker::PhantomData; 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>>; +pub type Amf0Object<'a> = BTreeMap, Amf0Value<'a>>; /// Represents any AMF0 array. -pub type Amf0Array<'a> = Cow<'a, [Amf0Value<'a>]>; +pub type Amf0Array<'a> = Vec>; /// Represents any AMF0 value. #[derive(Debug, PartialEq, Clone)] @@ -42,7 +43,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 +88,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 +210,7 @@ impl<'de> serde::de::Deserialize<'de> for Amf0Value<'de> { where A: serde::de::MapAccess<'de>, { - let mut object = HashMap::new(); + let mut object = BTreeMap::new(); while let Some((key, value)) = map.next_entry()? { object.insert(key, value); @@ -285,104 +272,140 @@ macro_rules! impl_de_number { V: serde::de::Visitor<'de>, { if let Amf0Value::Number(n) = self { - if let Some(n) = ::num_traits::cast(*n) { - return visitor.$visit_fn(n); + 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) } - - self.deserialize_any(visitor) } }; } -impl<'de, 'a: 'de> serde::Deserializer<'de> for &'a Amf0Value<'de> { - type Error = Amf0Error; +macro_rules! impl_deserializer { + ($ty:ty) => { + impl<'de> serde::Deserializer<'de> for $ty { + type Error = Amf0Error; - serde::forward_to_deserialize_any! { - bool f64 char str string unit - seq map newtype_struct tuple - struct enum ignored_any identifier - } + serde::forward_to_deserialize_any! { + bool f64 char str string unit + seq map newtype_struct tuple + struct enum ignored_any identifier + } - impl_de_number!(deserialize_i8, visit_i8); + impl_de_number!(deserialize_i8, visit_i8); - impl_de_number!(deserialize_i16, visit_i16); + impl_de_number!(deserialize_i16, visit_i16); - impl_de_number!(deserialize_i32, visit_i32); + impl_de_number!(deserialize_i32, visit_i32); - impl_de_number!(deserialize_i64, visit_i64); + impl_de_number!(deserialize_i64, visit_i64); - impl_de_number!(deserialize_u8, visit_u8); + impl_de_number!(deserialize_u8, visit_u8); - impl_de_number!(deserialize_u16, visit_u16); + impl_de_number!(deserialize_u16, visit_u16); - impl_de_number!(deserialize_u32, visit_u32); + impl_de_number!(deserialize_u32, visit_u32); - impl_de_number!(deserialize_u64, visit_u64); + impl_de_number!(deserialize_u64, visit_u64); - impl_de_number!(deserialize_f32, visit_f32); + impl_de_number!(deserialize_f32, visit_f32); - 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_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>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + 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_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) => visitor.visit_map(Amf0MapAccess { + iter: o.into_iter(), + value: None, + _phantom: PhantomData, + }), + } + } } - } + }; +} - fn deserialize_bytes(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_seq(visitor) - } +impl_deserializer!(Amf0Value<'de>); +impl_deserializer!(&'de Amf0Value<'de>); - fn deserialize_byte_buf(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_seq(visitor) - } +impl<'de> serde::de::IntoDeserializer<'de, Amf0Error> for Amf0Value<'de> { + type Deserializer = Amf0Value<'de>; - fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_unit(visitor) + fn into_deserializer(self) -> Self::Deserializer { + self } +} - fn deserialize_tuple_struct(self, _name: &'static str, len: usize, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_tuple(len, visitor) - } +impl<'de> serde::de::IntoDeserializer<'de, Amf0Error> for &'de Amf0Value<'de> { + type Deserializer = &'de Amf0Value<'de>; - 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), - Amf0Value::Number(n) => visitor.visit_f64(*n), - Amf0Value::String(s) => visitor.visit_borrowed_str(s.as_str()), - Amf0Value::Array(a) => visitor.visit_seq(Amf0SeqAccess { iter: a.iter() }), - Amf0Value::Object(o) => visitor.visit_map(Amf0MapAccess { - iter: o.iter(), - value: None, - }), - } + fn into_deserializer(self) -> Self::Deserializer { + self } } -struct Amf0SeqAccess<'a, 'de> { - iter: std::slice::Iter<'a, Amf0Value<'de>>, +struct Amf0SeqAccess { + iter: I, } -impl<'de, 'a: 'de> serde::de::SeqAccess<'de> for Amf0SeqAccess<'a, 'de> { +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> @@ -390,54 +413,56 @@ impl<'de, 'a: 'de> serde::de::SeqAccess<'de> for Amf0SeqAccess<'a, 'de> { T: serde::de::DeserializeSeed<'de>, { match self.iter.next() { - Some(value) => seed.deserialize(value).map(Some), + Some(value) => seed.deserialize(value.into_deserializer()).map(Some), None => Ok(None), } } } -struct Amf0MapAccess<'a, 'de> { - iter: std::collections::hash_map::Iter<'a, StringCow<'de>, Amf0Value<'de>>, - value: Option<&'a Amf0Value<'de>>, +struct Amf0MapAccess { + iter: I, + value: Option, + _phantom: PhantomData, } -impl<'de, 'a: 'de> serde::de::MapAccess<'de> for Amf0MapAccess<'a, 'de> { +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: K) -> Result, Self::Error> + fn next_key_seed(&mut self, seed: S) -> Result, Self::Error> where - K: serde::de::DeserializeSeed<'de>, + S: serde::de::DeserializeSeed<'de>, { match self.iter.next() { Some((key, value)) => { self.value = Some(value); - seed.deserialize(serde::de::IntoDeserializer::into_deserializer(key.as_str())) - .map(Some) + seed.deserialize(key.into_deserializer()).map(Some) } None => Ok(None), } } - fn next_value_seed(&mut self, seed: V) -> Result + fn next_value_seed(&mut self, seed: S) -> Result where - V: serde::de::DeserializeSeed<'de>, + S: serde::de::DeserializeSeed<'de>, { - seed.deserialize(self.value.take().unwrap()) + seed.deserialize(self.value.take().unwrap().into_deserializer()) } } #[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 super::Amf0Value; - use crate::{Amf0Array, Amf0Decoder, Amf0Encoder, Amf0Error, Amf0Marker, Amf0Object}; + use crate::{Amf0Decoder, Amf0Encoder, Amf0Error, Amf0Marker, Amf0Object}; #[test] fn from() { @@ -456,19 +481,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] @@ -532,7 +549,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(); @@ -573,7 +590,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); } @@ -719,20 +736,20 @@ 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), - ])), + ]), ); } @@ -740,7 +757,7 @@ mod tests { #[test] fn deserialize_bool() { let value = Amf0Value::Boolean(true); - let deserialized: bool = Deserialize::deserialize(&value).unwrap(); + let deserialized: bool = Deserialize::deserialize(value).unwrap(); assert!(deserialized); } @@ -748,7 +765,7 @@ mod tests { #[test] fn deserialize_number() { let value = Amf0Value::Number(42.0); - let deserialized: f64 = Deserialize::deserialize(&value).unwrap(); + let deserialized: f64 = Deserialize::deserialize(value).unwrap(); assert_eq!(deserialized, 42.0); } @@ -756,7 +773,7 @@ mod tests { #[test] fn deserialize_string() { let value = Amf0Value::String(StringCow::from("hello")); - let deserialized: String = Deserialize::deserialize(&value).unwrap(); + let deserialized: String = Deserialize::deserialize(value).unwrap(); assert_eq!(deserialized, "hello"); } @@ -764,41 +781,41 @@ mod tests { #[test] fn deserialize_null() { let value = Amf0Value::Null; - let deserialized: Option = Deserialize::deserialize(&value).unwrap(); + let deserialized: Option = Deserialize::deserialize(value).unwrap(); assert_eq!(deserialized, None); } #[cfg(feature = "serde")] #[test] fn deserialize_array() { - let value = Amf0Value::Array(Cow::Owned(vec![ - Amf0Value::Number(1.0), - Amf0Value::Number(2.0), - Amf0Value::Number(3.0), - ])); + let value = Amf0Value::Array(vec![Amf0Value::Number(1.0), Amf0Value::Number(2.0), Amf0Value::Number(3.0)]); - let deserialized: Vec = Deserialize::deserialize(&value).unwrap(); + 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(); + 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(HashMap::from([ + use std::collections::BTreeMap; + + let value = Amf0Value::Object(BTreeMap::from([ ( StringCow::from("numbers"), - Amf0Value::Array(Cow::Owned(vec![Amf0Value::Number(1.0), Amf0Value::Number(2.0)])), + Amf0Value::Array(vec![Amf0Value::Number(1.0), Amf0Value::Number(2.0)]), ), (StringCow::from("flag"), Amf0Value::Boolean(true)), ])); @@ -809,7 +826,7 @@ mod tests { flag: bool, } - let deserialized: Complex = Deserialize::deserialize(&value).unwrap(); + let deserialized: Complex = Deserialize::deserialize(value).unwrap(); assert_eq!( deserialized, 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>, From d00254a006218f2f128386f121c1dfc9eb417a9f Mon Sep 17 00:00:00 2001 From: philipch07 Date: Fri, 30 May 2025 20:56:29 -0400 Subject: [PATCH 04/24] fix versioning requirement for bytes-util in amf0 + update changelog to include bytes-util change from troy --- changes.d/pr-441.toml | 13 +++++++++---- crates/amf0/Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/changes.d/pr-441.toml b/changes.d/pr-441.toml index 6b64f27ea..88c8f1dd9 100644 --- a/changes.d/pr-441.toml +++ b/changes.d/pr-441.toml @@ -1,9 +1,14 @@ [[scuffle-amf0]] category = "fix" -description = "Fix amf0 deserialization to abide by deserialization rules" -authors = ["@philipch07"] +description = "Fix amf0 deserialization to abide by better deserialization standards" +authors = ["@philipch07", "@TroyKomodo"] [[scuffle-amf0]] category = "feat" -description = "Add amf0::value deserialization" -authors = ["@philipch07"] +description = "Add an Amf0Value deserializer" +authors = ["@philipch07", "@TroyKomodo"] + +[[scuffle-util-bytes]] +category = "feat" +description = "Add an `IntoDeserializer` implementation for `StringCow`" +authors = ["@TroyKomodo"] diff --git a/crates/amf0/Cargo.toml b/crates/amf0/Cargo.toml index b7e0260f7..45666b5fd 100644 --- a/crates/amf0/Cargo.toml +++ b/crates/amf0/Cargo.toml @@ -19,7 +19,7 @@ bytestring = "1.4.0" document-features = { optional = true, version = "0.2" } 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" } From 7a413dd1798a31ec95f3dc10e0b40b90363f01d8 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Fri, 30 May 2025 21:03:00 -0400 Subject: [PATCH 05/24] fix + update changelog --- changes.d/pr-441.toml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/changes.d/pr-441.toml b/changes.d/pr-441.toml index 88c8f1dd9..983bfce7b 100644 --- a/changes.d/pr-441.toml +++ b/changes.d/pr-441.toml @@ -3,12 +3,22 @@ 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-util-bytes]] +[[scuffle-bytes-util]] category = "feat" -description = "Add an `IntoDeserializer` implementation for `StringCow`" +description = "Add an `IntoDeserializer` implementation for a `&StringCow`" authors = ["@TroyKomodo"] From c46e655df71398543bfa6268ef8826eb1bd3e7df Mon Sep 17 00:00:00 2001 From: philipch07 Date: Sat, 31 May 2025 13:59:08 -0400 Subject: [PATCH 06/24] wip: stuck on lifetimes and uncertain about various impls in map.rs --- crates/amf0/src/lib.rs | 3 + crates/amf0/src/map.rs | 1176 ++++++++++++++++++++++++++++++++++++++ crates/amf0/src/value.rs | 4 +- 3 files changed, 1181 insertions(+), 2 deletions(-) create mode 100644 crates/amf0/src/map.rs diff --git a/crates/amf0/src/lib.rs b/crates/amf0/src/lib.rs index 48f07e3ae..4d4ce57c0 100644 --- a/crates/amf0/src/lib.rs +++ b/crates/amf0/src/lib.rs @@ -54,6 +54,7 @@ pub mod error; #[cfg(feature = "serde")] pub mod ser; pub mod value; +pub mod map; #[cfg(feature = "serde")] pub use de::{from_buf, from_reader, from_slice}; @@ -64,6 +65,8 @@ pub use error::{Amf0Error, Result}; pub use ser::{to_bytes, to_writer}; pub use value::{Amf0Array, Amf0Object, Amf0Value}; +extern crate alloc; + /// AMF0 marker types. /// /// Defined by: diff --git a/crates/amf0/src/map.rs b/crates/amf0/src/map.rs new file mode 100644 index 000000000..5f34a5cc3 --- /dev/null +++ b/crates/amf0/src/map.rs @@ -0,0 +1,1176 @@ +//! This code is taken from https://github.com/serde-rs/json/blob/v1.0.140/src/map.rs +//! +//! A map of String to serde_json::Value. +//! +//! By default the map is backed by a [`BTreeMap`]. Enable the `preserve_order` +//! feature of serde_json to use [`IndexMap`] instead. +//! +//! [`BTreeMap`]: std::collections::BTreeMap +//! [`IndexMap`]: indexmap::IndexMap + +use crate::Amf0Error as Error; +use crate::Amf0Value as Value; +use alloc::string::String; +#[cfg(feature = "preserve_order")] +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::fmt::{self, Debug}; +use core::hash::{Hash, Hasher}; +use core::iter::FusedIterator; +#[cfg(feature = "preserve_order")] +use core::mem; +use core::ops; +use serde::de; + +#[cfg(not(feature = "preserve_order"))] +use alloc::collections::{btree_map, BTreeMap}; +#[cfg(feature = "preserve_order")] +use indexmap::IndexMap; + +/// Represents a JSON key/value type. +pub struct Map { + map: MapImpl, +} + +#[cfg(not(feature = "preserve_order"))] +type MapImpl = BTreeMap; +#[cfg(feature = "preserve_order")] +type MapImpl = IndexMap; + +impl Map { + /// Makes a new empty Map. + #[inline] + pub fn new() -> Self { + Map { + map: MapImpl::new(), + } + } + + /// Makes a new empty Map with the given initial capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Map { + #[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<&Value> + where + String: 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 + String: 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 Value> + where + String: 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<(&String, &Value)> + where + String: 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: String, v: Value) -> 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")] + #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] + #[inline] + pub fn shift_insert(&mut self, index: usize, k: String, v: Value) -> 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 serde_json'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 + String: 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 serde_json'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<(String, Value)> + where + String: 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")] + #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] + #[inline] + pub fn swap_remove(&mut self, key: &Q) -> Option + where + String: 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")] + #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] + #[inline] + pub fn swap_remove_entry(&mut self, key: &Q) -> Option<(String, Value)> + where + String: 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")] + #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] + #[inline] + pub fn shift_remove(&mut self, key: &Q) -> Option + where + String: 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")] + #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] + #[inline] + pub fn shift_remove_entry(&mut self, key: &Q) -> Option<(String, Value)> + where + String: 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(mem::replace(&mut other.map, MapImpl::default())); + #[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: S) -> Entry + where + S: Into, + { + #[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 { + Iter { + iter: self.map.iter(), + } + } + + /// Gets a mutable iterator over the entries of the map. + #[inline] + pub fn iter_mut(&mut self) -> IterMut { + IterMut { + iter: self.map.iter_mut(), + } + } + + /// Gets an iterator over the keys of the map. + #[inline] + pub fn keys(&self) -> Keys { + Keys { + iter: self.map.keys(), + } + } + + /// Gets an iterator over the values of the map. + #[inline] + pub fn values(&self) -> Values { + Values { + iter: self.map.values(), + } + } + + /// Gets an iterator over mutable values of the map. + #[inline] + pub fn values_mut(&mut self) -> ValuesMut { + ValuesMut { + iter: self.map.values_mut(), + } + } + + /// Gets an iterator over the values of the map. + #[inline] + pub fn into_values(self) -> IntoValues { + 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(&String, &mut Value) -> bool, + { + self.map.retain(f); + } + + /// Sorts this map's entries in-place using `str`'s usual ordering. + /// + /// If serde_json's "preserve_order" feature is not enabled, this method + /// does no work because all JSON maps are always kept in a sorted state. + /// + /// If serde_json'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. + /// + /// Other maps nested within the values of this map are not sorted. If you + /// need the entire data structure to be sorted at all levels, you must also + /// call + /// map.[values_mut]\().for_each([Value::sort_all_objects]). + /// + /// [values_mut]: Map::values_mut + #[inline] + pub fn sort_keys(&mut self) { + #[cfg(feature = "preserve_order")] + self.map.sort_unstable_keys(); + } +} + +#[allow(clippy::derivable_impls)] // clippy bug: https://github.com/rust-lang/rust-clippy/issues/7655 +impl Default for Map { + #[inline] + fn default() -> Self { + Map { + map: MapImpl::new(), + } + } +} + +impl Clone for Map { + #[inline] + fn clone(&self) -> Self { + Map { + map: self.map.clone(), + } + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + self.map.clone_from(&source.map); + } +} + +impl PartialEq for Map { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.map.eq(&other.map) + } +} + +impl Eq for Map {} + +impl Hash for Map { + fn hash(&self, state: &mut H) { + #[cfg(not(feature = "preserve_order"))] + { + self.map.hash(state); + } + + #[cfg(feature = "preserve_order")] + { + let mut kv = Vec::from_iter(&self.map); + kv.sort_unstable_by(|a, b| a.0.cmp(b.0)); + kv.hash(state); + } + } +} + +/// Access an element of this map. Panics if the given key is not present in the +/// map. +/// +/// ``` +/// # use serde_json::Value; +/// # +/// # let val = &Value::String("".to_owned()); +/// # let _ = +/// match val { +/// Value::String(s) => Some(s.as_str()), +/// Value::Array(arr) => arr[0].as_str(), +/// Value::Object(map) => map["type"].as_str(), +/// _ => None, +/// } +/// # ; +/// ``` +impl ops::Index<&Q> for Map +where + String: Borrow, + Q: ?Sized + Ord + Eq + Hash, +{ + type Output = Value; + + fn index(&self, index: &Q) -> &Value { + self.map.index(index) + } +} + +/// Mutably access an element of this map. Panics if the given key is not +/// present in the map. +/// +/// ``` +/// # use serde_json::json; +/// # +/// # let mut map = serde_json::Map::new(); +/// # map.insert("key".to_owned(), serde_json::Value::Null); +/// # +/// map["key"] = json!("value"); +/// ``` +impl ops::IndexMut<&Q> for Map +where + String: Borrow, + Q: ?Sized + Ord + Eq + Hash, +{ + fn index_mut(&mut self, index: &Q) -> &mut Value { + self.map.get_mut(index).expect("no entry found for key") + } +} + +impl Debug for Map { + #[inline] + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.map.fmt(formatter) + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl serde::ser::Serialize for Map { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + use serde::ser::SerializeMap; + let mut map = tri!(serializer.serialize_map(Some(self.len()))); + for (k, v) in self { + tri!(map.serialize_entry(k, v)); + } + map.end() + } +} + +impl<'de> de::Deserialize<'de> for Map { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = Map; + + 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(Map::new()) + } + + #[cfg(any(feature = "std", feature = "alloc"))] + #[inline] + fn visit_map(self, mut visitor: V) -> Result + where + V: de::MapAccess<'de>, + { + let mut values = Map::new(); + + while let Some((key, value)) = tri!(visitor.next_entry()) { + values.insert(key, value); + } + + Ok(values) + } + } + + deserializer.deserialize_map(Visitor) + } +} + +impl FromIterator<(String, Value)> for Map { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + Map { + map: FromIterator::from_iter(iter), + } + } +} + +impl Extend<(String, Value)> for Map { + fn extend(&mut self, iter: T) + where + T: IntoIterator, + { + 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)* {} + } +} + +impl<'de> de::IntoDeserializer<'de, Error> for Map { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +impl<'de> de::IntoDeserializer<'de, Error> for &'de Map { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +////////////////////////////////////////////////////////////////////////////// + +/// 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 [`Map`]. +/// +/// [`entry`]: Map::entry +pub enum Entry<'a> { + /// A vacant Entry. + Vacant(VacantEntry<'a>), + /// An occupied Entry. + Occupied(OccupiedEntry<'a>), +} + +/// A vacant Entry. It is part of the [`Entry`] enum. +pub struct VacantEntry<'a> { + vacant: VacantEntryImpl<'a>, +} + +/// An occupied Entry. It is part of the [`Entry`] enum. +pub struct OccupiedEntry<'a> { + occupied: OccupiedEntryImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type VacantEntryImpl<'a> = btree_map::VacantEntry<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type VacantEntryImpl<'a> = indexmap::map::VacantEntry<'a, String, Value>; + +#[cfg(not(feature = "preserve_order"))] +type OccupiedEntryImpl<'a> = btree_map::OccupiedEntry<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type OccupiedEntryImpl<'a> = indexmap::map::OccupiedEntry<'a, String, Value>; + +impl<'a> Entry<'a> { + /// Returns a reference to this entry's key. + /// + /// # Examples + /// + /// ``` + /// let mut map = serde_json::Map::new(); + /// assert_eq!(map.entry("serde").key(), &"serde"); + /// ``` + pub fn key(&self) -> &String { + 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. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// let mut map = serde_json::Map::new(); + /// map.entry("serde").or_insert(json!(12)); + /// + /// assert_eq!(map["serde"], 12); + /// ``` + pub fn or_insert(self, default: Value) -> &'a mut Value { + 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. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// let mut map = serde_json::Map::new(); + /// map.entry("serde").or_insert_with(|| json!("hoho")); + /// + /// assert_eq!(map["serde"], "hoho".to_owned()); + /// ``` + pub fn or_insert_with(self, default: F) -> &'a mut Value + where + F: FnOnce() -> Value, + { + 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. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// let mut map = serde_json::Map::new(); + /// map.entry("serde") + /// .and_modify(|e| *e = json!("rust")) + /// .or_insert(json!("cpp")); + /// + /// assert_eq!(map["serde"], "cpp"); + /// + /// map.entry("serde") + /// .and_modify(|e| *e = json!("rust")) + /// .or_insert(json!("cpp")); + /// + /// assert_eq!(map["serde"], "rust"); + /// ``` + pub fn and_modify(self, f: F) -> Self + where + F: FnOnce(&mut Value), + { + match self { + Entry::Occupied(mut entry) => { + f(entry.get_mut()); + Entry::Occupied(entry) + } + Entry::Vacant(entry) => Entry::Vacant(entry), + } + } +} + +impl<'a> VacantEntry<'a> { + /// Gets a reference to the key that would be used when inserting a value + /// through the VacantEntry. + /// + /// # Examples + /// + /// ``` + /// use serde_json::map::Entry; + /// + /// let mut map = serde_json::Map::new(); + /// + /// match map.entry("serde") { + /// Entry::Vacant(vacant) => { + /// assert_eq!(vacant.key(), &"serde"); + /// } + /// Entry::Occupied(_) => unimplemented!(), + /// } + /// ``` + #[inline] + pub fn key(&self) -> &String { + self.vacant.key() + } + + /// Sets the value of the entry with the VacantEntry's key, and returns a + /// mutable reference to it. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// use serde_json::map::Entry; + /// + /// let mut map = serde_json::Map::new(); + /// + /// match map.entry("serde") { + /// Entry::Vacant(vacant) => { + /// vacant.insert(json!("hoho")); + /// } + /// Entry::Occupied(_) => unimplemented!(), + /// } + /// ``` + #[inline] + pub fn insert(self, value: Value) -> &'a mut Value { + self.vacant.insert(value) + } +} + +impl<'a> OccupiedEntry<'a> { + /// Gets a reference to the key in the entry. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// use serde_json::map::Entry; + /// + /// let mut map = serde_json::Map::new(); + /// map.insert("serde".to_owned(), json!(12)); + /// + /// match map.entry("serde") { + /// Entry::Occupied(occupied) => { + /// assert_eq!(occupied.key(), &"serde"); + /// } + /// Entry::Vacant(_) => unimplemented!(), + /// } + /// ``` + #[inline] + pub fn key(&self) -> &String { + self.occupied.key() + } + + /// Gets a reference to the value in the entry. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// use serde_json::map::Entry; + /// + /// let mut map = serde_json::Map::new(); + /// map.insert("serde".to_owned(), json!(12)); + /// + /// match map.entry("serde") { + /// Entry::Occupied(occupied) => { + /// assert_eq!(occupied.get(), 12); + /// } + /// Entry::Vacant(_) => unimplemented!(), + /// } + /// ``` + #[inline] + pub fn get(&self) -> &Value { + self.occupied.get() + } + + /// Gets a mutable reference to the value in the entry. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// use serde_json::map::Entry; + /// + /// let mut map = serde_json::Map::new(); + /// map.insert("serde".to_owned(), json!([1, 2, 3])); + /// + /// match map.entry("serde") { + /// Entry::Occupied(mut occupied) => { + /// occupied.get_mut().as_array_mut().unwrap().push(json!(4)); + /// } + /// Entry::Vacant(_) => unimplemented!(), + /// } + /// + /// assert_eq!(map["serde"].as_array().unwrap().len(), 4); + /// ``` + #[inline] + pub fn get_mut(&mut self) -> &mut Value { + self.occupied.get_mut() + } + + /// Converts the entry into a mutable reference to its value. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// use serde_json::map::Entry; + /// + /// let mut map = serde_json::Map::new(); + /// map.insert("serde".to_owned(), json!([1, 2, 3])); + /// + /// match map.entry("serde") { + /// Entry::Occupied(mut occupied) => { + /// occupied.into_mut().as_array_mut().unwrap().push(json!(4)); + /// } + /// Entry::Vacant(_) => unimplemented!(), + /// } + /// + /// assert_eq!(map["serde"].as_array().unwrap().len(), 4); + /// ``` + #[inline] + pub fn into_mut(self) -> &'a mut Value { + self.occupied.into_mut() + } + + /// Sets the value of the entry with the `OccupiedEntry`'s key, and returns + /// the entry's old value. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// use serde_json::map::Entry; + /// + /// let mut map = serde_json::Map::new(); + /// map.insert("serde".to_owned(), json!(12)); + /// + /// match map.entry("serde") { + /// Entry::Occupied(mut occupied) => { + /// assert_eq!(occupied.insert(json!(13)), 12); + /// assert_eq!(occupied.get(), 13); + /// } + /// Entry::Vacant(_) => unimplemented!(), + /// } + /// ``` + #[inline] + pub fn insert(&mut self, value: Value) -> Value { + self.occupied.insert(value) + } + + /// Takes the value of the entry out of the map, and returns it. + /// + /// If serde_json'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. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// use serde_json::map::Entry; + /// + /// let mut map = serde_json::Map::new(); + /// map.insert("serde".to_owned(), json!(12)); + /// + /// match map.entry("serde") { + /// Entry::Occupied(occupied) => { + /// assert_eq!(occupied.remove(), 12); + /// } + /// Entry::Vacant(_) => unimplemented!(), + /// } + /// ``` + #[inline] + pub fn remove(self) -> Value { + #[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")] + #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] + #[inline] + pub fn swap_remove(self) -> Value { + 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")] + #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] + #[inline] + pub fn shift_remove(self) -> Value { + self.occupied.shift_remove() + } + + /// Removes the entry from the map, returning the stored key and value. + /// + /// If serde_json'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. + /// + /// # Examples + /// + /// ``` + /// # use serde_json::json; + /// # + /// use serde_json::map::Entry; + /// + /// let mut map = serde_json::Map::new(); + /// map.insert("serde".to_owned(), json!(12)); + /// + /// match map.entry("serde") { + /// Entry::Occupied(occupied) => { + /// let (key, value) = occupied.remove_entry(); + /// assert_eq!(key, "serde"); + /// assert_eq!(value, 12); + /// } + /// Entry::Vacant(_) => unimplemented!(), + /// } + /// ``` + #[inline] + pub fn remove_entry(self) -> (String, Value) { + #[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")] + #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] + #[inline] + pub fn swap_remove_entry(self) -> (String, Value) { + 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")] + #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] + #[inline] + pub fn shift_remove_entry(self) -> (String, Value) { + self.occupied.shift_remove_entry() + } +} + +////////////////////////////////////////////////////////////////////////////// + +impl<'a> IntoIterator for &'a Map { + type Item = (&'a String, &'a Value); + type IntoIter = Iter<'a>; + #[inline] + fn into_iter(self) -> Self::IntoIter { + Iter { + iter: self.map.iter(), + } + } +} + +/// An iterator over a serde_json::Map's entries. +pub struct Iter<'a> { + iter: IterImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type IterImpl<'a> = btree_map::Iter<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type IterImpl<'a> = indexmap::map::Iter<'a, String, Value>; + +delegate_iterator!((Iter<'a>) => (&'a String, &'a Value)); + +////////////////////////////////////////////////////////////////////////////// + +impl<'a> IntoIterator for &'a mut Map { + type Item = (&'a String, &'a mut Value); + type IntoIter = IterMut<'a>; + #[inline] + fn into_iter(self) -> Self::IntoIter { + IterMut { + iter: self.map.iter_mut(), + } + } +} + +/// A mutable iterator over a serde_json::Map's entries. +pub struct IterMut<'a> { + iter: IterMutImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type IterMutImpl<'a> = btree_map::IterMut<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type IterMutImpl<'a> = indexmap::map::IterMut<'a, String, Value>; + +delegate_iterator!((IterMut<'a>) => (&'a String, &'a mut Value)); + +////////////////////////////////////////////////////////////////////////////// + +impl IntoIterator for Map { + type Item = (String, Value); + type IntoIter = IntoIter; + #[inline] + fn into_iter(self) -> Self::IntoIter { + IntoIter { + iter: self.map.into_iter(), + } + } +} + +/// An owning iterator over a serde_json::Map's entries. +pub struct IntoIter { + iter: IntoIterImpl, +} + +#[cfg(not(feature = "preserve_order"))] +type IntoIterImpl = btree_map::IntoIter; +#[cfg(feature = "preserve_order")] +type IntoIterImpl = indexmap::map::IntoIter; + +delegate_iterator!((IntoIter) => (String, Value)); + +////////////////////////////////////////////////////////////////////////////// + +/// An iterator over a serde_json::Map's keys. +pub struct Keys<'a> { + iter: KeysImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type KeysImpl<'a> = btree_map::Keys<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type KeysImpl<'a> = indexmap::map::Keys<'a, String, Value>; + +delegate_iterator!((Keys<'a>) => &'a String); + +////////////////////////////////////////////////////////////////////////////// + +/// An iterator over a serde_json::Map's values. +pub struct Values<'a> { + iter: ValuesImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type ValuesImpl<'a> = btree_map::Values<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type ValuesImpl<'a> = indexmap::map::Values<'a, String, Value>; + +delegate_iterator!((Values<'a>) => &'a Value); + +////////////////////////////////////////////////////////////////////////////// + +/// A mutable iterator over a serde_json::Map's values. +pub struct ValuesMut<'a> { + iter: ValuesMutImpl<'a>, +} + +#[cfg(not(feature = "preserve_order"))] +type ValuesMutImpl<'a> = btree_map::ValuesMut<'a, String, Value>; +#[cfg(feature = "preserve_order")] +type ValuesMutImpl<'a> = indexmap::map::ValuesMut<'a, String, Value>; + +delegate_iterator!((ValuesMut<'a>) => &'a mut Value); + +////////////////////////////////////////////////////////////////////////////// + +/// An owning iterator over a serde_json::Map's values. +pub struct IntoValues { + iter: IntoValuesImpl, +} + +#[cfg(not(feature = "preserve_order"))] +type IntoValuesImpl = btree_map::IntoValues; +#[cfg(feature = "preserve_order")] +type IntoValuesImpl = indexmap::map::IntoValues; + +delegate_iterator!((IntoValues) => Value); diff --git a/crates/amf0/src/value.rs b/crates/amf0/src/value.rs index 05ec757f4..8643083d6 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -1,17 +1,17 @@ //! AMF0 value types. -use std::collections::BTreeMap; use std::io; use std::marker::PhantomData; use scuffle_bytes_util::StringCow; use serde::de::IntoDeserializer; +use crate::map::Map; use crate::Amf0Error; use crate::encoder::Amf0Encoder; /// Represents any AMF0 object. -pub type Amf0Object<'a> = BTreeMap, Amf0Value<'a>>; +pub type Amf0Object<'a> = Map, Amf0Value<'a>>; /// Represents any AMF0 array. pub type Amf0Array<'a> = Vec>; From c70fc8a80d4088b42d348acadc22e10b12958136 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sun, 1 Jun 2025 14:16:40 +0000 Subject: [PATCH 07/24] fix object impl --- Cargo.lock | 1 + crates/amf0/Cargo.toml | 3 + crates/amf0/src/decoder.rs | 15 +- crates/amf0/src/encoder.rs | 17 +- crates/amf0/src/lib.rs | 7 +- crates/amf0/src/{map.rs => object.rs} | 463 +++++++++++++----------- crates/amf0/src/value.rs | 67 +--- crates/bytes-util/src/cow/string/mod.rs | 8 +- 8 files changed, 299 insertions(+), 282 deletions(-) rename crates/amf0/src/{map.rs => object.rs} (73%) diff --git a/Cargo.lock b/Cargo.lock index e65a927cb..56c793f64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3164,6 +3164,7 @@ dependencies = [ "bytes", "bytestring", "document-features", + "indexmap", "num-derive", "num-traits", "scuffle-bytes-util", diff --git a/crates/amf0/Cargo.toml b/crates/amf0/Cargo.toml index 45666b5fd..6f9e875ea 100644 --- a/crates/amf0/Cargo.toml +++ b/crates/amf0/Cargo.toml @@ -19,6 +19,7 @@ bytestring = "1.4.0" document-features = { optional = true, version = "0.2" } num-derive = "0.4" num-traits = "0.2" +indexmap = { version = "2.4.0", optional = true } 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 @@ -33,6 +34,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/src/decoder.rs b/crates/amf0/src/decoder.rs index 6ccf96c78..bca3a8d1a 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. /// @@ -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,11 @@ 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("defg").unwrap(), Amf0Value::Boolean(true)); } #[test] diff --git a/crates/amf0/src/encoder.rs b/crates/amf0/src/encoder.rs index d0add67a1..99077d07e 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. /// @@ -83,11 +84,17 @@ where } /// 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()?)?; + 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(()) diff --git a/crates/amf0/src/lib.rs b/crates/amf0/src/lib.rs index 4d4ce57c0..dd2955991 100644 --- a/crates/amf0/src/lib.rs +++ b/crates/amf0/src/lib.rs @@ -51,21 +51,20 @@ pub mod de; pub mod decoder; pub mod encoder; pub mod error; +pub mod object; #[cfg(feature = "serde")] pub mod ser; pub mod value; -pub mod map; #[cfg(feature = "serde")] 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}; - -extern crate alloc; +pub use value::Amf0Value; /// AMF0 marker types. /// diff --git a/crates/amf0/src/map.rs b/crates/amf0/src/object.rs similarity index 73% rename from crates/amf0/src/map.rs rename to crates/amf0/src/object.rs index 5f34a5cc3..c81be77c3 100644 --- a/crates/amf0/src/map.rs +++ b/crates/amf0/src/object.rs @@ -8,28 +8,26 @@ //! [`BTreeMap`]: std::collections::BTreeMap //! [`IndexMap`]: indexmap::IndexMap -use crate::Amf0Error as Error; -use crate::Amf0Value as Value; -use alloc::string::String; -#[cfg(feature = "preserve_order")] -use alloc::vec::Vec; +#[cfg(not(feature = "preserve_order"))] +use alloc::collections::{BTreeMap, btree_map}; use core::borrow::Borrow; use core::fmt::{self, Debug}; -use core::hash::{Hash, Hasher}; +use core::hash::Hash; use core::iter::FusedIterator; -#[cfg(feature = "preserve_order")] -use core::mem; use core::ops; -use serde::de; +use std::marker::PhantomData; -#[cfg(not(feature = "preserve_order"))] -use alloc::collections::{btree_map, BTreeMap}; #[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. -pub struct Map { - map: MapImpl, +#[derive(Clone, PartialEq)] +pub struct Amf0Object<'a> { + map: MapImpl, Amf0Value<'a>>, } #[cfg(not(feature = "preserve_order"))] @@ -37,19 +35,23 @@ type MapImpl = BTreeMap; #[cfg(feature = "preserve_order")] type MapImpl = IndexMap; -impl Map { +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 { - Map { - map: MapImpl::new(), - } + Amf0Object { map: MapImpl::new() } } /// Makes a new empty Map with the given initial capacity. #[inline] pub fn with_capacity(capacity: usize) -> Self { - Map { + Amf0Object { #[cfg(not(feature = "preserve_order"))] map: { // does not support with_capacity @@ -72,9 +74,9 @@ impl 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. #[inline] - pub fn get(&self, key: &Q) -> Option<&Value> + pub fn get(&self, key: &Q) -> Option<&Amf0Value<'a>> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { self.map.get(key) @@ -87,7 +89,7 @@ impl Map { #[inline] pub fn contains_key(&self, key: &Q) -> bool where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { self.map.contains_key(key) @@ -98,9 +100,9 @@ impl 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. #[inline] - pub fn get_mut(&mut self, key: &Q) -> Option<&mut Value> + pub fn get_mut(&mut self, key: &Q) -> Option<&mut Amf0Value<'a>> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { self.map.get_mut(key) @@ -111,9 +113,9 @@ impl 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. #[inline] - pub fn get_key_value(&self, key: &Q) -> Option<(&String, &Value)> + pub fn get_key_value(&self, key: &Q) -> Option<(&StringCow<'a>, &Amf0Value<'a>)> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { self.map.get_key_value(key) @@ -126,7 +128,7 @@ impl Map { /// 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: String, v: Value) -> Option { + pub fn insert(&mut self, k: StringCow<'a>, v: Amf0Value<'a>) -> Option> { self.map.insert(k, v) } @@ -137,9 +139,8 @@ impl Map { /// 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")] - #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] - pub fn shift_insert(&mut self, index: usize, k: String, v: Value) -> Option { + pub fn shift_insert(&mut self, index: usize, k: StringCow<'a>, v: Amf0Value<'a>) -> Option> { self.map.shift_insert(index, k, v) } @@ -155,9 +156,9 @@ impl Map { /// 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 + pub fn remove(&mut self, key: &Q) -> Option> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { #[cfg(feature = "preserve_order")] @@ -178,9 +179,9 @@ impl Map { /// 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<(String, Value)> + pub fn remove_entry(&mut self, key: &Q) -> Option<(StringCow<'a>, Amf0Value<'a>)> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { #[cfg(feature = "preserve_order")] @@ -199,9 +200,9 @@ impl Map { #[cfg(feature = "preserve_order")] #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] - pub fn swap_remove(&mut self, key: &Q) -> Option + pub fn swap_remove(&mut self, key: &Q) -> Option> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { self.map.swap_remove(key) @@ -217,9 +218,9 @@ impl Map { #[cfg(feature = "preserve_order")] #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] - pub fn swap_remove_entry(&mut self, key: &Q) -> Option<(String, Value)> + pub fn swap_remove_entry(&mut self, key: &Q) -> Option<(StringCow<'a>, Amf0Value<'a>)> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { self.map.swap_remove_entry(key) @@ -235,9 +236,9 @@ impl Map { #[cfg(feature = "preserve_order")] #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] - pub fn shift_remove(&mut self, key: &Q) -> Option + pub fn shift_remove(&mut self, key: &Q) -> Option> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { self.map.shift_remove(key) @@ -253,9 +254,9 @@ impl Map { #[cfg(feature = "preserve_order")] #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] - pub fn shift_remove_entry(&mut self, key: &Q) -> Option<(String, Value)> + pub fn shift_remove_entry(&mut self, key: &Q) -> Option<(StringCow<'a>, Amf0Value<'a>)> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { self.map.shift_remove_entry(key) @@ -265,20 +266,17 @@ impl Map { #[inline] pub fn append(&mut self, other: &mut Self) { #[cfg(feature = "preserve_order")] - self.map - .extend(mem::replace(&mut other.map, MapImpl::default())); + 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: S) -> Entry - where - S: Into, - { + 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; @@ -302,15 +300,13 @@ impl Map { /// Gets an iterator over the entries of the map. #[inline] - pub fn iter(&self) -> Iter { - Iter { - iter: self.map.iter(), - } + 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 { + pub fn iter_mut(&mut self) -> IterMut<'_, 'a> { IterMut { iter: self.map.iter_mut(), } @@ -318,23 +314,19 @@ impl Map { /// Gets an iterator over the keys of the map. #[inline] - pub fn keys(&self) -> Keys { - Keys { - iter: self.map.keys(), - } + 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 { - Values { - iter: self.map.values(), - } + 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 { + pub fn values_mut(&mut self) -> ValuesMut<'_, 'a> { ValuesMut { iter: self.map.values_mut(), } @@ -342,7 +334,7 @@ impl Map { /// Gets an iterator over the values of the map. #[inline] - pub fn into_values(self) -> IntoValues { + pub fn into_values(self) -> IntoValues<'a> { IntoValues { iter: self.map.into_values(), } @@ -355,7 +347,7 @@ impl Map { #[inline] pub fn retain(&mut self, f: F) where - F: FnMut(&String, &mut Value) -> bool, + F: FnMut(&StringCow<'a>, &mut Amf0Value<'a>) -> bool, { self.map.retain(f); } @@ -384,55 +376,6 @@ impl Map { } } -#[allow(clippy::derivable_impls)] // clippy bug: https://github.com/rust-lang/rust-clippy/issues/7655 -impl Default for Map { - #[inline] - fn default() -> Self { - Map { - map: MapImpl::new(), - } - } -} - -impl Clone for Map { - #[inline] - fn clone(&self) -> Self { - Map { - map: self.map.clone(), - } - } - - #[inline] - fn clone_from(&mut self, source: &Self) { - self.map.clone_from(&source.map); - } -} - -impl PartialEq for Map { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.map.eq(&other.map) - } -} - -impl Eq for Map {} - -impl Hash for Map { - fn hash(&self, state: &mut H) { - #[cfg(not(feature = "preserve_order"))] - { - self.map.hash(state); - } - - #[cfg(feature = "preserve_order")] - { - let mut kv = Vec::from_iter(&self.map); - kv.sort_unstable_by(|a, b| a.0.cmp(b.0)); - kv.hash(state); - } - } -} - /// Access an element of this map. Panics if the given key is not present in the /// map. /// @@ -449,14 +392,14 @@ impl Hash for Map { /// } /// # ; /// ``` -impl ops::Index<&Q> for Map +impl<'a, Q> ops::Index<&Q> for Amf0Object<'a> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { - type Output = Value; + type Output = Amf0Value<'a>; - fn index(&self, index: &Q) -> &Value { + fn index(&self, index: &Q) -> &Self::Output { self.map.index(index) } } @@ -472,40 +415,41 @@ where /// # /// map["key"] = json!("value"); /// ``` -impl ops::IndexMut<&Q> for Map +impl<'a, Q> ops::IndexMut<&Q> for Amf0Object<'a> where - String: Borrow, + StringCow<'a>: Borrow, Q: ?Sized + Ord + Eq + Hash, { - fn index_mut(&mut self, index: &Q) -> &mut Value { + fn index_mut(&mut self, index: &Q) -> &mut Self::Output { self.map.get_mut(index).expect("no entry found for key") } } -impl Debug for Map { +impl Debug for Amf0Object<'_> { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { self.map.fmt(formatter) } } -#[cfg(any(feature = "std", feature = "alloc"))] -impl serde::ser::Serialize for Map { +#[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 = tri!(serializer.serialize_map(Some(self.len()))); + let mut map = serializer.serialize_map(Some(self.len()))?; for (k, v) in self { - tri!(map.serialize_entry(k, v)); + map.serialize_entry(k, v)?; } map.end() } } -impl<'de> de::Deserialize<'de> for Map { +#[cfg(feature = "serde")] +impl<'de> de::Deserialize<'de> for Amf0Object<'de> { #[inline] fn deserialize(deserializer: D) -> Result where @@ -514,7 +458,7 @@ impl<'de> de::Deserialize<'de> for Map { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { - type Value = Map; + type Value = Amf0Object<'de>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a map") @@ -525,18 +469,17 @@ impl<'de> de::Deserialize<'de> for Map { where E: de::Error, { - Ok(Map::new()) + Ok(Amf0Object::new()) } - #[cfg(any(feature = "std", feature = "alloc"))] #[inline] fn visit_map(self, mut visitor: V) -> Result where V: de::MapAccess<'de>, { - let mut values = Map::new(); + let mut values = Amf0Object::new(); - while let Some((key, value)) = tri!(visitor.next_entry()) { + while let Some((key, value)) = visitor.next_entry()? { values.insert(key, value); } @@ -548,21 +491,21 @@ impl<'de> de::Deserialize<'de> for Map { } } -impl FromIterator<(String, Value)> for Map { +impl<'a> FromIterator<(StringCow<'a>, Amf0Value<'a>)> for Amf0Object<'a> { fn from_iter(iter: T) -> Self where - T: IntoIterator, + T: IntoIterator, Amf0Value<'a>)>, { - Map { + Amf0Object { map: FromIterator::from_iter(iter), } } } -impl Extend<(String, Value)> for Map { +impl<'a> Extend<(StringCow<'a>, Amf0Value<'a>)> for Amf0Object<'a> { fn extend(&mut self, iter: T) where - T: IntoIterator, + T: IntoIterator, Amf0Value<'a>)>, { self.map.extend(iter); } @@ -600,7 +543,8 @@ macro_rules! delegate_iterator { } } -impl<'de> de::IntoDeserializer<'de, Error> for Map { +#[cfg(feature = "serde")] +impl<'de> de::IntoDeserializer<'de, Amf0Error> for Amf0Object<'de> { type Deserializer = Self; fn into_deserializer(self) -> Self::Deserializer { @@ -608,7 +552,8 @@ impl<'de> de::IntoDeserializer<'de, Error> for Map { } } -impl<'de> de::IntoDeserializer<'de, Error> for &'de Map { +#[cfg(feature = "serde")] +impl<'de> de::IntoDeserializer<'de, Amf0Error> for &'de Amf0Object<'de> { type Deserializer = Self; fn into_deserializer(self) -> Self::Deserializer { @@ -616,40 +561,127 @@ impl<'de> de::IntoDeserializer<'de, Error> for &'de Map { } } +#[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 [`Map`]. /// /// [`entry`]: Map::entry -pub enum Entry<'a> { +pub enum Entry<'a, 'b> { /// A vacant Entry. - Vacant(VacantEntry<'a>), + Vacant(VacantEntry<'a, 'b>), /// An occupied Entry. - Occupied(OccupiedEntry<'a>), + Occupied(OccupiedEntry<'a, 'b>), } /// A vacant Entry. It is part of the [`Entry`] enum. -pub struct VacantEntry<'a> { - vacant: VacantEntryImpl<'a>, +pub struct VacantEntry<'a, 'b> { + vacant: VacantEntryImpl<'a, 'b>, } /// An occupied Entry. It is part of the [`Entry`] enum. -pub struct OccupiedEntry<'a> { - occupied: OccupiedEntryImpl<'a>, +pub struct OccupiedEntry<'a, 'b> { + occupied: OccupiedEntryImpl<'a, 'b>, } #[cfg(not(feature = "preserve_order"))] -type VacantEntryImpl<'a> = btree_map::VacantEntry<'a, String, Value>; +type VacantEntryImpl<'a, 'b> = btree_map::VacantEntry<'a, StringCow<'b>, Amf0Value<'b>>; #[cfg(feature = "preserve_order")] -type VacantEntryImpl<'a> = indexmap::map::VacantEntry<'a, String, Value>; +type VacantEntryImpl<'a, 'b> = indexmap::map::VacantEntry<'a, StringCow<'b>, Amf0Value<'b>>; #[cfg(not(feature = "preserve_order"))] -type OccupiedEntryImpl<'a> = btree_map::OccupiedEntry<'a, String, Value>; +type OccupiedEntryImpl<'a, 'b> = btree_map::OccupiedEntry<'a, StringCow<'b>, Amf0Value<'b>>; #[cfg(feature = "preserve_order")] -type OccupiedEntryImpl<'a> = indexmap::map::OccupiedEntry<'a, String, Value>; +type OccupiedEntryImpl<'a, 'b> = indexmap::map::OccupiedEntry<'a, StringCow<'b>, Amf0Value<'b>>; -impl<'a> Entry<'a> { +impl<'a, 'b> Entry<'a, 'b> { /// Returns a reference to this entry's key. /// /// # Examples @@ -658,7 +690,7 @@ impl<'a> Entry<'a> { /// let mut map = serde_json::Map::new(); /// assert_eq!(map.entry("serde").key(), &"serde"); /// ``` - pub fn key(&self) -> &String { + pub fn key(&self) -> &StringCow<'b> { match self { Entry::Vacant(e) => e.key(), Entry::Occupied(e) => e.key(), @@ -678,7 +710,7 @@ impl<'a> Entry<'a> { /// /// assert_eq!(map["serde"], 12); /// ``` - pub fn or_insert(self, default: Value) -> &'a mut Value { + 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(), @@ -699,9 +731,9 @@ impl<'a> Entry<'a> { /// /// assert_eq!(map["serde"], "hoho".to_owned()); /// ``` - pub fn or_insert_with(self, default: F) -> &'a mut Value + pub fn or_insert_with(self, default: F) -> &'a mut Amf0Value<'b> where - F: FnOnce() -> Value, + F: FnOnce() -> Amf0Value<'b>, { match self { Entry::Vacant(entry) => entry.insert(default()), @@ -732,7 +764,7 @@ impl<'a> Entry<'a> { /// ``` pub fn and_modify(self, f: F) -> Self where - F: FnOnce(&mut Value), + F: FnOnce(&mut Amf0Value<'b>), { match self { Entry::Occupied(mut entry) => { @@ -744,7 +776,7 @@ impl<'a> Entry<'a> { } } -impl<'a> VacantEntry<'a> { +impl<'a, 'b> VacantEntry<'a, 'b> { /// Gets a reference to the key that would be used when inserting a value /// through the VacantEntry. /// @@ -763,7 +795,7 @@ impl<'a> VacantEntry<'a> { /// } /// ``` #[inline] - pub fn key(&self) -> &String { + pub fn key(&self) -> &StringCow<'b> { self.vacant.key() } @@ -787,12 +819,12 @@ impl<'a> VacantEntry<'a> { /// } /// ``` #[inline] - pub fn insert(self, value: Value) -> &'a mut Value { + pub fn insert(self, value: Amf0Value<'b>) -> &'a mut Amf0Value<'b> { self.vacant.insert(value) } } -impl<'a> OccupiedEntry<'a> { +impl<'a, 'b> OccupiedEntry<'a, 'b> { /// Gets a reference to the key in the entry. /// /// # Examples @@ -813,7 +845,7 @@ impl<'a> OccupiedEntry<'a> { /// } /// ``` #[inline] - pub fn key(&self) -> &String { + pub fn key(&self) -> &StringCow<'b> { self.occupied.key() } @@ -837,7 +869,7 @@ impl<'a> OccupiedEntry<'a> { /// } /// ``` #[inline] - pub fn get(&self) -> &Value { + pub fn get(&self) -> &Amf0Value<'b> { self.occupied.get() } @@ -863,7 +895,7 @@ impl<'a> OccupiedEntry<'a> { /// assert_eq!(map["serde"].as_array().unwrap().len(), 4); /// ``` #[inline] - pub fn get_mut(&mut self) -> &mut Value { + pub fn get_mut(&mut self) -> &mut Amf0Value<'b> { self.occupied.get_mut() } @@ -889,7 +921,7 @@ impl<'a> OccupiedEntry<'a> { /// assert_eq!(map["serde"].as_array().unwrap().len(), 4); /// ``` #[inline] - pub fn into_mut(self) -> &'a mut Value { + pub fn into_mut(self) -> &'a mut Amf0Value<'b> { self.occupied.into_mut() } @@ -915,7 +947,7 @@ impl<'a> OccupiedEntry<'a> { /// } /// ``` #[inline] - pub fn insert(&mut self, value: Value) -> Value { + pub fn insert(&mut self, value: Amf0Value<'b>) -> Amf0Value<'b> { self.occupied.insert(value) } @@ -945,7 +977,7 @@ impl<'a> OccupiedEntry<'a> { /// } /// ``` #[inline] - pub fn remove(self) -> Value { + pub fn remove(self) -> Amf0Value<'b> { #[cfg(feature = "preserve_order")] return self.swap_remove(); #[cfg(not(feature = "preserve_order"))] @@ -962,7 +994,7 @@ impl<'a> OccupiedEntry<'a> { #[cfg(feature = "preserve_order")] #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] - pub fn swap_remove(self) -> Value { + pub fn swap_remove(self) -> Amf0Value<'b> { self.occupied.swap_remove() } @@ -976,7 +1008,7 @@ impl<'a> OccupiedEntry<'a> { #[cfg(feature = "preserve_order")] #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] - pub fn shift_remove(self) -> Value { + pub fn shift_remove(self) -> Amf0Value<'b> { self.occupied.shift_remove() } @@ -1008,7 +1040,7 @@ impl<'a> OccupiedEntry<'a> { /// } /// ``` #[inline] - pub fn remove_entry(self) -> (String, Value) { + pub fn remove_entry(self) -> (StringCow<'b>, Amf0Value<'b>) { #[cfg(feature = "preserve_order")] return self.swap_remove_entry(); #[cfg(not(feature = "preserve_order"))] @@ -1025,7 +1057,7 @@ impl<'a> OccupiedEntry<'a> { #[cfg(feature = "preserve_order")] #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] - pub fn swap_remove_entry(self) -> (String, Value) { + pub fn swap_remove_entry(self) -> (StringCow<'b>, Amf0Value<'b>) { self.occupied.swap_remove_entry() } @@ -1039,41 +1071,41 @@ impl<'a> OccupiedEntry<'a> { #[cfg(feature = "preserve_order")] #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] - pub fn shift_remove_entry(self) -> (String, Value) { + pub fn shift_remove_entry(self) -> (StringCow<'b>, Amf0Value<'b>) { self.occupied.shift_remove_entry() } } ////////////////////////////////////////////////////////////////////////////// -impl<'a> IntoIterator for &'a Map { - type Item = (&'a String, &'a Value); - type IntoIter = Iter<'a>; +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(), - } + Iter { iter: self.map.iter() } } } /// An iterator over a serde_json::Map's entries. -pub struct Iter<'a> { - iter: IterImpl<'a>, +pub struct Iter<'a, 'b> { + iter: IterImpl<'a, 'b>, } #[cfg(not(feature = "preserve_order"))] -type IterImpl<'a> = btree_map::Iter<'a, String, Value>; +type IterImpl<'a, 'b> = btree_map::Iter<'a, StringCow<'b>, Amf0Value<'b>>; #[cfg(feature = "preserve_order")] -type IterImpl<'a> = indexmap::map::Iter<'a, String, Value>; +type IterImpl<'a, 'b> = indexmap::map::Iter<'a, StringCow<'b>, Amf0Value<'b>>; -delegate_iterator!((Iter<'a>) => (&'a String, &'a Value)); +delegate_iterator!((Iter<'a, 'b>) => (&'a StringCow<'b>, &'a Amf0Value<'b>)); ////////////////////////////////////////////////////////////////////////////// -impl<'a> IntoIterator for &'a mut Map { - type Item = (&'a String, &'a mut Value); - type IntoIter = IterMut<'a>; +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 { @@ -1083,22 +1115,23 @@ impl<'a> IntoIterator for &'a mut Map { } /// A mutable iterator over a serde_json::Map's entries. -pub struct IterMut<'a> { - iter: IterMutImpl<'a>, +pub struct IterMut<'a, 'b> { + iter: IterMutImpl<'a, 'b>, } #[cfg(not(feature = "preserve_order"))] -type IterMutImpl<'a> = btree_map::IterMut<'a, String, Value>; +type IterMutImpl<'a, 'b> = btree_map::IterMut<'a, StringCow<'b>, Amf0Value<'b>>; #[cfg(feature = "preserve_order")] -type IterMutImpl<'a> = indexmap::map::IterMut<'a, String, Value>; +type IterMutImpl<'a, 'b> = indexmap::map::IterMut<'a, StringCow<'b>, Amf0Value<'b>>; -delegate_iterator!((IterMut<'a>) => (&'a String, &'a mut Value)); +delegate_iterator!((IterMut<'a, 'b>) => (&'a StringCow<'b>, &'a mut Amf0Value<'b>)); ////////////////////////////////////////////////////////////////////////////// -impl IntoIterator for Map { - type Item = (String, Value); - type IntoIter = IntoIter; +impl<'a> IntoIterator for Amf0Object<'a> { + type IntoIter = IntoIter<'a>; + type Item = (StringCow<'a>, Amf0Value<'a>); + #[inline] fn into_iter(self) -> Self::IntoIter { IntoIter { @@ -1108,69 +1141,69 @@ impl IntoIterator for Map { } /// An owning iterator over a serde_json::Map's entries. -pub struct IntoIter { - iter: IntoIterImpl, +pub struct IntoIter<'a> { + iter: IntoIterImpl<'a>, } #[cfg(not(feature = "preserve_order"))] -type IntoIterImpl = btree_map::IntoIter; +type IntoIterImpl<'a> = btree_map::IntoIter, Amf0Value<'a>>; #[cfg(feature = "preserve_order")] -type IntoIterImpl = indexmap::map::IntoIter; +type IntoIterImpl<'a> = indexmap::map::IntoIter, Amf0Value<'a>>; -delegate_iterator!((IntoIter) => (String, Value)); +delegate_iterator!((IntoIter<'a>) => (StringCow<'a>, Amf0Value<'a>)); ////////////////////////////////////////////////////////////////////////////// /// An iterator over a serde_json::Map's keys. -pub struct Keys<'a> { - iter: KeysImpl<'a>, +pub struct Keys<'a, 'b> { + iter: KeysImpl<'a, 'b>, } #[cfg(not(feature = "preserve_order"))] -type KeysImpl<'a> = btree_map::Keys<'a, String, Value>; +type KeysImpl<'a, 'b> = btree_map::Keys<'a, StringCow<'b>, Amf0Value<'b>>; #[cfg(feature = "preserve_order")] -type KeysImpl<'a> = indexmap::map::Keys<'a, String, Value>; +type KeysImpl<'a, 'b> = indexmap::map::Keys<'a, StringCow<'b>, Amf0Value<'b>>; -delegate_iterator!((Keys<'a>) => &'a String); +delegate_iterator!((Keys<'a, 'b>) => &'a StringCow<'b>); ////////////////////////////////////////////////////////////////////////////// /// An iterator over a serde_json::Map's values. -pub struct Values<'a> { - iter: ValuesImpl<'a>, +pub struct Values<'a, 'b> { + iter: ValuesImpl<'a, 'b>, } #[cfg(not(feature = "preserve_order"))] -type ValuesImpl<'a> = btree_map::Values<'a, String, Value>; +type ValuesImpl<'a, 'b> = btree_map::Values<'a, StringCow<'b>, Amf0Value<'b>>; #[cfg(feature = "preserve_order")] -type ValuesImpl<'a> = indexmap::map::Values<'a, String, Value>; +type ValuesImpl<'a, 'b> = indexmap::map::Values<'a, StringCow<'b>, Amf0Value<'b>>; -delegate_iterator!((Values<'a>) => &'a Value); +delegate_iterator!((Values<'a, 'b>) => &'a Amf0Value<'b>); ////////////////////////////////////////////////////////////////////////////// /// A mutable iterator over a serde_json::Map's values. -pub struct ValuesMut<'a> { - iter: ValuesMutImpl<'a>, +pub struct ValuesMut<'a, 'b> { + iter: ValuesMutImpl<'a, 'b>, } #[cfg(not(feature = "preserve_order"))] -type ValuesMutImpl<'a> = btree_map::ValuesMut<'a, String, Value>; +type ValuesMutImpl<'a, 'b> = btree_map::ValuesMut<'a, StringCow<'b>, Amf0Value<'b>>; #[cfg(feature = "preserve_order")] -type ValuesMutImpl<'a> = indexmap::map::ValuesMut<'a, String, Value>; +type ValuesMutImpl<'a, 'b> = indexmap::map::ValuesMut<'a, StringCow<'b>, Amf0Value<'b>>; -delegate_iterator!((ValuesMut<'a>) => &'a mut Value); +delegate_iterator!((ValuesMut<'a, 'b>) => &'a mut Amf0Value<'b>); ////////////////////////////////////////////////////////////////////////////// /// An owning iterator over a serde_json::Map's values. -pub struct IntoValues { - iter: IntoValuesImpl, +pub struct IntoValues<'a> { + iter: IntoValuesImpl<'a>, } #[cfg(not(feature = "preserve_order"))] -type IntoValuesImpl = btree_map::IntoValues; +type IntoValuesImpl<'a> = btree_map::IntoValues, Amf0Value<'a>>; #[cfg(feature = "preserve_order")] -type IntoValuesImpl = indexmap::map::IntoValues; +type IntoValuesImpl<'a> = indexmap::map::IntoValues, Amf0Value<'a>>; -delegate_iterator!((IntoValues) => Value); +delegate_iterator!((IntoValues<'a>) => Amf0Value<'a>); diff --git a/crates/amf0/src/value.rs b/crates/amf0/src/value.rs index 8643083d6..bd22db520 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -1,19 +1,13 @@ //! AMF0 value types. use std::io; -use std::marker::PhantomData; use scuffle_bytes_util::StringCow; use serde::de::IntoDeserializer; -use crate::map::Map; use crate::Amf0Error; use crate::encoder::Amf0Encoder; - -/// Represents any AMF0 object. -pub type Amf0Object<'a> = Map, Amf0Value<'a>>; -/// Represents any AMF0 array. -pub type Amf0Array<'a> = Vec>; +use crate::object::Amf0Object; /// Represents any AMF0 value. #[derive(Debug, PartialEq, Clone)] @@ -29,7 +23,7 @@ pub enum Amf0Value<'a> { /// AMF0 Null. Null, /// AMF0 Array. - Array(Amf0Array<'a>), + Array(Vec>), } impl Amf0Value<'_> { @@ -210,7 +204,9 @@ impl<'de> serde::de::Deserialize<'de> for Amf0Value<'de> { where A: serde::de::MapAccess<'de>, { - let mut object = BTreeMap::new(); + use crate::object::Amf0Object; + + let mut object = Amf0Object::new(); while let Some((key, value)) = map.next_entry()? { object.insert(key, value); @@ -265,6 +261,7 @@ 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 @@ -285,6 +282,7 @@ macro_rules! impl_de_number { }; } +#[cfg(feature = "serde")] macro_rules! impl_deserializer { ($ty:ty) => { impl<'de> serde::Deserializer<'de> for $ty { @@ -367,20 +365,19 @@ macro_rules! impl_deserializer { 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) => visitor.visit_map(Amf0MapAccess { - iter: o.into_iter(), - value: None, - _phantom: PhantomData, - }), + 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>; @@ -389,6 +386,7 @@ impl<'de> serde::de::IntoDeserializer<'de, Amf0Error> for Amf0Value<'de> { } } +#[cfg(feature = "serde")] impl<'de> serde::de::IntoDeserializer<'de, Amf0Error> for &'de Amf0Value<'de> { type Deserializer = &'de Amf0Value<'de>; @@ -397,10 +395,12 @@ impl<'de> serde::de::IntoDeserializer<'de, Amf0Error> for &'de Amf0Value<'de> { } } +#[cfg(feature = "serde")] struct Amf0SeqAccess { iter: I, } +#[cfg(feature = "serde")] impl<'de, I> serde::de::SeqAccess<'de> for Amf0SeqAccess where I: Iterator, @@ -419,41 +419,6 @@ where } } -struct Amf0MapAccess { - iter: I, - value: Option, - _phantom: PhantomData, -} - -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()) - } -} - #[cfg(test)] #[cfg_attr(all(test, coverage_nightly), coverage(off))] mod tests { @@ -810,9 +775,7 @@ mod tests { #[cfg(feature = "serde")] #[test] fn deserialize_complex_structure() { - use std::collections::BTreeMap; - - let value = Amf0Value::Object(BTreeMap::from([ + let value = Amf0Value::Object(Amf0Object::from_iter([ ( StringCow::from("numbers"), Amf0Value::Array(vec![Amf0Value::Number(1.0), Amf0Value::Number(2.0)]), 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 { From 21b3ad7e72ed71fa8cdd47613b2c2d3ae0fa54c1 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Sun, 1 Jun 2025 20:58:14 -0400 Subject: [PATCH 08/24] fix docs and doctests --- crates/amf0/src/object.rs | 258 +++++--------------------------------- 1 file changed, 28 insertions(+), 230 deletions(-) diff --git a/crates/amf0/src/object.rs b/crates/amf0/src/object.rs index c81be77c3..0a3d167de 100644 --- a/crates/amf0/src/object.rs +++ b/crates/amf0/src/object.rs @@ -1,9 +1,9 @@ -//! This code is taken from https://github.com/serde-rs/json/blob/v1.0.140/src/map.rs +//! This code is modified from https://github.com/serde-rs/json/blob/v1.0.140/src/map.rs //! -//! A map of String to serde_json::Value. +//! 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 serde_json to use [`IndexMap`] instead. +//! feature of scuffle_amf0 to use [`IndexMap`] instead. //! //! [`BTreeMap`]: std::collections::BTreeMap //! [`IndexMap`]: indexmap::IndexMap @@ -150,7 +150,7 @@ impl<'a> Amf0Object<'a> { /// 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 serde_json's "preserve_order" is enabled, `.remove(key)` is + /// 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 @@ -173,7 +173,7 @@ impl<'a> Amf0Object<'a> { /// 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 serde_json's "preserve_order" is enabled, `.remove_entry(key)` is + /// 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 @@ -354,10 +354,10 @@ impl<'a> Amf0Object<'a> { /// Sorts this map's entries in-place using `str`'s usual ordering. /// - /// If serde_json's "preserve_order" feature is not enabled, this method + /// 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 serde_json's "preserve_order" feature is enabled, this method + /// 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 @@ -380,17 +380,15 @@ impl<'a> Amf0Object<'a> { /// map. /// /// ``` -/// # use serde_json::Value; -/// # -/// # let val = &Value::String("".to_owned()); -/// # let _ = -/// match val { -/// Value::String(s) => Some(s.as_str()), -/// Value::Array(arr) => arr[0].as_str(), -/// Value::Object(map) => map["type"].as_str(), -/// _ => None, -/// } -/// # ; +/// 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 @@ -406,15 +404,6 @@ where /// Mutably access an element of this map. Panics if the given key is not /// present in the map. -/// -/// ``` -/// # use serde_json::json; -/// # -/// # let mut map = serde_json::Map::new(); -/// # map.insert("key".to_owned(), serde_json::Value::Null); -/// # -/// map["key"] = json!("value"); -/// ``` impl<'a, Q> ops::IndexMut<&Q> for Amf0Object<'a> where StringCow<'a>: Borrow, @@ -687,7 +676,7 @@ impl<'a, 'b> Entry<'a, 'b> { /// # Examples /// /// ``` - /// let mut map = serde_json::Map::new(); + /// let mut map = scuffle_amf0::Amf0Object::new(); /// assert_eq!(map.entry("serde").key(), &"serde"); /// ``` pub fn key(&self) -> &StringCow<'b> { @@ -699,17 +688,6 @@ impl<'a, 'b> Entry<'a, 'b> { /// Ensures a value is in the entry by inserting the default if empty, and /// returns a mutable reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// let mut map = serde_json::Map::new(); - /// map.entry("serde").or_insert(json!(12)); - /// - /// assert_eq!(map["serde"], 12); - /// ``` pub fn or_insert(self, default: Amf0Value<'b>) -> &'a mut Amf0Value<'b> { match self { Entry::Vacant(entry) => entry.insert(default), @@ -720,17 +698,6 @@ impl<'a, 'b> Entry<'a, 'b> { /// 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. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// let mut map = serde_json::Map::new(); - /// map.entry("serde").or_insert_with(|| json!("hoho")); - /// - /// assert_eq!(map["serde"], "hoho".to_owned()); - /// ``` pub fn or_insert_with(self, default: F) -> &'a mut Amf0Value<'b> where F: FnOnce() -> Amf0Value<'b>, @@ -743,25 +710,6 @@ impl<'a, 'b> Entry<'a, 'b> { /// Provides in-place mutable access to an occupied entry before any /// potential inserts into the map. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// let mut map = serde_json::Map::new(); - /// map.entry("serde") - /// .and_modify(|e| *e = json!("rust")) - /// .or_insert(json!("cpp")); - /// - /// assert_eq!(map["serde"], "cpp"); - /// - /// map.entry("serde") - /// .and_modify(|e| *e = json!("rust")) - /// .or_insert(json!("cpp")); - /// - /// assert_eq!(map["serde"], "rust"); - /// ``` pub fn and_modify(self, f: F) -> Self where F: FnOnce(&mut Amf0Value<'b>), @@ -783,9 +731,9 @@ impl<'a, 'b> VacantEntry<'a, 'b> { /// # Examples /// /// ``` - /// use serde_json::map::Entry; + /// use scuffle_amf0::object::Entry; /// - /// let mut map = serde_json::Map::new(); + /// let mut map = scuffle_amf0::Amf0Object::new(); /// /// match map.entry("serde") { /// Entry::Vacant(vacant) => { @@ -801,23 +749,6 @@ impl<'a, 'b> VacantEntry<'a, 'b> { /// Sets the value of the entry with the VacantEntry's key, and returns a /// mutable reference to it. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// use serde_json::map::Entry; - /// - /// let mut map = serde_json::Map::new(); - /// - /// match map.entry("serde") { - /// Entry::Vacant(vacant) => { - /// vacant.insert(json!("hoho")); - /// } - /// Entry::Occupied(_) => unimplemented!(), - /// } - /// ``` #[inline] pub fn insert(self, value: Amf0Value<'b>) -> &'a mut Amf0Value<'b> { self.vacant.insert(value) @@ -826,100 +757,24 @@ impl<'a, 'b> VacantEntry<'a, 'b> { impl<'a, 'b> OccupiedEntry<'a, 'b> { /// Gets a reference to the key in the entry. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// use serde_json::map::Entry; - /// - /// let mut map = serde_json::Map::new(); - /// map.insert("serde".to_owned(), json!(12)); - /// - /// match map.entry("serde") { - /// Entry::Occupied(occupied) => { - /// assert_eq!(occupied.key(), &"serde"); - /// } - /// Entry::Vacant(_) => unimplemented!(), - /// } - /// ``` #[inline] pub fn key(&self) -> &StringCow<'b> { self.occupied.key() } /// Gets a reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// use serde_json::map::Entry; - /// - /// let mut map = serde_json::Map::new(); - /// map.insert("serde".to_owned(), json!(12)); - /// - /// match map.entry("serde") { - /// Entry::Occupied(occupied) => { - /// assert_eq!(occupied.get(), 12); - /// } - /// Entry::Vacant(_) => unimplemented!(), - /// } - /// ``` #[inline] pub fn get(&self) -> &Amf0Value<'b> { self.occupied.get() } /// Gets a mutable reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// use serde_json::map::Entry; - /// - /// let mut map = serde_json::Map::new(); - /// map.insert("serde".to_owned(), json!([1, 2, 3])); - /// - /// match map.entry("serde") { - /// Entry::Occupied(mut occupied) => { - /// occupied.get_mut().as_array_mut().unwrap().push(json!(4)); - /// } - /// Entry::Vacant(_) => unimplemented!(), - /// } - /// - /// assert_eq!(map["serde"].as_array().unwrap().len(), 4); - /// ``` #[inline] pub fn get_mut(&mut self) -> &mut Amf0Value<'b> { self.occupied.get_mut() } /// Converts the entry into a mutable reference to its value. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// use serde_json::map::Entry; - /// - /// let mut map = serde_json::Map::new(); - /// map.insert("serde".to_owned(), json!([1, 2, 3])); - /// - /// match map.entry("serde") { - /// Entry::Occupied(mut occupied) => { - /// occupied.into_mut().as_array_mut().unwrap().push(json!(4)); - /// } - /// Entry::Vacant(_) => unimplemented!(), - /// } - /// - /// assert_eq!(map["serde"].as_array().unwrap().len(), 4); - /// ``` #[inline] pub fn into_mut(self) -> &'a mut Amf0Value<'b> { self.occupied.into_mut() @@ -927,25 +782,6 @@ impl<'a, 'b> OccupiedEntry<'a, 'b> { /// Sets the value of the entry with the `OccupiedEntry`'s key, and returns /// the entry's old value. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// use serde_json::map::Entry; - /// - /// let mut map = serde_json::Map::new(); - /// map.insert("serde".to_owned(), json!(12)); - /// - /// match map.entry("serde") { - /// Entry::Occupied(mut occupied) => { - /// assert_eq!(occupied.insert(json!(13)), 12); - /// assert_eq!(occupied.get(), 13); - /// } - /// Entry::Vacant(_) => unimplemented!(), - /// } - /// ``` #[inline] pub fn insert(&mut self, value: Amf0Value<'b>) -> Amf0Value<'b> { self.occupied.insert(value) @@ -953,29 +789,11 @@ impl<'a, 'b> OccupiedEntry<'a, 'b> { /// Takes the value of the entry out of the map, and returns it. /// - /// If serde_json's "preserve_order" is enabled, `.remove()` is + /// 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. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// use serde_json::map::Entry; - /// - /// let mut map = serde_json::Map::new(); - /// map.insert("serde".to_owned(), json!(12)); - /// - /// match map.entry("serde") { - /// Entry::Occupied(occupied) => { - /// assert_eq!(occupied.remove(), 12); - /// } - /// Entry::Vacant(_) => unimplemented!(), - /// } - /// ``` #[inline] pub fn remove(self) -> Amf0Value<'b> { #[cfg(feature = "preserve_order")] @@ -1014,31 +832,11 @@ impl<'a, 'b> OccupiedEntry<'a, 'b> { /// Removes the entry from the map, returning the stored key and value. /// - /// If serde_json's "preserve_order" is enabled, `.remove_entry()` is + /// 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. - /// - /// # Examples - /// - /// ``` - /// # use serde_json::json; - /// # - /// use serde_json::map::Entry; - /// - /// let mut map = serde_json::Map::new(); - /// map.insert("serde".to_owned(), json!(12)); - /// - /// match map.entry("serde") { - /// Entry::Occupied(occupied) => { - /// let (key, value) = occupied.remove_entry(); - /// assert_eq!(key, "serde"); - /// assert_eq!(value, 12); - /// } - /// Entry::Vacant(_) => unimplemented!(), - /// } - /// ``` #[inline] pub fn remove_entry(self) -> (StringCow<'b>, Amf0Value<'b>) { #[cfg(feature = "preserve_order")] @@ -1088,7 +886,7 @@ impl<'a, 'b> IntoIterator for &'a Amf0Object<'b> { } } -/// An iterator over a serde_json::Map's entries. +/// An iterator over a scuffle_amf0::Amf0Object's entries. pub struct Iter<'a, 'b> { iter: IterImpl<'a, 'b>, } @@ -1114,7 +912,7 @@ impl<'a, 'b> IntoIterator for &'a mut Amf0Object<'b> { } } -/// A mutable iterator over a serde_json::Map's entries. +/// A mutable iterator over a scuffle_amf0::Amf0Object's entries. pub struct IterMut<'a, 'b> { iter: IterMutImpl<'a, 'b>, } @@ -1140,7 +938,7 @@ impl<'a> IntoIterator for Amf0Object<'a> { } } -/// An owning iterator over a serde_json::Map's entries. +/// An owning iterator over a scuffle_amf0::Amf0Object's entries. pub struct IntoIter<'a> { iter: IntoIterImpl<'a>, } @@ -1154,7 +952,7 @@ delegate_iterator!((IntoIter<'a>) => (StringCow<'a>, Amf0Value<'a>)); ////////////////////////////////////////////////////////////////////////////// -/// An iterator over a serde_json::Map's keys. +/// An iterator over a scuffle_amf0::Amf0Object's keys. pub struct Keys<'a, 'b> { iter: KeysImpl<'a, 'b>, } @@ -1168,7 +966,7 @@ delegate_iterator!((Keys<'a, 'b>) => &'a StringCow<'b>); ////////////////////////////////////////////////////////////////////////////// -/// An iterator over a serde_json::Map's values. +/// An iterator over a scuffle_amf0::Amf0Object's values. pub struct Values<'a, 'b> { iter: ValuesImpl<'a, 'b>, } @@ -1182,7 +980,7 @@ delegate_iterator!((Values<'a, 'b>) => &'a Amf0Value<'b>); ////////////////////////////////////////////////////////////////////////////// -/// A mutable iterator over a serde_json::Map's values. +/// A mutable iterator over a scuffle_amf0::Amf0Object's values. pub struct ValuesMut<'a, 'b> { iter: ValuesMutImpl<'a, 'b>, } @@ -1196,7 +994,7 @@ delegate_iterator!((ValuesMut<'a, 'b>) => &'a mut Amf0Value<'b>); ////////////////////////////////////////////////////////////////////////////// -/// An owning iterator over a serde_json::Map's values. +/// An owning iterator over a scuffle_amf0::Amf0Object's values. pub struct IntoValues<'a> { iter: IntoValuesImpl<'a>, } From 80ae2a7fadba910dc8c5ed7bf824b2327524fb04 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Sun, 1 Jun 2025 21:06:32 -0400 Subject: [PATCH 09/24] fix cargo toml formatting via taplo fmt --- crates/amf0/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/amf0/Cargo.toml b/crates/amf0/Cargo.toml index 6f9e875ea..baffa7164 100644 --- a/crates/amf0/Cargo.toml +++ b/crates/amf0/Cargo.toml @@ -17,9 +17,9 @@ 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" -indexmap = { version = "2.4.0", optional = true } 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 From 816eb8dac794f6fd5859b96a944b8d0858c8519f Mon Sep 17 00:00:00 2001 From: philipch07 Date: Sun, 1 Jun 2025 21:06:41 -0400 Subject: [PATCH 10/24] add taplo fmt to just fmt --- Justfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 41f31ae285ea697e6bd2b51e38c20abd0710c211 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Sun, 1 Jun 2025 21:06:57 -0400 Subject: [PATCH 11/24] update setup docs to include taplo for toml formatting --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23da81d40..6b031b534 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ 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 ``` From 03dc12ced348dd1391deaa720f9d461e023e60d6 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Mon, 2 Jun 2025 18:51:24 -0400 Subject: [PATCH 12/24] partially fix docs --- crates/amf0/README.md | 37 ++++++++++++++++++------------------- crates/amf0/src/decoder.rs | 5 ++++- crates/amf0/src/encoder.rs | 2 +- crates/amf0/src/lib.rs | 2 +- crates/amf0/src/object.rs | 13 +++---------- 5 files changed, 27 insertions(+), 32 deletions(-) diff --git a/crates/amf0/README.md b/crates/amf0/README.md index f4b923b2a..e855fb6c2 100644 --- a/crates/amf0/README.md +++ b/crates/amf0/README.md @@ -19,28 +19,23 @@ A pure-rust implementation of AMF0 encoder and decoder. This crate provides serde support for serialization and deserialization of AMF0 data. +## Specification -See the [changelog](./CHANGELOG.md) for a full release history. +| Name | Version | Link | Comments | +| --- | --- | --- | --- | +| Action Message Format -- AMF 0 | - | | Refered to as 'AMF0 spec' in this documentation | -### Feature flags +## Limitations -* **`serde`** — Enables serde support -* **`docs`** — Enables changelog and documentation of feature flags +- Does not support AMF0 references. +- Does not support the AVM+ Type Marker. (see AMF 0 spec, 3.1) -### Specification +## Example -|Name|Version|Link|Comments| -|----|-------|----|--------| -|Action Message Format – AMF 0|-||Refered to as ‘AMF0 spec’ in this documentation| - -### Limitations - -* Does not support AMF0 references. -* Does not support the AVM+ Type Marker. (see AMF 0 spec, 3.1) - -### Example - -````rust +```rust +# fn test() -> Result<(), Box> { +# let bytes = &[0x02, 0, 1, b'a']; +# let mut writer = Vec::new(); // Decode a string value from bytes let value: String = scuffle_amf0::from_slice(bytes)?; @@ -48,9 +43,13 @@ let value: String = scuffle_amf0::from_slice(bytes)?; // Encode a value into a writer scuffle_amf0::to_writer(&mut writer, &value)?; -```` +# assert_eq!(writer, bytes); +# Ok(()) +# } +# test().expect("test failed"); +``` -### License +## License This project is licensed under the MIT or Apache-2.0 license. You can choose between one of them if you use this work. diff --git a/crates/amf0/src/decoder.rs b/crates/amf0/src/decoder.rs index bca3a8d1a..31679e60c 100644 --- a/crates/amf0/src/decoder.rs +++ b/crates/amf0/src/decoder.rs @@ -383,7 +383,10 @@ mod tests { *object.get(&StringCow::from_static("abc")).unwrap(), Amf0Value::String("val".into()) ); - assert_eq!(*object.get("defg").unwrap(), Amf0Value::Boolean(true)); + 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 99077d07e..5dd9a2f5d 100644 --- a/crates/amf0/src/encoder.rs +++ b/crates/amf0/src/encoder.rs @@ -83,7 +83,7 @@ where Ok(()) } - /// Encode an [`Amf0Array`] as an AMF0 StrictArray value. + /// Encode an Amf0Array as an AMF0 StrictArray value. pub fn encode_array<'a, I, B>(&mut self, values: I) -> Result<(), Amf0Error> where B: Borrow>, diff --git a/crates/amf0/src/lib.rs b/crates/amf0/src/lib.rs index dd2955991..4424e736d 100644 --- a/crates/amf0/src/lib.rs +++ b/crates/amf0/src/lib.rs @@ -41,7 +41,7 @@ //! //! `SPDX-License-Identifier: MIT OR Apache-2.0` #![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] #![deny(missing_docs)] #![deny(unsafe_code)] #![deny(unreachable_pub)] diff --git a/crates/amf0/src/object.rs b/crates/amf0/src/object.rs index 0a3d167de..97f11af4b 100644 --- a/crates/amf0/src/object.rs +++ b/crates/amf0/src/object.rs @@ -1,4 +1,4 @@ -//! This code is modified from https://github.com/serde-rs/json/blob/v1.0.140/src/map.rs +//! This code is modified from //! //! A map of scuffle_bytes_util::StringCow to crate::Amf0Value. //! @@ -362,13 +362,6 @@ impl<'a> Amf0Object<'a> { /// 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. - /// - /// Other maps nested within the values of this map are not sorted. If you - /// need the entire data structure to be sorted at all levels, you must also - /// call - /// map.[values_mut]\().for_each([Value::sort_all_objects]). - /// - /// [values_mut]: Map::values_mut #[inline] pub fn sort_keys(&mut self) { #[cfg(feature = "preserve_order")] @@ -640,9 +633,9 @@ where ////////////////////////////////////////////////////////////////////////////// /// 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 [`Map`]. +/// This enum is constructed from the [`entry`] method on [`Amf0Object`]. /// -/// [`entry`]: Map::entry +/// [`entry`]: Amf0Object::entry pub enum Entry<'a, 'b> { /// A vacant Entry. Vacant(VacantEntry<'a, 'b>), From 17ce3a08a56e459ccbd4178a98a4b9efe771cea1 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Mon, 2 Jun 2025 19:39:25 -0400 Subject: [PATCH 13/24] update setup docs for taplo and cargo-sync-rdme --- CONTRIBUTING.md | 6 ++++++ dev-tools/xtask/src/cmd/dev_tools.rs | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b031b534..fd0d38c6e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,6 +46,12 @@ then all of the other crates can be installed with `cargo binstall`. 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 +``` + ### FFmpeg diff --git a/dev-tools/xtask/src/cmd/dev_tools.rs b/dev-tools/xtask/src/cmd/dev_tools.rs index ff0452d75..a6c173980 100644 --- a/dev-tools/xtask/src/cmd/dev_tools.rs +++ b/dev-tools/xtask/src/cmd/dev_tools.rs @@ -48,12 +48,20 @@ 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(()) From 047b4e8da1df93b803483458f30b2340851244e3 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Mon, 2 Jun 2025 19:39:52 -0400 Subject: [PATCH 14/24] properly sync readme and fix docs --- crates/amf0/README.md | 38 ++++++++++++++++++++------------------ crates/amf0/src/lib.rs | 3 +++ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/crates/amf0/README.md b/crates/amf0/README.md index e855fb6c2..022922080 100644 --- a/crates/amf0/README.md +++ b/crates/amf0/README.md @@ -19,23 +19,29 @@ A pure-rust implementation of AMF0 encoder and decoder. This crate provides serde support for serialization and deserialization of AMF0 data. -## Specification -| Name | Version | Link | Comments | -| --- | --- | --- | --- | -| Action Message Format -- AMF 0 | - | | Refered to as 'AMF0 spec' in this documentation | +See the [changelog](./CHANGELOG.md) for a full release history. -## Limitations +### Feature flags -- Does not support AMF0 references. -- Does not support the AVM+ Type Marker. (see AMF 0 spec, 3.1) +* **`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 -## Example +### Specification -```rust -# fn test() -> Result<(), Box> { -# let bytes = &[0x02, 0, 1, b'a']; -# let mut writer = Vec::new(); +|Name|Version|Link|Comments| +|----|-------|----|--------| +|Action Message Format – AMF 0|-||Refered to as ‘AMF0 spec’ in this documentation| + +### Limitations + +* Does not support AMF0 references. +* Does not support the AVM+ Type Marker. (see AMF 0 spec, 3.1) + +### Example + +````rust // Decode a string value from bytes let value: String = scuffle_amf0::from_slice(bytes)?; @@ -43,13 +49,9 @@ let value: String = scuffle_amf0::from_slice(bytes)?; // Encode a value into a writer scuffle_amf0::to_writer(&mut writer, &value)?; -# assert_eq!(writer, bytes); -# Ok(()) -# } -# test().expect("test failed"); -``` +```` -## License +### License This project is licensed under the MIT or Apache-2.0 license. You can choose between one of them if you use this work. diff --git a/crates/amf0/src/lib.rs b/crates/amf0/src/lib.rs index 4424e736d..fa6836daa 100644 --- a/crates/amf0/src/lib.rs +++ b/crates/amf0/src/lib.rs @@ -46,6 +46,9 @@ #![deny(unsafe_code)] #![deny(unreachable_pub)] +#[cfg(not(feature = "preserve_order"))] +extern crate alloc; + #[cfg(feature = "serde")] pub mod de; pub mod decoder; From 0c303941bc2a5624e38d5a4de398af75d0bc4dda Mon Sep 17 00:00:00 2001 From: philipch07 Date: Mon, 2 Jun 2025 19:45:06 -0400 Subject: [PATCH 15/24] fmt --- dev-tools/xtask/src/cmd/dev_tools.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dev-tools/xtask/src/cmd/dev_tools.rs b/dev-tools/xtask/src/cmd/dev_tools.rs index a6c173980..5c886686d 100644 --- a/dev-tools/xtask/src/cmd/dev_tools.rs +++ b/dev-tools/xtask/src/cmd/dev_tools.rs @@ -56,11 +56,13 @@ impl DevTools { } // 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")); + 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"); From 4704fbcaaadcb4202f891fc9ef4f639039f4ef87 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Mon, 2 Jun 2025 20:35:56 -0400 Subject: [PATCH 16/24] address pr comments --- crates/amf0/src/de/mod.rs | 30 ++++++++++++++++++------------ crates/amf0/src/ser.rs | 10 ++++++---- crates/amf0/src/value.rs | 18 +++++++++++++++++- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index e26a9517d..a6f45bcfd 100644 --- a/crates/amf0/src/de/mod.rs +++ b/crates/amf0/src/de/mod.rs @@ -70,7 +70,7 @@ where type Error = Amf0Error; serde::forward_to_deserialize_any! { - char ignored_any + ignored_any } impl_de_number!(deserialize_i8, visit_i8); @@ -102,6 +102,23 @@ where } } + fn deserialize_char(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + if let Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument = self.peek_marker()? { + let value = self.decode_string()?; + let s = value.as_str(); + if s.len() == 1 { + visitor.visit_char(s.chars().next().unwrap()) + } else { + value.into_deserializer().deserialize_string(visitor) + } + } else { + self.deserialize_any(visitor) + } + } + fn deserialize_any(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, @@ -587,17 +604,6 @@ mod tests { let bytes = [Amf0Marker::Null as u8]; from_buf::<()>(Bytes::from_owner(bytes)).unwrap(); - // same as before about the string stuff - // 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); 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 bd22db520..6fc7e470a 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -289,7 +289,7 @@ macro_rules! impl_deserializer { type Error = Amf0Error; serde::forward_to_deserialize_any! { - bool f64 char str string unit + bool f64 str string unit seq map newtype_struct tuple struct enum ignored_any identifier } @@ -312,6 +312,22 @@ macro_rules! impl_deserializer { 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 { + let s = s.as_str(); + if s.len() == 1 { + visitor.visit_char(s.chars().next().unwrap()) + } else { + s.into_deserializer().deserialize_any(visitor) + } + } else { + self.deserialize_any(visitor) + } + } + fn deserialize_option(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, From d7afd5e180c99b42b8a94296f79ac9bad4746adf Mon Sep 17 00:00:00 2001 From: philipch07 Date: Mon, 2 Jun 2025 22:22:17 -0400 Subject: [PATCH 17/24] add tests --- Cargo.lock | 1 + crates/amf0/Cargo.toml | 1 + crates/amf0/src/object.rs | 1486 +++++++++++++++++++++++++++++++++++++ 3 files changed, 1488 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 56c793f64..38719a80c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3165,6 +3165,7 @@ dependencies = [ "bytestring", "document-features", "indexmap", + "insta", "num-derive", "num-traits", "scuffle-bytes-util", diff --git a/crates/amf0/Cargo.toml b/crates/amf0/Cargo.toml index baffa7164..176275de4 100644 --- a/crates/amf0/Cargo.toml +++ b/crates/amf0/Cargo.toml @@ -27,6 +27,7 @@ serde = { optional = true, version = "1" } thiserror = "2.0" [dev-dependencies] +insta = "1.42" serde_derive = "1" [features] diff --git a/crates/amf0/src/object.rs b/crates/amf0/src/object.rs index 97f11af4b..b429d697b 100644 --- a/crates/amf0/src/object.rs +++ b/crates/amf0/src/object.rs @@ -998,3 +998,1489 @@ type IntoValuesImpl<'a> = btree_map::IntoValues, Amf0Value<'a>>; 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.14); + + 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.14)); + + let v: &Amf0Value = &obj["alpha"]; + assert_eq!(v, &Amf0Value::Number(3.14)); + } + + #[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))); + } +} From 37af964c3d55e3ed3b2a1c71c8e5bcfe04c19d21 Mon Sep 17 00:00:00 2001 From: philipch07 <59272129+philipch07@users.noreply.github.com> Date: Mon, 2 Jun 2025 22:59:44 -0400 Subject: [PATCH 18/24] Fix clippy --- crates/amf0/src/object.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/amf0/src/object.rs b/crates/amf0/src/object.rs index b429d697b..4d3f2d250 100644 --- a/crates/amf0/src/object.rs +++ b/crates/amf0/src/object.rs @@ -1093,7 +1093,7 @@ mod tests { fn test_get_key_value_valid() { let mut obj = Amf0Object::new(); let key = StringCow::from("foo"); - let value = Amf0Value::Number(3.14); + let value = Amf0Value::Number(3.21); obj.map.insert(key.clone(), value.clone()); @@ -1774,10 +1774,10 @@ mod tests { #[test] fn test_index_returns_value_for_existing_key() { let mut obj = Amf0Object::new(); - obj.map.insert(StringCow::from("alpha"), Amf0Value::Number(3.14)); + obj.map.insert(StringCow::from("alpha"), Amf0Value::Number(3.21)); let v: &Amf0Value = &obj["alpha"]; - assert_eq!(v, &Amf0Value::Number(3.14)); + assert_eq!(v, &Amf0Value::Number(3.21)); } #[test] From ecb552909b6e6e50524398261f84fc4d0521f7b7 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Thu, 5 Jun 2025 19:44:08 -0400 Subject: [PATCH 19/24] address pr comments --- crates/amf0/src/de/mod.rs | 22 ++++------------------ crates/amf0/src/lib.rs | 2 +- crates/amf0/src/object.rs | 8 -------- crates/amf0/src/value.rs | 7 +------ 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index a6f45bcfd..1479b457e 100644 --- a/crates/amf0/src/de/mod.rs +++ b/crates/amf0/src/de/mod.rs @@ -108,12 +108,7 @@ where { if let Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument = self.peek_marker()? { let value = self.decode_string()?; - let s = value.as_str(); - if s.len() == 1 { - visitor.visit_char(s.chars().next().unwrap()) - } else { - value.into_deserializer().deserialize_string(visitor) - } + value.into_deserializer().deserialize_any(visitor) } else { self.deserialize_any(visitor) } @@ -154,7 +149,7 @@ where { if let Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument = self.peek_marker()? { let value = self.decode_string()?; - value.into_deserializer().deserialize_string(visitor) + value.into_deserializer().deserialize_str(visitor) } else { self.deserialize_any(visitor) } @@ -164,12 +159,7 @@ where where V: serde::de::Visitor<'de>, { - if let Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument = self.peek_marker()? { - let value = self.decode_string()?; - visitor.visit_string(value.to_string()) - } else { - self.deserialize_any(visitor) - } + self.deserialize_string(visitor) } fn deserialize_bytes(self, visitor: V) -> Result @@ -272,11 +262,7 @@ where where V: serde::de::Visitor<'de>, { - if let Amf0Marker::StrictArray = self.peek_marker()? { - self.deserialize_tuple(len, visitor) - } else { - self.deserialize_any(visitor) - } + self.deserialize_tuple(len, visitor) } fn deserialize_map(self, visitor: V) -> Result diff --git a/crates/amf0/src/lib.rs b/crates/amf0/src/lib.rs index fa6836daa..c21fc5b7d 100644 --- a/crates/amf0/src/lib.rs +++ b/crates/amf0/src/lib.rs @@ -41,7 +41,7 @@ //! //! `SPDX-License-Identifier: MIT OR Apache-2.0` #![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))] -#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![deny(missing_docs)] #![deny(unsafe_code)] #![deny(unreachable_pub)] diff --git a/crates/amf0/src/object.rs b/crates/amf0/src/object.rs index 4d3f2d250..df902d74d 100644 --- a/crates/amf0/src/object.rs +++ b/crates/amf0/src/object.rs @@ -198,7 +198,6 @@ impl<'a> Amf0Object<'a> { /// /// [`Vec::swap_remove`]: std::vec::Vec::swap_remove #[cfg(feature = "preserve_order")] - #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] pub fn swap_remove(&mut self, key: &Q) -> Option> where @@ -216,7 +215,6 @@ impl<'a> Amf0Object<'a> { /// /// [`Vec::swap_remove`]: std::vec::Vec::swap_remove #[cfg(feature = "preserve_order")] - #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] pub fn swap_remove_entry(&mut self, key: &Q) -> Option<(StringCow<'a>, Amf0Value<'a>)> where @@ -234,7 +232,6 @@ impl<'a> Amf0Object<'a> { /// /// [`Vec::remove`]: std::vec::Vec::remove #[cfg(feature = "preserve_order")] - #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] pub fn shift_remove(&mut self, key: &Q) -> Option> where @@ -252,7 +249,6 @@ impl<'a> Amf0Object<'a> { /// /// [`Vec::remove`]: std::vec::Vec::remove #[cfg(feature = "preserve_order")] - #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] pub fn shift_remove_entry(&mut self, key: &Q) -> Option<(StringCow<'a>, Amf0Value<'a>)> where @@ -803,7 +799,6 @@ impl<'a, 'b> OccupiedEntry<'a, 'b> { /// /// [`Vec::swap_remove`]: std::vec::Vec::swap_remove #[cfg(feature = "preserve_order")] - #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] pub fn swap_remove(self) -> Amf0Value<'b> { self.occupied.swap_remove() @@ -817,7 +812,6 @@ impl<'a, 'b> OccupiedEntry<'a, 'b> { /// /// [`Vec::remove`]: std::vec::Vec::remove #[cfg(feature = "preserve_order")] - #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] pub fn shift_remove(self) -> Amf0Value<'b> { self.occupied.shift_remove() @@ -846,7 +840,6 @@ impl<'a, 'b> OccupiedEntry<'a, 'b> { /// /// [`Vec::swap_remove`]: std::vec::Vec::swap_remove #[cfg(feature = "preserve_order")] - #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] pub fn swap_remove_entry(self) -> (StringCow<'b>, Amf0Value<'b>) { self.occupied.swap_remove_entry() @@ -860,7 +853,6 @@ impl<'a, 'b> OccupiedEntry<'a, 'b> { /// /// [`Vec::remove`]: std::vec::Vec::remove #[cfg(feature = "preserve_order")] - #[cfg_attr(docsrs, doc(cfg(feature = "preserve_order")))] #[inline] pub fn shift_remove_entry(self) -> (StringCow<'b>, Amf0Value<'b>) { self.occupied.shift_remove_entry() diff --git a/crates/amf0/src/value.rs b/crates/amf0/src/value.rs index 6fc7e470a..4e75bb8bf 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -317,12 +317,7 @@ macro_rules! impl_deserializer { V: serde::de::Visitor<'de>, { if let Amf0Value::String(s) = self { - let s = s.as_str(); - if s.len() == 1 { - visitor.visit_char(s.chars().next().unwrap()) - } else { - s.into_deserializer().deserialize_any(visitor) - } + s.into_deserializer().deserialize_any(visitor) } else { self.deserialize_any(visitor) } From 2993d4fdc880e4b981ca601ba736d5a9c4e4cf11 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Thu, 5 Jun 2025 20:51:54 -0400 Subject: [PATCH 20/24] address pr comment --- crates/amf0/src/de/mod.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index 1479b457e..b08bc551b 100644 --- a/crates/amf0/src/de/mod.rs +++ b/crates/amf0/src/de/mod.rs @@ -149,7 +149,7 @@ where { if let Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument = self.peek_marker()? { let value = self.decode_string()?; - value.into_deserializer().deserialize_str(visitor) + value.into_deserializer().deserialize_any(visitor) } else { self.deserialize_any(visitor) } @@ -246,16 +246,7 @@ where where V: serde::de::Visitor<'de>, { - if let Amf0Marker::StrictArray = self.peek_marker()? { - let size = self.decode_strict_array_header()? as usize; - - visitor.visit_seq(StrictArray { - de: self, - remaining: size, - }) - } else { - self.deserialize_any(visitor) - } + self.deserialize_seq(visitor) } fn deserialize_tuple_struct(self, _name: &'static str, len: usize, visitor: V) -> Result From 55f4f4ef7a86266c362086ebf3286ea4b3d79813 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Thu, 5 Jun 2025 21:20:42 -0400 Subject: [PATCH 21/24] address pr comments (forward to any) --- crates/amf0/src/de/mod.rs | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index b08bc551b..4b8e892eb 100644 --- a/crates/amf0/src/de/mod.rs +++ b/crates/amf0/src/de/mod.rs @@ -70,7 +70,7 @@ where type Error = Amf0Error; serde::forward_to_deserialize_any! { - ignored_any + tuple tuple_struct ignored_any identifier } impl_de_number!(deserialize_i8, visit_i8); @@ -188,7 +188,7 @@ where where V: serde::de::Visitor<'de>, { - if matches!(self.peek_marker()?, Amf0Marker::Null | Amf0Marker::Undefined) { + if let Amf0Marker::Null | Amf0Marker::Undefined = self.peek_marker()? { self.decode_null()?; visitor.visit_none() } else { @@ -242,20 +242,6 @@ where } } - fn deserialize_tuple(self, _len: usize, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_seq(visitor) - } - - 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>, @@ -298,18 +284,6 @@ where { visitor.visit_enum(Enum { de: self }) } - - fn deserialize_identifier(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - if let Amf0Marker::String | Amf0Marker::LongString = self.peek_marker()? { - let s = self.decode_string()?; - s.into_deserializer().deserialize_identifier(visitor) - } else { - self.deserialize_any(visitor) - } - } } struct StrictArray<'a, R> { From 8cc6878bafafc99c209d3eee696f97171e11a5c7 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Mon, 9 Jun 2025 12:18:45 +0000 Subject: [PATCH 22/24] small cleanup --- crates/amf0/src/de/mod.rs | 175 ++++++++++---------------------------- crates/amf0/src/value.rs | 36 ++++---- 2 files changed, 63 insertions(+), 148 deletions(-) diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index 4b8e892eb..c0465e387 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; @@ -71,6 +71,8 @@ where serde::forward_to_deserialize_any! { tuple tuple_struct ignored_any identifier + str string seq map unit unit_struct bool + f64 struct } impl_de_number!(deserialize_i8, visit_i8); @@ -91,59 +93,36 @@ where impl_de_number!(deserialize_f32, visit_f32); - fn deserialize_f64(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - if let Amf0Marker::Number | Amf0Marker::Date = self.peek_marker()? { - visitor.visit_f64(self.decode_number()?) - } else { - self.deserialize_any(visitor) - } - } - - fn deserialize_char(self, visitor: V) -> Result + fn deserialize_byte_buf(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument = self.peek_marker()? { - let value = self.decode_string()?; - value.into_deserializer().deserialize_any(visitor) + 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_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)), - } - } - - fn deserialize_bool(self, visitor: V) -> Result + fn deserialize_bytes(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - if let Amf0Marker::Boolean = self.peek_marker()? { - let value = self.decode_boolean()?; - visitor.visit_bool(value) - } else { - self.deserialize_any(visitor) - } + 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>, { @@ -155,32 +134,38 @@ where } } - fn deserialize_str(self, visitor: V) -> Result + fn deserialize_any(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, { - self.deserialize_string(visitor) - } + let marker = self.peek_marker()?; - fn deserialize_bytes(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - if let Amf0Marker::StrictArray = self.peek_marker()? { - self.deserialize_seq(visitor) - } else { - self.deserialize_any(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 => 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>, - { - if let Amf0Marker::StrictArray = self.peek_marker()? { - self.deserialize_seq(visitor) - } else { - self.deserialize_any(visitor) + visitor.visit_seq(StrictArray { + de: self, + remaining: size, + }) + } + _ => Err(Amf0Error::UnsupportedMarker(marker)), } } @@ -196,25 +181,6 @@ where } } - fn deserialize_unit(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - if let Amf0Marker::Null | Amf0Marker::Undefined = self.peek_marker()? { - self.decode_null()?; - visitor.visit_unit() - } else { - self.deserialize_any(visitor) - } - } - - 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>, @@ -226,53 +192,6 @@ where } } - fn deserialize_seq(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - if let Amf0Marker::StrictArray = self.peek_marker()? { - let size = self.decode_strict_array_header()? as usize; - - visitor.visit_seq(StrictArray { - de: self, - remaining: size, - }) - } else { - self.deserialize_any(visitor) - } - } - - fn deserialize_map(self, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - if let Amf0Marker::Object | Amf0Marker::TypedObject | Amf0Marker::EcmaArray = self.peek_marker()? { - 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, - }), - } - } else { - self.deserialize_any(visitor) - } - } - - 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, diff --git a/crates/amf0/src/value.rs b/crates/amf0/src/value.rs index 4e75bb8bf..6499e1730 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -292,6 +292,7 @@ macro_rules! impl_deserializer { 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); @@ -337,7 +338,21 @@ macro_rules! impl_deserializer { where V: serde::de::Visitor<'de>, { - self.deserialize_seq(visitor) + 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 @@ -347,25 +362,6 @@ macro_rules! impl_deserializer { self.deserialize_seq(visitor) } - fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result - where - V: serde::de::Visitor<'de>, - { - self.deserialize_unit(visitor) - } - - 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_any(self, visitor: V) -> Result where V: serde::de::Visitor<'de>, From 8c429d9c9b0db5f7604458ceda4d735920c89239 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Mon, 9 Jun 2025 13:18:55 +0000 Subject: [PATCH 23/24] fix decode null --- crates/amf0/src/de/mod.rs | 5 ++++- crates/amf0/src/encoder.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/amf0/src/de/mod.rs b/crates/amf0/src/de/mod.rs index c0465e387..87c5b696b 100644 --- a/crates/amf0/src/de/mod.rs +++ b/crates/amf0/src/de/mod.rs @@ -146,7 +146,10 @@ where Amf0Marker::String | Amf0Marker::LongString | Amf0Marker::XmlDocument => { self.decode_string()?.into_deserializer().deserialize_any(visitor) } - Amf0Marker::Null | Amf0Marker::Undefined => visitor.visit_unit(), + Amf0Marker::Null | Amf0Marker::Undefined => { + self.decode_null()?; + visitor.visit_unit() + }, Amf0Marker::Object | Amf0Marker::TypedObject | Amf0Marker::EcmaArray => { let header = self.decode_object_header()?; match header { diff --git a/crates/amf0/src/encoder.rs b/crates/amf0/src/encoder.rs index 5dd9a2f5d..26031b562 100644 --- a/crates/amf0/src/encoder.rs +++ b/crates/amf0/src/encoder.rs @@ -112,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(()) } From 51e201e87ed00632fe65d94f23c4818b6f533fb0 Mon Sep 17 00:00:00 2001 From: philipch07 Date: Sun, 22 Jun 2025 12:15:08 -0400 Subject: [PATCH 24/24] enum tests fail --- crates/amf0/src/value.rs | 270 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) diff --git a/crates/amf0/src/value.rs b/crates/amf0/src/value.rs index 6499e1730..15609544e 100644 --- a/crates/amf0/src/value.rs +++ b/crates/amf0/src/value.rs @@ -429,9 +429,12 @@ where #[cfg(test)] #[cfg_attr(all(test, coverage_nightly), coverage(off))] mod tests { + use std::collections::HashMap; + use scuffle_bytes_util::StringCow; #[cfg(feature = "serde")] use serde::Deserialize; + use serde::Serialize; use super::Amf0Value; use crate::{Amf0Decoder, Amf0Encoder, Amf0Error, Amf0Marker, Amf0Object}; @@ -806,4 +809,271 @@ mod tests { } ); } + + #[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); + } }