From b2d0e93f8ce9705f627b8d1c68088bb1b6e92043 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 30 Oct 2024 10:35:48 +0000 Subject: [PATCH 1/5] Add serde support to agb_hashmap::HashMap --- agb-hashmap/Cargo.toml | 2 ++ agb-hashmap/src/lib.rs | 3 ++ agb-hashmap/src/serde.rs | 76 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 agb-hashmap/src/serde.rs diff --git a/agb-hashmap/Cargo.toml b/agb-hashmap/Cargo.toml index f4f91ac7..fba3c796 100644 --- a/agb-hashmap/Cargo.toml +++ b/agb-hashmap/Cargo.toml @@ -9,9 +9,11 @@ exclude = ["/benches"] [features] allocator_api = [] +serde = ["dep:serde"] [dependencies] rustc-hash = { version = "1", default-features = false } +serde = { version = "1", default-features = false, optional = true } [dev-dependencies] rand = { version = "0.8", default-features = false, features = ["small_rng"] } diff --git a/agb-hashmap/src/lib.rs b/agb-hashmap/src/lib.rs index 940df370..30c13fd0 100644 --- a/agb-hashmap/src/lib.rs +++ b/agb-hashmap/src/lib.rs @@ -44,6 +44,9 @@ mod allocate { pub(crate) use core::alloc::Allocator; } +#[cfg(feature = "serde")] +mod serde; + use core::{ borrow::Borrow, fmt::Debug, diff --git a/agb-hashmap/src/serde.rs b/agb-hashmap/src/serde.rs new file mode 100644 index 00000000..e014120f --- /dev/null +++ b/agb-hashmap/src/serde.rs @@ -0,0 +1,76 @@ +use core::{hash::Hash, marker::PhantomData}; +use serde::{ + de::{MapAccess, Visitor}, + ser::SerializeMap, + Deserialize, Serialize, +}; + +use crate::{ClonableAllocator, HashMap}; + +impl Serialize + for HashMap +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(Some(self.len()))?; + + for (key, value) in self { + map.serialize_entry(key, value)?; + } + + map.end() + } +} + +impl<'de, K, V> Deserialize<'de> for HashMap +where + K: Deserialize<'de> + Hash + Eq, + V: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(HashMapVisitor::new()) + } +} + +#[derive(Default)] +struct HashMapVisitor { + _marker: PhantomData HashMap>, +} + +impl HashMapVisitor { + fn new() -> Self { + Self { + _marker: PhantomData, + } + } +} + +impl<'de, K, V> Visitor<'de> for HashMapVisitor +where + K: Deserialize<'de> + Hash + Eq, + V: Deserialize<'de>, +{ + type Value = HashMap; + + fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result { + formatter.write_str("an agb::HashMap") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(8)); + + while let Some((key, value)) = access.next_entry()? { + map.insert(key, value); + } + + Ok(map) + } +} From 69219e3d734b4b2388c35c8dad039589f01135e1 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 30 Oct 2024 10:36:36 +0000 Subject: [PATCH 2/5] Pull the hashmap Serialize/Deserialize impl into a module --- agb-hashmap/src/serde.rs | 122 ++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/agb-hashmap/src/serde.rs b/agb-hashmap/src/serde.rs index e014120f..7b081de4 100644 --- a/agb-hashmap/src/serde.rs +++ b/agb-hashmap/src/serde.rs @@ -7,70 +7,74 @@ use serde::{ use crate::{ClonableAllocator, HashMap}; -impl Serialize - for HashMap -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut map = serializer.serialize_map(Some(self.len()))?; +mod hashmap { + use super::*; - for (key, value) in self { - map.serialize_entry(key, value)?; + impl Serialize + for HashMap + { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut map = serializer.serialize_map(Some(self.len()))?; + + for (key, value) in self { + map.serialize_entry(key, value)?; + } + + map.end() + } + } + + impl<'de, K, V> Deserialize<'de> for HashMap + where + K: Deserialize<'de> + Hash + Eq, + V: Deserialize<'de>, + { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(HashMapVisitor::new()) + } + } + + #[derive(Default)] + struct HashMapVisitor { + _marker: PhantomData HashMap>, + } + + impl HashMapVisitor { + fn new() -> Self { + Self { + _marker: PhantomData, + } + } + } + + impl<'de, K, V> Visitor<'de> for HashMapVisitor + where + K: Deserialize<'de> + Hash + Eq, + V: Deserialize<'de>, + { + type Value = HashMap; + + fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result { + formatter.write_str("an agb::HashMap") } - map.end() - } -} + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(8)); -impl<'de, K, V> Deserialize<'de> for HashMap -where - K: Deserialize<'de> + Hash + Eq, - V: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_map(HashMapVisitor::new()) - } -} + while let Some((key, value)) = access.next_entry()? { + map.insert(key, value); + } -#[derive(Default)] -struct HashMapVisitor { - _marker: PhantomData HashMap>, -} - -impl HashMapVisitor { - fn new() -> Self { - Self { - _marker: PhantomData, + Ok(map) } } } - -impl<'de, K, V> Visitor<'de> for HashMapVisitor -where - K: Deserialize<'de> + Hash + Eq, - V: Deserialize<'de>, -{ - type Value = HashMap; - - fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result { - formatter.write_str("an agb::HashMap") - } - - fn visit_map(self, mut access: M) -> Result - where - M: MapAccess<'de>, - { - let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(8)); - - while let Some((key, value)) = access.next_entry()? { - map.insert(key, value); - } - - Ok(map) - } -} From b71c544b9a1ec898065e8703c980f5d059131461 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 30 Oct 2024 10:56:19 +0000 Subject: [PATCH 3/5] implement serde for HashSet and tests --- agb-hashmap/Cargo.toml | 1 + agb-hashmap/src/serde.rs | 124 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 3 deletions(-) diff --git a/agb-hashmap/Cargo.toml b/agb-hashmap/Cargo.toml index fba3c796..71234a47 100644 --- a/agb-hashmap/Cargo.toml +++ b/agb-hashmap/Cargo.toml @@ -19,3 +19,4 @@ serde = { version = "1", default-features = false, optional = true } rand = { version = "0.8", default-features = false, features = ["small_rng"] } lazy_static = "1.4" quickcheck = "1" +serde_json = { version = "1", default-features = false, features = ["alloc"] } diff --git a/agb-hashmap/src/serde.rs b/agb-hashmap/src/serde.rs index 7b081de4..5d7b201a 100644 --- a/agb-hashmap/src/serde.rs +++ b/agb-hashmap/src/serde.rs @@ -1,11 +1,11 @@ use core::{hash::Hash, marker::PhantomData}; use serde::{ - de::{MapAccess, Visitor}, + de::{MapAccess, SeqAccess, Visitor}, ser::SerializeMap, Deserialize, Serialize, }; -use crate::{ClonableAllocator, HashMap}; +use crate::{ClonableAllocator, HashMap, HashSet}; mod hashmap { use super::*; @@ -40,7 +40,6 @@ mod hashmap { } } - #[derive(Default)] struct HashMapVisitor { _marker: PhantomData HashMap>, } @@ -78,3 +77,122 @@ mod hashmap { } } } + +mod hashset { + + use super::*; + + impl Serialize for HashSet { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.collect_seq(self) + } + } + + struct HashSetVisitor { + _marker: PhantomData HashSet>, + } + + impl HashSetVisitor { + fn new() -> Self { + Self { + _marker: PhantomData, + } + } + } + + impl<'de, K> Visitor<'de> for HashSetVisitor + where + K: Deserialize<'de> + Hash + Eq, + { + type Value = HashSet; + + fn expecting(&self, formatter: &mut alloc::fmt::Formatter) -> alloc::fmt::Result { + formatter.write_str("an agb::HashSet") + } + + fn visit_seq(self, mut access: A) -> Result + where + A: SeqAccess<'de>, + { + let mut set = HashSet::with_capacity(access.size_hint().unwrap_or(8)); + + while let Some(value) = access.next_element()? { + set.insert(value); + } + + Ok(set) + } + } + + impl<'de, K> Deserialize<'de> for HashSet + where + K: Deserialize<'de> + Hash + Eq, + { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_seq(HashSetVisitor::new()) + } + } +} + +#[cfg(test)] +mod test { + use alloc::{ + string::{String, ToString}, + vec::Vec, + }; + + use crate::{HashMap, HashSet}; + + #[test] + fn deserialize_map() { + let json = r#" + { + "three": 3, + "seven": 7 + } + "#; + + let map = serde_json::from_str::>(json).unwrap(); + + assert_eq!( + map, + HashMap::from_iter([("three".to_string(), 3), ("seven".to_string(), 7)]) + ); + } + + #[test] + fn serialize_map() { + let map = HashMap::from_iter([("three".to_string(), 3), ("seven".to_string(), 7)]); + + let json = serde_json::to_string(&map).unwrap(); + + let possibilities = &[r#"{"three":3,"seven":7}"#, r#"{"seven":7,"three":3}"#]; + + assert!(possibilities.contains(&json.as_str())); + } + + #[test] + fn deserialize_hashset() { + let json = "[1, 2, 5, 8, 9, 3, 4]"; + let set = serde_json::from_str::>(json).unwrap(); + + assert_eq!(set, HashSet::from_iter([1, 2, 3, 4, 5, 8, 9])); + } + + #[test] + fn serialize_hashset() { + let set = HashSet::from_iter([1, 2, 3, 5, 8, 9, 10]); + let serialized = serde_json::to_string(&set).unwrap(); + + let mut deserialized = serde_json::from_str::>(&serialized).unwrap(); + deserialized.sort(); + + assert_eq!(deserialized, &[1, 2, 3, 5, 8, 9, 10]); + } +} From e1e0af7418b76c81e51481eb1115c765a09fc1dc Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 30 Oct 2024 11:08:49 +0000 Subject: [PATCH 4/5] Ensure the hashmap tests get run with serde --- justfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/justfile b/justfile index a3164741..5f885e09 100644 --- a/justfile +++ b/justfile @@ -19,6 +19,10 @@ clippy: just _all-crates _clippy test: + # test the workspace + cargo test + # also need to explicitly hit the serde tests in agb-hashmap + (cd agb-hashmap && cargo test --features=serde serde) just _test-debug agb just _test-debug tracker/agb-tracker just _test-multiboot From 246baabcf8d72d86f4f46e41b68ee31dbef0b4d6 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 30 Oct 2024 11:18:07 +0000 Subject: [PATCH 5/5] Changelog entry for serde support in agb-hashmap --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69fe7400..4fa56221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Optional serde support for agb-hashmap via the `serde` feature flag + ### Fixed - Fixed build error due to breaking change in `xmrs`.