From de749b1a5230d0b4ecce975374d39d1a74ff2a92 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Fri, 18 Dec 2020 12:25:58 +0100 Subject: [PATCH] schema: ExtractValueDeserializer A deserializer which takes an `&mut Value` and an object schema reference and deserializes by extracting (removing) the values from the references serde Value. Signed-off-by: Wolfgang Bumiller --- proxmox/src/api/de.rs | 270 +++++++++++++++++++++++++++++++++++++++++ proxmox/src/api/mod.rs | 2 + 2 files changed, 272 insertions(+) create mode 100644 proxmox/src/api/de.rs diff --git a/proxmox/src/api/de.rs b/proxmox/src/api/de.rs new file mode 100644 index 00000000..b48fd856 --- /dev/null +++ b/proxmox/src/api/de.rs @@ -0,0 +1,270 @@ +//! Partial object deserialization by extracting object portions from a Value using an api schema. + +use std::fmt; + +use serde::de::{self, IntoDeserializer, Visitor}; +use serde_json::Value; + +use crate::api::schema::{ObjectSchema, ObjectSchemaType, Schema}; + +pub struct Error { + inner: anyhow::Error, +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.inner, f) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.inner, f) + } +} + +impl std::error::Error for Error {} + +impl serde::de::Error for Error { + fn custom(msg: T) -> Self { + Self { + inner: anyhow::format_err!("{}", msg), + } + } +} + +impl From for Error { + fn from(inner: serde_json::Error) -> Self { + Error { + inner: inner.into(), + } + } +} + +pub struct ExtractValueDeserializer<'o> { + object: &'o mut serde_json::Map, + schema: &'static ObjectSchema, +} + +impl<'o> ExtractValueDeserializer<'o> { + pub fn try_new( + object: &'o mut serde_json::Map, + schema: &'static Schema, + ) -> Option { + match schema { + Schema::Object(schema) => Some(Self { object, schema }), + _ => None, + } + } +} + +macro_rules! deserialize_non_object { + ($name:ident) => { + fn $name(self, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(de::Error::custom( + "deserializing partial object into type which is not an object", + )) + } + }; + ($name:ident ( $($args:tt)* )) => { + fn $name(self, $($args)*, _visitor: V) -> Result + where + V: Visitor<'de>, + { + Err(de::Error::custom( + "deserializing partial object into type which is not an object", + )) + } + }; +} + +impl<'de> de::Deserializer<'de> for ExtractValueDeserializer<'de> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_map(MapAccess::<'de>::new( + self.object, + self.schema.properties().map(|(name, _, _)| *name), + )) + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_map(MapAccess::<'de>::new( + self.object, + self.schema.properties().map(|(name, _, _)| *name), + )) + } + + deserialize_non_object!(deserialize_i8); + deserialize_non_object!(deserialize_i16); + deserialize_non_object!(deserialize_i32); + deserialize_non_object!(deserialize_i64); + deserialize_non_object!(deserialize_u8); + deserialize_non_object!(deserialize_u16); + deserialize_non_object!(deserialize_u32); + deserialize_non_object!(deserialize_u64); + deserialize_non_object!(deserialize_f32); + deserialize_non_object!(deserialize_f64); + deserialize_non_object!(deserialize_char); + deserialize_non_object!(deserialize_bool); + deserialize_non_object!(deserialize_str); + deserialize_non_object!(deserialize_string); + deserialize_non_object!(deserialize_bytes); + deserialize_non_object!(deserialize_byte_buf); + deserialize_non_object!(deserialize_option); + deserialize_non_object!(deserialize_seq); + deserialize_non_object!(deserialize_unit); + deserialize_non_object!(deserialize_identifier); + deserialize_non_object!(deserialize_unit_struct(_: &'static str)); + deserialize_non_object!(deserialize_newtype_struct(_: &'static str)); + deserialize_non_object!(deserialize_tuple(_: usize)); + deserialize_non_object!(deserialize_tuple_struct(_: &'static str, _: usize)); + deserialize_non_object!(deserialize_enum( + _: &'static str, + _: &'static [&'static str] + )); + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } +} + +struct MapAccess<'o, I> { + object: &'o mut serde_json::Map, + iter: I, + value: Option, +} + +impl<'o, I> MapAccess<'o, I> +where + I: Iterator, +{ + fn new(object: &'o mut serde_json::Map, iter: I) -> Self { + Self { + object, + iter, + value: None, + } + } +} + +impl<'de, I> de::MapAccess<'de> for MapAccess<'de, I> +where + I: Iterator, +{ + type Error = Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Error> + where + K: de::DeserializeSeed<'de>, + { + loop { + return match self.iter.next() { + Some(key) => match self.object.remove(key) { + Some(value) => { + self.value = Some(value); + seed.deserialize(key.into_deserializer()).map(Some) + } + None => continue, + }, + None => Ok(None), + }; + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + match self.value.take() { + Some(value) => seed.deserialize(value).map_err(Error::from), + None => Err(de::Error::custom("value is missing")), + } + } +} + +#[test] +fn test_extraction() { + use serde::Deserialize; + + use crate::api::schema::StringSchema; + + #[derive(Deserialize)] + struct Foo { + foo1: String, + foo2: String, + } + + const SIMPLE_STRING: Schema = StringSchema::new("simple").schema(); + const FOO_SCHEMA: Schema = ObjectSchema::new( + "A Foo", + &[ + ("foo1", false, &SIMPLE_STRING), + ("foo2", false, &SIMPLE_STRING), + ], + ) + .schema(); + + #[derive(Deserialize)] + struct Bar { + bar1: String, + bar2: String, + } + + const BAR_SCHEMA: Schema = ObjectSchema::new( + "A Bar", + &[ + ("bar1", false, &SIMPLE_STRING), + ("bar2", false, &SIMPLE_STRING), + ], + ) + .schema(); + + let mut data = serde_json::json!({ + "foo1": "hey1", + "foo2": "hey2", + "bar1": "there1", + "bar2": "there2", + }); + + let data = data.as_object_mut().unwrap(); + + let foo: Foo = + Foo::deserialize(ExtractValueDeserializer::try_new(data, &FOO_SCHEMA).unwrap()).unwrap(); + + assert!(data.remove("foo1").is_none()); + assert!(data.remove("foo2").is_none()); + assert_eq!(foo.foo1, "hey1"); + assert_eq!(foo.foo2, "hey2"); + + let bar = + Bar::deserialize(ExtractValueDeserializer::try_new(data, &BAR_SCHEMA).unwrap()).unwrap(); + + assert!(data.is_empty()); + assert_eq!(bar.bar1, "there1"); + assert_eq!(bar.bar2, "there2"); +} diff --git a/proxmox/src/api/mod.rs b/proxmox/src/api/mod.rs index b0b83339..8c6f597f 100644 --- a/proxmox/src/api/mod.rs +++ b/proxmox/src/api/mod.rs @@ -48,3 +48,5 @@ pub use router::{ #[cfg(feature = "cli")] pub mod cli; + +pub mod de;