diff --git a/proxmox-schema/src/property_string.rs b/proxmox-schema/src/property_string.rs index bda6ed87..cf18a18a 100644 --- a/proxmox-schema/src/property_string.rs +++ b/proxmox-schema/src/property_string.rs @@ -221,7 +221,7 @@ impl PropertyString { } } -impl PropertyString { +impl PropertyString { pub fn to_property_string(&self) -> Result { print(&self.0) } @@ -322,7 +322,7 @@ where impl Serialize for PropertyString where - T: Serialize, + T: Serialize + ApiType, { fn serialize(&self, serializer: S) -> Result where @@ -335,8 +335,11 @@ where } /// Serialize a value as a property string. -pub fn print(value: &T) -> Result { - value.serialize(crate::ser::PropertyStringSerializer::new(String::new())) +pub fn print(value: &T) -> Result { + value.serialize(crate::ser::PropertyStringSerializer::new( + String::new(), + &T::API_SCHEMA, + )) } /// Deserialize a value from a property string. diff --git a/proxmox-schema/src/ser/mod.rs b/proxmox-schema/src/ser/mod.rs index ab81c4bd..bcf473dc 100644 --- a/proxmox-schema/src/ser/mod.rs +++ b/proxmox-schema/src/ser/mod.rs @@ -6,6 +6,7 @@ use std::mem; use serde::ser::{self, Serialize, Serializer}; use crate::de::Error; +use crate::schema::{ArraySchema, ObjectSchemaType, Schema}; impl serde::ser::Error for Error { fn custom(msg: T) -> Self { @@ -15,11 +16,26 @@ impl serde::ser::Error for Error { pub struct PropertyStringSerializer { inner: T, + schema: &'static Schema, } -impl PropertyStringSerializer { - pub fn new(inner: T) -> Self { - Self { inner } +impl PropertyStringSerializer { + pub fn new(inner: T, schema: &'static Schema) -> Self { + Self { inner, schema } + } + + fn do_seq(self) -> Result, Error> { + let schema = self.schema.array().ok_or_else(|| { + Error::msg("property string serializer used for array with non-array schema") + })?; + Ok(SerializeSeq::new(self.inner, Some(schema))) + } + + fn do_object(self) -> Result, Error> { + let schema = self.schema.any_object().ok_or_else(|| { + Error::msg("property string serializer used for object with non-object schema") + })?; + Ok(SerializeStruct::new(self.inner, Some(schema))) } } @@ -27,21 +43,21 @@ macro_rules! not_an_object { () => {}; ($name:ident($ty:ty) $($rest:tt)*) => { fn $name(self, _v: $ty) -> Result { - Err(Error::msg("property string serializer used with a non-object type")) + Err(Error::msg("property string serializer used with a non-object/array type")) } not_an_object! { $($rest)* } }; ($name:ident($($args:tt)*) $($rest:tt)*) => { fn $name(self, $($args)*) -> Result { - Err(Error::msg("property string serializer used with a non-object type")) + Err(Error::msg("property string serializer used with a non-object/array type")) } not_an_object! { $($rest)* } }; ($name:ident<($($gen:tt)*)>($($args:tt)*) $($rest:tt)*) => { fn $name<$($gen)*>(self, $($args)*) -> Result { - Err(Error::msg("property string serializer used with a non-object type")) + Err(Error::msg("property string serializer used with a non-object/array type")) } not_an_object! { $($rest)* } @@ -124,11 +140,11 @@ impl Serializer for PropertyStringSerializer { } fn serialize_seq(self, _len: Option) -> Result { - Ok(SerializeSeq::new(self.inner)) + self.do_seq() } fn serialize_tuple(self, _len: usize) -> Result { - Ok(SerializeSeq::new(self.inner)) + self.do_seq() } fn serialize_tuple_struct( @@ -136,7 +152,7 @@ impl Serializer for PropertyStringSerializer { _name: &'static str, _len: usize, ) -> Result { - Ok(SerializeSeq::new(self.inner)) + self.do_seq() } fn serialize_tuple_variant( @@ -146,7 +162,7 @@ impl Serializer for PropertyStringSerializer { _variant: &'static str, _len: usize, ) -> Result { - Ok(SerializeSeq::new(self.inner)) + self.do_seq() } fn serialize_struct( @@ -154,7 +170,7 @@ impl Serializer for PropertyStringSerializer { _name: &'static str, _len: usize, ) -> Result, Error> { - Ok(SerializeStruct::new(self.inner)) + self.do_object() } fn serialize_struct_variant( @@ -164,44 +180,72 @@ impl Serializer for PropertyStringSerializer { _variant: &'static str, _len: usize, ) -> Result, Error> { - Ok(SerializeStruct::new(self.inner)) + self.do_object() } fn serialize_map(self, _len: Option) -> Result { - Ok(SerializeStruct::new(self.inner)) + self.do_object() } } pub struct SerializeStruct { inner: Option, comma: bool, + schema: Option<&'static dyn ObjectSchemaType>, + value_schema: Option<&'static Schema>, } impl SerializeStruct { - fn new(inner: T) -> Self { + fn new(inner: T, schema: Option<&'static dyn ObjectSchemaType>) -> Self { Self { inner: Some(inner), comma: false, + schema, + value_schema: None, } } - fn field(&mut self, key: &'static str, value: &V) -> Result<(), Error> - where - V: Serialize + ?Sized, - { - let mut inner = self.inner.take().unwrap(); - - if mem::replace(&mut self.comma, true) { - inner.write_char(',')?; - } - write!(inner, "{key}=")?; - self.inner = Some(value.serialize(ElementSerializer::new(inner))?); - Ok(()) - } - fn finish(mut self) -> Result { Ok(self.inner.take().unwrap()) } + + fn do_key(&mut self, key: &K) -> Result<(), Error> + where + K: Serialize + ?Sized, + { + let key = key.serialize(ElementSerializer::new(String::new(), None))?; + + let inner = self.inner.as_mut().unwrap(); + if mem::replace(&mut self.comma, true) { + inner.write_char(',')?; + } + + if let Some(schema) = self.schema { + self.value_schema = schema.lookup(&key).map(|(_optional, schema)| schema); + if self.value_schema.is_none() && !schema.additional_properties() { + return Err(Error::msg(format!( + "key {key:?} is not part of the schema and it does not allow additional properties" + ))); + } + if schema.default_key() == Some(&key[..]) { + return Ok(()); + } + } + + inner.write_str(&key)?; + inner.write_char('=')?; + Ok(()) + } + + fn do_value(&mut self, value: &V) -> Result<(), Error> + where + V: Serialize + ?Sized, + { + let mut inner = self.inner.take().unwrap(); + inner = value.serialize(ElementSerializer::new(inner, self.value_schema))?; + self.inner = Some(inner); + Ok(()) + } } same_impl! { @@ -215,7 +259,8 @@ same_impl! { where V: Serialize + ?Sized, { - self.field(key, value) + self.do_key(key)?; + self.do_value(value) } fn end(self) -> Result { @@ -232,24 +277,14 @@ impl ser::SerializeMap for SerializeStruct { where K: Serialize + ?Sized, { - let mut inner = self.inner.take().unwrap(); - if mem::replace(&mut self.comma, true) { - inner.write_char(',')?; - } - inner = key.serialize(ElementSerializer::new(inner))?; - inner.write_char('=')?; - self.inner = Some(inner); - Ok(()) + self.do_key(key) } fn serialize_value(&mut self, value: &V) -> Result<(), Self::Error> where V: Serialize + ?Sized, { - let mut inner = self.inner.take().unwrap(); - inner = value.serialize(ElementSerializer::new(inner))?; - self.inner = Some(inner); - Ok(()) + self.do_key(value) } fn end(self) -> Result { @@ -260,13 +295,15 @@ impl ser::SerializeMap for SerializeStruct { pub struct SerializeSeq { inner: Option, comma: bool, + schema: Option<&'static ArraySchema>, } impl SerializeSeq { - fn new(inner: T) -> Self { + fn new(inner: T, schema: Option<&'static ArraySchema>) -> Self { Self { inner: Some(inner), comma: false, + schema, } } @@ -279,7 +316,7 @@ impl SerializeSeq { inner.write_char(',')?; } - inner = value.serialize(ElementSerializer::new(inner))?; + inner = value.serialize(ElementSerializer::new(inner, self.schema.map(|s| s.items)))?; self.inner = Some(inner); Ok(()) } @@ -331,11 +368,12 @@ same_impl! { pub struct ElementSerializer { inner: T, + schema: Option<&'static Schema>, } impl ElementSerializer { - fn new(inner: T) -> Self { - Self { inner } + fn new(inner: T, schema: Option<&'static Schema>) -> Self { + Self { inner, schema } } } @@ -345,6 +383,26 @@ impl ElementSerializer { .map_err(|err| Error::msg(format!("failed to write string: {err}")))?; Ok(self.inner) } + + fn do_seq(self) -> Result, Error> { + let schema = match self.schema { + Some(schema) => Some(schema.array().ok_or_else(|| { + Error::msg("property string serializer used for array with non-array schema") + })?), + None => None, + }; + Ok(ElementSerializeSeq::new(self.inner, schema)) + } + + fn do_object(self) -> Result, Error> { + let schema = match self.schema { + Some(schema) => Some(schema.any_object().ok_or_else(|| { + Error::msg("property string serializer used for object with non-object schema") + })?), + None => None, + }; + Ok(ElementSerializeStruct::new(self.inner, schema)) + } } macro_rules! forward_to_display { @@ -456,11 +514,11 @@ impl Serializer for ElementSerializer { } fn serialize_seq(self, _len: Option) -> Result { - Ok(ElementSerializeSeq::new(self.inner)) + self.do_seq() } fn serialize_tuple(self, _len: usize) -> Result { - Ok(ElementSerializeSeq::new(self.inner)) + self.do_seq() } fn serialize_tuple_struct( @@ -468,7 +526,7 @@ impl Serializer for ElementSerializer { _name: &'static str, _len: usize, ) -> Result { - Ok(ElementSerializeSeq::new(self.inner)) + self.do_seq() } fn serialize_tuple_variant( @@ -478,7 +536,7 @@ impl Serializer for ElementSerializer { _variant: &'static str, _len: usize, ) -> Result { - Ok(ElementSerializeSeq::new(self.inner)) + self.do_seq() } fn serialize_struct( @@ -486,7 +544,7 @@ impl Serializer for ElementSerializer { _name: &'static str, _len: usize, ) -> Result { - Ok(ElementSerializeStruct::new(self.inner)) + self.do_object() } fn serialize_struct_variant( @@ -496,11 +554,11 @@ impl Serializer for ElementSerializer { _variant: &'static str, _len: usize, ) -> Result { - Ok(ElementSerializeStruct::new(self.inner)) + self.do_object() } fn serialize_map(self, _len: Option) -> Result { - Ok(ElementSerializeStruct::new(self.inner)) + self.do_object() } } @@ -510,10 +568,10 @@ pub struct ElementSerializeStruct { } impl ElementSerializeStruct { - fn new(inner: T) -> Self { + fn new(inner: T, schema: Option<&'static dyn ObjectSchemaType>) -> Self { Self { output: inner, - inner: SerializeStruct::new(String::new()), + inner: SerializeStruct::new(String::new(), schema), } } @@ -537,7 +595,8 @@ same_impl! { where V: Serialize + ?Sized, { - self.inner.field(key, value) + self.inner.do_key(key)?; + self.inner.do_value(value) } fn end(self) -> Result { @@ -575,10 +634,10 @@ pub struct ElementSerializeSeq { } impl ElementSerializeSeq { - fn new(inner: T) -> Self { + fn new(inner: T, schema: Option<&'static ArraySchema>) -> Self { Self { output: inner, - inner: SerializeSeq::new(String::new()), + inner: SerializeSeq::new(String::new(), schema), } }