+++ /dev/null
-//! 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::{ObjectSchemaType, Schema};
-
-pub struct Error {
- msg: String,
-}
-
-impl fmt::Debug for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- fmt::Debug::fmt(&self.msg, f)
- }
-}
-
-impl fmt::Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- fmt::Display::fmt(&self.msg, f)
- }
-}
-
-impl std::error::Error for Error {}
-
-impl serde::de::Error for Error {
- fn custom<T: fmt::Display>(msg: T) -> Self {
- Self {
- msg: msg.to_string(),
- }
- }
-}
-
-impl From<serde_json::Error> for Error {
- fn from(error: serde_json::Error) -> Self {
- Error {
- msg: error.to_string(),
- }
- }
-}
-
-pub struct ExtractValueDeserializer<'o> {
- object: &'o mut serde_json::Map<String, Value>,
- schema: &'static Schema,
-}
-
-impl<'o> ExtractValueDeserializer<'o> {
- pub fn try_new(
- object: &'o mut serde_json::Map<String, Value>,
- schema: &'static Schema,
- ) -> Option<Self> {
- match schema {
- Schema::Object(_) | Schema::AllOf(_) => Some(Self { object, schema }),
- _ => None,
- }
- }
-}
-
-macro_rules! deserialize_non_object {
- ($name:ident) => {
- fn $name<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
- where
- V: Visitor<'de>,
- {
- Err(de::Error::custom(
- "deserializing partial object into type which is not an object",
- ))
- }
- };
- ($name:ident ( $($args:tt)* )) => {
- fn $name<V>(self, $($args)*, _visitor: V) -> Result<V::Value, Self::Error>
- 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<V>(self, visitor: V) -> Result<V::Value, Error>
- where
- V: Visitor<'de>,
- {
- self.deserialize_map(visitor)
- }
-
- fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Error>
- where
- V: Visitor<'de>,
- {
- use serde::de::Error;
-
- match self.schema {
- Schema::Object(schema) => visitor.visit_map(MapAccess::<'de>::new(
- self.object,
- schema.properties().map(|(name, _, _)| *name),
- )),
- Schema::AllOf(schema) => visitor.visit_map(MapAccess::<'de>::new(
- self.object,
- schema.properties().map(|(name, _, _)| *name),
- )),
-
- // The following should be caught by ExtractValueDeserializer::new()!
- _ => Err(Error::custom(
- "ExtractValueDeserializer used with invalid schema",
- )),
- }
- }
-
- fn deserialize_struct<V>(
- self,
- _name: &'static str,
- _fields: &'static [&'static str],
- visitor: V,
- ) -> Result<V::Value, Error>
- where
- V: Visitor<'de>,
- {
- use serde::de::Error;
-
- match self.schema {
- Schema::Object(schema) => visitor.visit_map(MapAccess::<'de>::new(
- self.object,
- schema.properties().map(|(name, _, _)| *name),
- )),
- Schema::AllOf(schema) => visitor.visit_map(MapAccess::<'de>::new(
- self.object,
- schema.properties().map(|(name, _, _)| *name),
- )),
-
- // The following should be caught by ExtractValueDeserializer::new()!
- _ => Err(Error::custom(
- "ExtractValueDeserializer used with invalid schema",
- )),
- }
- }
-
- 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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
- where
- V: Visitor<'de>,
- {
- visitor.visit_unit()
- }
-}
-
-struct MapAccess<'o, I> {
- object: &'o mut serde_json::Map<String, Value>,
- iter: I,
- value: Option<Value>,
-}
-
-impl<'o, I> MapAccess<'o, I>
-where
- I: Iterator<Item = &'static str>,
-{
- fn new(object: &'o mut serde_json::Map<String, Value>, iter: I) -> Self {
- Self {
- object,
- iter,
- value: None,
- }
- }
-}
-
-impl<'de, I> de::MapAccess<'de> for MapAccess<'de, I>
-where
- I: Iterator<Item = &'static str>,
-{
- type Error = Error;
-
- fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, 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<V>(&mut self, seed: V) -> Result<V::Value, Error>
- 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]
-#[allow(clippy::disallowed_names)]
-fn test_extraction() {
- use serde::Deserialize;
-
- use crate::{ObjectSchema, 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");
-}
--- /dev/null
+use std::borrow::{Borrow, Cow};
+use std::fmt;
+use std::ops::Range;
+
+/// Manage 2 lifetimes for deserializing.
+///
+/// When deserializing from a value it is considered to have lifetime `'de`. Any value that doesn't
+/// need to live longer than the deserialized *input* can *borrow* from that lifetime.
+///
+/// For example, from the `String` `{ "hello": "you" }` you can deserialize a `HashMap<&'de str,
+/// &'de str>`, as long as that map only exists as long as the original string.
+///
+/// However, if the data is `{ "hello": "\"hello\"" }`, then the value string needs to be
+/// unescaped, and can only be owned. However, if you only need it *temporarily*, eg. to parse a
+/// property string of numbers, you may want to avoid cloning individual parts from that.
+///
+/// Due to implementation details (particularly not wanting to provide a `Cow` version of
+/// `PropertyIterator`), we may need to be able to hold references to such intermediate values.
+///
+/// For the above scenario, `'o` would be the original `'de` lifetime, and `'i` the intermediate
+/// lifetime for the unescaped string.
+///
+/// Finally we also have an "Owned" value as a 3rd option.
+pub enum Cow3<'o, 'i, B>
+where
+ B: 'o + 'i + ToOwned + ?Sized,
+{
+ /// Original lifetime from the deserialization entry point.
+ Original(&'o B),
+
+ /// Borrowed from an intermediate value.
+ Intermediate(&'i B),
+
+ /// Owned data.
+ Owned(<B as ToOwned>::Owned),
+}
+
+impl<'o, 'i, B> Cow3<'o, 'i, B>
+where
+ B: 'o + 'i + ToOwned + ?Sized,
+{
+ /// From a `Cow` with the original lifetime.
+ pub fn from_original<T>(value: T) -> Self
+ where
+ T: Into<Cow<'o, B>>,
+ {
+ match value.into() {
+ Cow::Borrowed(v) => Self::Original(v),
+ Cow::Owned(v) => Self::Owned(v),
+ }
+ }
+
+ /// From a `Cow` with the intermediate lifetime.
+ pub fn from_intermediate<T>(value: T) -> Self
+ where
+ T: Into<Cow<'i, B>>,
+ {
+ match value.into() {
+ Cow::Borrowed(v) => Self::Intermediate(v),
+ Cow::Owned(v) => Self::Owned(v),
+ }
+ }
+
+ /// Turn into a `Cow`, forcing intermediate values to become owned.
+ pub fn into_original_or_owned(self) -> Cow<'o, B> {
+ match self {
+ Self::Original(v) => Cow::Borrowed(v),
+ Self::Intermediate(v) => Cow::Owned(v.to_owned()),
+ Self::Owned(v) => Cow::Owned(v),
+ }
+ }
+}
+
+impl<'o, 'i, B> std::ops::Deref for Cow3<'o, 'i, B>
+where
+ B: 'o + 'i + ToOwned + ?Sized,
+ <B as ToOwned>::Owned: Borrow<B>,
+{
+ type Target = B;
+
+ fn deref(&self) -> &B {
+ match self {
+ Self::Original(v) => v,
+ Self::Intermediate(v) => v,
+ Self::Owned(v) => v.borrow(),
+ }
+ }
+}
+
+impl<'o, 'i, B> AsRef<B> for Cow3<'o, 'i, B>
+where
+ B: 'o + 'i + ToOwned + ?Sized,
+ <B as ToOwned>::Owned: Borrow<B>,
+{
+ fn as_ref(&self) -> &B {
+ &self
+ }
+}
+
+/// Build a `Cow3` with a value surviving the `'o` lifetime.
+impl<'x, 'o, 'i, B> From<&'x B> for Cow3<'o, 'i, B>
+where
+ B: 'o + 'i + ToOwned + ?Sized,
+ <B as ToOwned>::Owned: Borrow<B>,
+ 'x: 'o,
+{
+ fn from(value: &'x B) -> Self {
+ Self::Original(value)
+ }
+}
+
+impl<B: ?Sized> fmt::Display for Cow3<'_, '_, B>
+where
+ B: fmt::Display + ToOwned,
+ B::Owned: fmt::Display,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::Original(ref b) => fmt::Display::fmt(b, f),
+ Self::Intermediate(ref b) => fmt::Display::fmt(b, f),
+ Self::Owned(ref o) => fmt::Display::fmt(o, f),
+ }
+ }
+}
+
+impl<B: ?Sized> fmt::Debug for Cow3<'_, '_, B>
+where
+ B: fmt::Debug + ToOwned,
+ B::Owned: fmt::Debug,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match *self {
+ Self::Original(ref b) => fmt::Debug::fmt(b, f),
+ Self::Intermediate(ref b) => fmt::Debug::fmt(b, f),
+ Self::Owned(ref o) => fmt::Debug::fmt(o, f),
+ }
+ }
+}
+
+impl<'o, 'i> Cow3<'o, 'i, str> {
+ /// Index value as a borrowed value.
+ pub fn slice<'ni, I>(&'ni self, index: I) -> Cow3<'o, 'ni, str>
+ where
+ I: std::slice::SliceIndex<str, Output = str>,
+ 'i: 'ni,
+ {
+ match self {
+ Self::Original(value) => Cow3::Original(&value[index]),
+ Self::Intermediate(value) => Cow3::Intermediate(&value[index]),
+ Self::Owned(value) => Cow3::Intermediate(&value.as_str()[index]),
+ }
+ }
+}
+
+pub fn str_slice_to_range(original: &str, slice: &str) -> Option<Range<usize>> {
+ let bytes = original.as_bytes();
+
+ let orig_addr = bytes.as_ptr() as usize;
+ let slice_addr = slice.as_bytes().as_ptr() as usize;
+ let offset = slice_addr.checked_sub(orig_addr)?;
+ if offset > orig_addr + bytes.len() {
+ return None;
+ }
+
+ let end = offset + slice.as_bytes().len();
+ if end > orig_addr + bytes.len() {
+ return None;
+ }
+
+ Some(offset..end)
+}
--- /dev/null
+//! 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::{ObjectSchemaType, Schema};
+
+pub struct Error {
+ msg: String,
+}
+
+impl fmt::Debug for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(&self.msg, f)
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(&self.msg, f)
+ }
+}
+
+impl std::error::Error for Error {}
+
+impl serde::de::Error for Error {
+ fn custom<T: fmt::Display>(msg: T) -> Self {
+ Self {
+ msg: msg.to_string(),
+ }
+ }
+}
+
+impl From<serde_json::Error> for Error {
+ fn from(error: serde_json::Error) -> Self {
+ Error {
+ msg: error.to_string(),
+ }
+ }
+}
+
+pub struct ExtractValueDeserializer<'o> {
+ object: &'o mut serde_json::Map<String, Value>,
+ schema: &'static Schema,
+}
+
+impl<'o> ExtractValueDeserializer<'o> {
+ pub fn try_new(
+ object: &'o mut serde_json::Map<String, Value>,
+ schema: &'static Schema,
+ ) -> Option<Self> {
+ match schema {
+ Schema::Object(_) | Schema::AllOf(_) => Some(Self { object, schema }),
+ _ => None,
+ }
+ }
+}
+
+macro_rules! deserialize_non_object {
+ ($name:ident) => {
+ fn $name<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
+ where
+ V: Visitor<'de>,
+ {
+ Err(de::Error::custom(
+ "deserializing partial object into type which is not an object",
+ ))
+ }
+ };
+ ($name:ident ( $($args:tt)* )) => {
+ fn $name<V>(self, $($args)*, _visitor: V) -> Result<V::Value, Self::Error>
+ 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<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: Visitor<'de>,
+ {
+ self.deserialize_map(visitor)
+ }
+
+ fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: Visitor<'de>,
+ {
+ use serde::de::Error;
+
+ match self.schema {
+ Schema::Object(schema) => visitor.visit_map(MapAccess::<'de>::new(
+ self.object,
+ schema.properties().map(|(name, _, _)| *name),
+ )),
+ Schema::AllOf(schema) => visitor.visit_map(MapAccess::<'de>::new(
+ self.object,
+ schema.properties().map(|(name, _, _)| *name),
+ )),
+
+ // The following should be caught by ExtractValueDeserializer::new()!
+ _ => Err(Error::custom(
+ "ExtractValueDeserializer used with invalid schema",
+ )),
+ }
+ }
+
+ fn deserialize_struct<V>(
+ self,
+ _name: &'static str,
+ _fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Error>
+ where
+ V: Visitor<'de>,
+ {
+ use serde::de::Error;
+
+ match self.schema {
+ Schema::Object(schema) => visitor.visit_map(MapAccess::<'de>::new(
+ self.object,
+ schema.properties().map(|(name, _, _)| *name),
+ )),
+ Schema::AllOf(schema) => visitor.visit_map(MapAccess::<'de>::new(
+ self.object,
+ schema.properties().map(|(name, _, _)| *name),
+ )),
+
+ // The following should be caught by ExtractValueDeserializer::new()!
+ _ => Err(Error::custom(
+ "ExtractValueDeserializer used with invalid schema",
+ )),
+ }
+ }
+
+ 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<V>(self, visitor: V) -> Result<V::Value, Self::Error>
+ where
+ V: Visitor<'de>,
+ {
+ visitor.visit_unit()
+ }
+}
+
+struct MapAccess<'o, I> {
+ object: &'o mut serde_json::Map<String, Value>,
+ iter: I,
+ value: Option<Value>,
+}
+
+impl<'o, I> MapAccess<'o, I>
+where
+ I: Iterator<Item = &'static str>,
+{
+ fn new(object: &'o mut serde_json::Map<String, Value>, iter: I) -> Self {
+ Self {
+ object,
+ iter,
+ value: None,
+ }
+ }
+}
+
+impl<'de, I> de::MapAccess<'de> for MapAccess<'de, I>
+where
+ I: Iterator<Item = &'static str>,
+{
+ type Error = Error;
+
+ fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, 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<V>(&mut self, seed: V) -> Result<V::Value, Error>
+ 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]
+#[allow(clippy::disallowed_names)]
+fn test_extraction() {
+ use serde::Deserialize;
+
+ use crate::{ObjectSchema, 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");
+}
--- /dev/null
+//! Property string deserialization.
+
+use std::borrow::Cow;
+use std::fmt;
+use std::ops::Range;
+
+use serde::de::{self, IntoDeserializer};
+
+use crate::schema::{self, ArraySchema, Schema};
+
+mod cow3;
+mod extract;
+mod no_schema;
+
+pub mod verify;
+
+pub use extract::ExtractValueDeserializer;
+
+use cow3::{str_slice_to_range, Cow3};
+
+#[derive(Debug)]
+pub struct Error(Cow<'static, str>);
+
+impl std::error::Error for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(&self.0, f)
+ }
+}
+
+impl Error {
+ pub(crate) fn msg<T: Into<Cow<'static, str>>>(msg: T) -> Self {
+ Self(msg.into())
+ }
+
+ fn invalid<T: fmt::Display>(msg: T) -> Self {
+ Self::msg(format!("schema validation failed: {}", msg))
+ }
+}
+
+impl serde::de::Error for Error {
+ fn custom<T: fmt::Display>(msg: T) -> Self {
+ Self(msg.to_string().into())
+ }
+}
+
+impl From<serde_json::Error> for Error {
+ fn from(error: serde_json::Error) -> Self {
+ Self(error.to_string().into())
+ }
+}
+
+impl From<fmt::Error> for Error {
+ fn from(err: fmt::Error) -> Self {
+ Self::msg(err.to_string())
+ }
+}
+
+/// Deserializer for parts a part of a property string given a schema.
+pub struct SchemaDeserializer<'de, 'i> {
+ input: Cow3<'de, 'i, str>,
+ schema: &'static Schema,
+}
+
+impl<'de, 'i> SchemaDeserializer<'de, 'i> {
+ pub fn new_cow(input: Cow3<'de, 'i, str>, schema: &'static Schema) -> Self {
+ Self { input, schema }
+ }
+
+ pub fn new<T>(input: T, schema: &'static Schema) -> Self
+ where
+ T: Into<Cow<'de, str>>,
+ {
+ Self {
+ input: Cow3::from_original(input.into()),
+ schema,
+ }
+ }
+
+ fn deserialize_str<V>(
+ self,
+ visitor: V,
+ schema: &'static schema::StringSchema,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ schema
+ .check_constraints(&self.input)
+ .map_err(|err| Error::invalid(err))?;
+ match self.input {
+ Cow3::Original(input) => visitor.visit_borrowed_str(input),
+ Cow3::Intermediate(input) => visitor.visit_str(input),
+ Cow3::Owned(input) => visitor.visit_string(input),
+ }
+ }
+
+ fn deserialize_property_string<V>(
+ self,
+ visitor: V,
+ schema: &'static Schema,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match schema {
+ Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
+ Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
+ _ => Err(Error::msg(
+ "non-object-like schema in ApiStringFormat::PropertyString while deserializing a property string",
+ )),
+ }
+ }
+
+ fn deserialize_array_string<V>(
+ self,
+ visitor: V,
+ schema: &'static Schema,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match schema {
+ Schema::Array(schema) => visitor.visit_seq(SeqAccess::new(self.input, schema)),
+ _ => Err(Error::msg(
+ "non-array schema in ApiStringFormat::PropertyString while deserializing an array",
+ )),
+ }
+ }
+}
+
+impl<'de, 'i> de::Deserializer<'de> for SchemaDeserializer<'de, 'i> {
+ type Error = Error;
+
+ fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.schema {
+ Schema::Array(schema) => visitor.visit_seq(SeqAccess::new(self.input, schema)),
+ Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
+ Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
+ Schema::Null => Err(Error::msg("null")),
+ Schema::Boolean(_) => visitor.visit_bool(
+ schema::parse_boolean(&self.input)
+ .map_err(|_| Error::msg(format!("not a boolean: {:?}", self.input)))?,
+ ),
+ Schema::Integer(schema) => {
+ // FIXME: isize vs explicit i64, needs fixing in schema check_constraints api
+ let value: isize = self
+ .input
+ .parse()
+ .map_err(|_| Error::msg(format!("not an integer: {:?}", self.input)))?;
+
+ schema
+ .check_constraints(value)
+ .map_err(|err| Error::invalid(err))?;
+
+ let value: i64 = i64::try_from(value)
+ .map_err(|_| Error::invalid("isize did not fit into i64"))?;
+
+ if let Ok(value) = u64::try_from(value) {
+ visitor.visit_u64(value)
+ } else {
+ visitor.visit_i64(value)
+ }
+ }
+ Schema::Number(schema) => {
+ let value: f64 = self
+ .input
+ .parse()
+ .map_err(|_| Error::msg(format!("not a valid number: {:?}", self.input)))?;
+
+ schema
+ .check_constraints(value)
+ .map_err(|err| Error::invalid(err))?;
+
+ visitor.visit_f64(value)
+ }
+ Schema::String(schema) => {
+ // If not requested differently, strings stay strings, otherwise deserializing to a
+ // `Value` will get objects here instead of strings, which we do not expect
+ // anywhere.
+ self.deserialize_str(visitor, schema)
+ }
+ }
+ }
+
+ fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ if self.input.is_empty() {
+ visitor.visit_none()
+ } else {
+ visitor.visit_some(self)
+ }
+ }
+
+ fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.schema {
+ Schema::String(schema) => self.deserialize_str(visitor, schema),
+ _ => Err(Error::msg(
+ "tried to deserialize a string with a non-string-schema",
+ )),
+ }
+ }
+
+ fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.schema {
+ Schema::String(schema) => self.deserialize_str(visitor, schema),
+ _ => Err(Error::msg(
+ "tried to deserialize a string with a non-string-schema",
+ )),
+ }
+ }
+
+ fn deserialize_newtype_struct<V>(
+ self,
+ _name: &'static str,
+ visitor: V,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ visitor.visit_newtype_struct(self)
+ }
+
+ fn deserialize_struct<V>(
+ self,
+ name: &'static str,
+ _fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.schema {
+ Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
+ Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
+ Schema::String(schema) => match schema.format {
+ Some(schema::ApiStringFormat::PropertyString(schema)) => {
+ self.deserialize_property_string(visitor, schema)
+ }
+ _ => Err(Error::msg(format!(
+ "cannot deserialize struct '{}' with a string schema",
+ name
+ ))),
+ },
+ _ => Err(Error::msg(format!(
+ "cannot deserialize struct '{}' with non-object schema",
+ name,
+ ))),
+ }
+ }
+
+ fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.schema {
+ Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
+ Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
+ Schema::String(schema) => match schema.format {
+ Some(schema::ApiStringFormat::PropertyString(schema)) => {
+ self.deserialize_property_string(visitor, schema)
+ }
+ _ => Err(Error::msg(format!(
+ "cannot deserialize map with a string schema",
+ ))),
+ },
+ _ => Err(Error::msg(format!(
+ "cannot deserialize map with non-object schema",
+ ))),
+ }
+ }
+
+ fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.schema {
+ Schema::Array(schema) => visitor.visit_seq(SeqAccess::new(self.input, schema)),
+ Schema::String(schema) => match schema.format {
+ Some(schema::ApiStringFormat::PropertyString(schema)) => {
+ self.deserialize_array_string(visitor, schema)
+ }
+ _ => Err(Error::msg("cannot deserialize array with a string schema")),
+ },
+ _ => Err(Error::msg(
+ "cannot deserialize array with non-object schema",
+ )),
+ }
+ }
+
+ fn deserialize_enum<V>(
+ self,
+ name: &'static str,
+ _variants: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.schema {
+ Schema::String(_) => visitor.visit_enum(self.input.into_deserializer()),
+ _ => Err(Error::msg(format!(
+ "cannot deserialize enum '{}' with non-string schema",
+ name,
+ ))),
+ }
+ }
+
+ serde::forward_to_deserialize_any! {
+ i8 i16 i32 i64
+ u8 u16 u32 u64
+ f32 f64
+ bool
+ char
+ bytes byte_buf
+ unit unit_struct
+ tuple tuple_struct
+ identifier
+ ignored_any
+ }
+}
+
+fn next_str_entry(input: &str, at: &mut usize, has_null: bool) -> Option<Range<usize>> {
+ while *at != input.len() {
+ let begin = *at;
+
+ let part = &input[*at..];
+
+ let part_end = if has_null {
+ part.find('\0')
+ } else {
+ part.find(|c: char| c == ',' || c == ';' || char::is_ascii_whitespace(&c))
+ };
+
+ let end = match part_end {
+ None => {
+ *at = input.len();
+ input.len()
+ }
+ Some(rel_end) => {
+ *at += rel_end + 1;
+ begin + rel_end
+ }
+ };
+
+ if input[..end].is_empty() {
+ continue;
+ }
+
+ return Some(begin..end);
+ }
+
+ None
+}
+
+/// Parse an array with a schema.
+///
+/// Provides both `SeqAccess` and `Deserializer` implementations.
+pub struct SeqAccess<'o, 'i, 's> {
+ schema: &'s ArraySchema,
+ was_empty: bool,
+ input: Cow3<'o, 'i, str>,
+ has_null: bool,
+ at: usize,
+ count: usize,
+}
+
+impl<'o, 'i, 's> SeqAccess<'o, 'i, 's> {
+ pub fn new(input: Cow3<'o, 'i, str>, schema: &'s ArraySchema) -> Self {
+ Self {
+ schema,
+ was_empty: input.is_empty(),
+ has_null: input.contains('\0'),
+ input,
+ at: 0,
+ count: 0,
+ }
+ }
+}
+
+impl<'de, 'i, 's> de::SeqAccess<'de> for SeqAccess<'de, 'i, 's> {
+ type Error = Error;
+
+ fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Error>
+ where
+ T: de::DeserializeSeed<'de>,
+ {
+ if self.was_empty {
+ return Ok(None);
+ }
+
+ while let Some(el_range) = next_str_entry(&self.input, &mut self.at, self.has_null) {
+ if el_range.is_empty() {
+ continue;
+ }
+
+ if let Some(max) = self.schema.max_length {
+ if self.count == max {
+ return Err(Error::msg("too many elements"));
+ }
+ }
+
+ self.count += 1;
+
+ return seed
+ .deserialize(SchemaDeserializer::new_cow(
+ self.input.slice(el_range),
+ self.schema.items,
+ ))
+ .map(Some);
+ }
+
+ if let Some(min) = self.schema.min_length {
+ if self.count < min {
+ return Err(Error::msg("not enough elements"));
+ }
+ }
+
+ Ok(None)
+ }
+}
+
+impl<'de, 'i, 's> de::Deserializer<'de> for SeqAccess<'de, 'i, 's> {
+ type Error = Error;
+
+ fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ visitor.visit_seq(self)
+ }
+
+ fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ if self.was_empty {
+ visitor.visit_none()
+ } else {
+ visitor.visit_some(self)
+ }
+ }
+
+ serde::forward_to_deserialize_any! {
+ i8 i16 i32 i64 u8 u16 u32 u64 f32 f64
+ bool char str string
+ bytes byte_buf
+ unit unit_struct
+ newtype_struct
+ tuple tuple_struct
+ enum map seq
+ struct
+ identifier ignored_any
+ }
+}
+
+/// Provides serde's `MapAccess` for parsing a property string.
+pub struct MapAccess<'de, 'i> {
+ // The property string iterator and quoted string handler.
+ input: Cow3<'de, 'i, str>,
+ input_at: usize, // for when using `Cow3::Owned`.
+
+ /// As a `Deserializer` we want to be able to handle `deserialize_option` and need to know
+ /// whether this was an empty string.
+ was_empty: bool,
+
+ /// The schema used to verify the contents and distinguish between structs and property
+ /// strings.
+ schema: &'static dyn schema::ObjectSchemaType,
+
+ /// The current next value's key, value and schema (if available).
+ value: Option<(Cow<'de, str>, Cow<'de, str>, Option<&'static Schema>)>,
+}
+
+impl<'de, 'i> MapAccess<'de, 'i> {
+ pub fn new<S: schema::ObjectSchemaType>(input: &'de str, schema: &'static S) -> Self {
+ Self {
+ was_empty: input.is_empty(),
+ input: Cow3::Original(input),
+ schema,
+ input_at: 0,
+ value: None,
+ }
+ }
+
+ pub fn new_cow<S: schema::ObjectSchemaType>(
+ input: Cow3<'de, 'i, str>,
+ schema: &'static S,
+ ) -> Self {
+ Self {
+ was_empty: input.is_empty(),
+ input,
+ schema,
+ input_at: 0,
+ value: None,
+ }
+ }
+
+ pub fn new_intermediate<S: schema::ObjectSchemaType>(
+ input: &'i str,
+ schema: &'static S,
+ ) -> Self {
+ Self {
+ was_empty: input.is_empty(),
+ input: Cow3::Intermediate(input),
+ schema,
+ input_at: 0,
+ value: None,
+ }
+ }
+}
+
+impl<'de, 'i> de::MapAccess<'de> for MapAccess<'de, 'i> {
+ type Error = Error;
+
+ fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>
+ where
+ K: de::DeserializeSeed<'de>,
+ {
+ use crate::property_string::next_property;
+
+ if self.was_empty {
+ // shortcut
+ return Ok(None);
+ }
+
+ let (key, value, rem) = match next_property(&self.input[self.input_at..]) {
+ None => return Ok(None),
+ Some(entry) => entry?,
+ };
+
+ if rem.is_empty() {
+ self.input_at = self.input.len();
+ } else {
+ let ofs = unsafe { rem.as_ptr().offset_from(self.input.as_ptr()) };
+ if ofs < 0 || (ofs as usize) > self.input.len() {
+ // 'rem' is either an empty string (rem.is_empty() is true), or a valid offset into
+ // the input string...
+ panic!("unexpected remainder in next_property");
+ }
+ self.input_at = ofs as usize;
+ }
+
+ let value = match value {
+ Cow::Owned(value) => Cow::Owned(value),
+ Cow::Borrowed(value) => match str_slice_to_range(&self.input, value) {
+ None => Cow::Owned(value.to_string()),
+ Some(range) => match &self.input {
+ Cow3::Original(orig) => Cow::Borrowed(&orig[range]),
+ _ => Cow::Owned(value.to_string()),
+ },
+ },
+ };
+
+ let (key, schema) = match key {
+ Some(key) => {
+ let schema = self.schema.lookup(&key);
+ let key = match str_slice_to_range(&self.input, key) {
+ None => Cow::Owned(key.to_string()),
+ Some(range) => match &self.input {
+ Cow3::Original(orig) => Cow::Borrowed(&orig[range]),
+ _ => Cow::Owned(key.to_string()),
+ },
+ };
+ (key, schema)
+ }
+ None => match self.schema.default_key() {
+ Some(key) => {
+ let schema = self
+ .schema
+ .lookup(key)
+ .ok_or(Error::msg("bad default key"))?;
+ (Cow::Borrowed(key), Some(schema))
+ }
+ None => return Err(Error::msg("missing key")),
+ },
+ };
+ let schema = schema.map(|(_optional, schema)| schema);
+
+ let out = match &key {
+ Cow::Borrowed(key) => {
+ seed.deserialize(de::value::BorrowedStrDeserializer::<'de, Error>::new(key))?
+ }
+ Cow::Owned(key) => {
+ seed.deserialize(IntoDeserializer::<Error>::into_deserializer(key.as_str()))?
+ }
+ };
+
+ self.value = Some((key, value, schema));
+
+ Ok(Some(out))
+ }
+
+ fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Error>
+ where
+ V: de::DeserializeSeed<'de>,
+ {
+ let (key, input, schema) = self.value.take().ok_or(Error::msg("bad map access"))?;
+
+ if let Some(schema) = schema {
+ seed.deserialize(SchemaDeserializer::new(input, schema))
+ } else {
+ if !verify::is_verifying() && !self.schema.additional_properties() {
+ return Err(Error::msg(format!("unknown key {:?}", key.as_ref())));
+ }
+
+ // additional properties are treated as strings...
+ let deserializer = no_schema::NoSchemaDeserializer::new(input);
+ seed.deserialize(deserializer)
+ }
+ }
+}
--- /dev/null
+//! When we have no schema we allow simple values and arrays.
+
+use std::borrow::Cow;
+
+use serde::de;
+
+use super::cow3::Cow3;
+use super::Error;
+
+/// This can only deserialize strings and lists of strings and has no schema.
+pub struct NoSchemaDeserializer<'de, 'i> {
+ input: Cow3<'de, 'i, str>,
+}
+
+impl<'de, 'i> NoSchemaDeserializer<'de, 'i> {
+ pub fn new<T>(input: T) -> Self
+ where
+ T: Into<Cow<'de, str>>,
+ {
+ Self {
+ input: Cow3::from_original(input),
+ }
+ }
+}
+
+macro_rules! deserialize_num {
+ ($( $name:ident : $visit:ident : $ty:ty : $error:literal, )*) => {$(
+ fn $name<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
+ let value: $ty = self
+ .input
+ .parse()
+ .map_err(|_| Error::msg(format!($error, self.input)))?;
+ visitor.$visit(value)
+ }
+ )*}
+}
+
+impl<'de, 'i> de::Deserializer<'de> for NoSchemaDeserializer<'de, 'i> {
+ type Error = Error;
+
+ fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.input {
+ Cow3::Original(input) => visitor.visit_borrowed_str(input),
+ Cow3::Intermediate(input) => visitor.visit_str(input),
+ Cow3::Owned(input) => visitor.visit_string(input),
+ }
+ }
+
+ fn deserialize_struct<V>(
+ self,
+ _name: &'static str,
+ _fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ self.deserialize_any(visitor)
+ }
+
+ fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ if self.input.is_empty() {
+ visitor.visit_none()
+ } else {
+ visitor.visit_some(self)
+ }
+ }
+
+ fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ self.deserialize_any(visitor)
+ }
+
+ fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ visitor.visit_seq(SimpleSeqAccess::new(self.input))
+ }
+
+ fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ visitor.visit_seq(SimpleSeqAccess::new(self.input))
+ }
+
+ fn deserialize_tuple_struct<V>(
+ self,
+ _name: &'static str,
+ _len: usize,
+ visitor: V,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ visitor.visit_seq(SimpleSeqAccess::new(self.input))
+ }
+
+ deserialize_num! {
+ deserialize_i8 : visit_i8 : i8 : "not an integer: {:?}",
+ deserialize_u8 : visit_u8 : u8 : "not an integer: {:?}",
+ deserialize_i16 : visit_i16 : i16 : "not an integer: {:?}",
+ deserialize_u16 : visit_u16 : u16 : "not an integer: {:?}",
+ deserialize_i32 : visit_i32 : i32 : "not an integer: {:?}",
+ deserialize_u32 : visit_u32 : u32 : "not an integer: {:?}",
+ deserialize_i64 : visit_i64 : i64 : "not an integer: {:?}",
+ deserialize_u64 : visit_u64 : u64 : "not an integer: {:?}",
+ deserialize_f32 : visit_f32 : f32 : "not a number: {:?}",
+ deserialize_f64 : visit_f64 : f64 : "not a number: {:?}",
+ deserialize_bool : visit_bool : bool : "not a boolean: {:?}",
+ }
+
+ fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ let mut chars = self.input.chars();
+ let ch = chars
+ .next()
+ .ok_or_else(|| Error::msg(format!("not a single character: {:?}", self.input)))?;
+ if chars.next().is_some() {
+ return Err(Error::msg(format!(
+ "not a single character: {:?}",
+ self.input
+ )));
+ }
+ visitor.visit_char(ch)
+ }
+
+ fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.input {
+ Cow3::Original(input) => visitor.visit_borrowed_str(input),
+ Cow3::Intermediate(input) => visitor.visit_str(input),
+ Cow3::Owned(input) => visitor.visit_string(input),
+ }
+ }
+
+ fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.input {
+ Cow3::Original(input) => visitor.visit_borrowed_str(input),
+ Cow3::Intermediate(input) => visitor.visit_str(input),
+ Cow3::Owned(input) => visitor.visit_string(input),
+ }
+ }
+
+ fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.input {
+ Cow3::Original(input) => visitor.visit_borrowed_str(input),
+ Cow3::Intermediate(input) => visitor.visit_str(input),
+ Cow3::Owned(input) => visitor.visit_string(input),
+ }
+ }
+
+ fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.input {
+ Cow3::Original(input) => visitor.visit_borrowed_bytes(input.as_bytes()),
+ Cow3::Intermediate(input) => visitor.visit_bytes(input.as_bytes()),
+ Cow3::Owned(input) => visitor.visit_byte_buf(input.into_bytes()),
+ }
+ }
+
+ fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ match self.input {
+ Cow3::Original(input) => visitor.visit_borrowed_bytes(input.as_bytes()),
+ Cow3::Intermediate(input) => visitor.visit_bytes(input.as_bytes()),
+ Cow3::Owned(input) => visitor.visit_byte_buf(input.into_bytes()),
+ }
+ }
+
+ fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ if self.input.is_empty() {
+ visitor.visit_unit()
+ } else {
+ self.deserialize_string(visitor)
+ }
+ }
+
+ fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ if self.input.is_empty() {
+ visitor.visit_unit()
+ } else {
+ self.deserialize_string(visitor)
+ }
+ }
+
+ fn deserialize_newtype_struct<V>(
+ self,
+ _name: &'static str,
+ visitor: V,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ visitor.visit_newtype_struct(self)
+ }
+
+ fn deserialize_enum<V>(
+ self,
+ _name: &'static str,
+ _variants: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ use serde::de::IntoDeserializer;
+ visitor.visit_enum(self.input.into_deserializer())
+ }
+
+ fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Error>
+ where
+ V: de::Visitor<'de>,
+ {
+ self.deserialize_string(visitor)
+ }
+}
+
+/// Parse an array without a schema.
+///
+/// It may only contain simple values.
+struct SimpleSeqAccess<'de, 'i> {
+ input: Cow3<'de, 'i, str>,
+ has_null: bool,
+ at: usize,
+}
+
+impl<'de, 'i> SimpleSeqAccess<'de, 'i> {
+ fn new(input: Cow3<'de, 'i, str>) -> Self {
+ Self {
+ has_null: input.contains('\0'),
+ input,
+ at: 0,
+ }
+ }
+}
+
+impl<'de, 'i> de::SeqAccess<'de> for SimpleSeqAccess<'de, 'i> {
+ type Error = Error;
+
+ fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Error>
+ where
+ T: de::DeserializeSeed<'de>,
+ {
+ while self.at != self.input.len() {
+ let begin = self.at;
+
+ let input = &self.input[self.at..];
+
+ let end = if self.has_null {
+ input.find('\0')
+ } else {
+ input.find(|c: char| c == ',' || c == ';' || char::is_ascii_whitespace(&c))
+ };
+
+ let end = match end {
+ None => {
+ self.at = self.input.len();
+ input.len()
+ }
+ Some(pos) => {
+ self.at += pos + 1;
+ pos
+ }
+ };
+
+ if input[..end].is_empty() {
+ continue;
+ }
+
+ return seed
+ .deserialize(NoSchemaDeserializer::new(match &self.input {
+ Cow3::Original(input) => Cow::Borrowed(&input[begin..end]),
+ Cow3::Intermediate(input) => Cow::Owned(input[begin..end].to_string()),
+ Cow3::Owned(input) => Cow::Owned(input[begin..end].to_string()),
+ }))
+ .map(Some);
+ }
+
+ Ok(None)
+ }
+}
--- /dev/null
+use std::borrow::Cow;
+use std::cell::UnsafeCell;
+use std::collections::HashSet;
+use std::fmt;
+use std::mem;
+
+use anyhow::format_err;
+use serde::de::{self, Deserialize, Unexpected};
+
+use super::Schema;
+use crate::schema::ParameterError;
+
+struct VerifyState {
+ schema: Option<&'static Schema>,
+ path: String,
+}
+
+thread_local! {
+ static VERIFY_SCHEMA: UnsafeCell<Option<VerifyState>> = UnsafeCell::new(None);
+ static ERRORS: UnsafeCell<Vec<(String, anyhow::Error)>> = UnsafeCell::new(Vec::new());
+}
+
+pub(crate) struct SchemaGuard(Option<VerifyState>);
+
+impl Drop for SchemaGuard {
+ fn drop(&mut self) {
+ VERIFY_SCHEMA.with(|schema| unsafe {
+ if self.0.is_none() {
+ ERRORS.with(|errors| (*errors.get()).clear())
+ }
+ *schema.get() = self.0.take();
+ });
+ }
+}
+
+impl SchemaGuard {
+ /// If this is the "final" guard, take out the errors:
+ fn errors(self) -> Option<Vec<(String, anyhow::Error)>> {
+ if self.0.is_none() {
+ Some(ERRORS.with(|e| mem::take(unsafe { &mut *e.get() })))
+ } else {
+ None
+ }
+ }
+}
+
+pub(crate) fn push_schema(schema: Option<&'static Schema>, path: Option<&str>) -> SchemaGuard {
+ SchemaGuard(VERIFY_SCHEMA.with(|s| {
+ let prev = unsafe { (*s.get()).take() };
+ let path = match (path, &prev) {
+ (Some(path), Some(prev)) => join_path(&prev.path, path),
+ (Some(path), None) => path.to_owned(),
+ (None, Some(prev)) => prev.path.clone(),
+ (None, None) => String::new(),
+ };
+
+ unsafe {
+ (*s.get()) = Some(VerifyState { schema, path });
+ }
+
+ prev
+ }))
+}
+
+fn get_path() -> Option<String> {
+ VERIFY_SCHEMA.with(|s| unsafe { (*s.get()).as_ref().map(|state| state.path.clone()) })
+}
+
+fn get_schema() -> Option<&'static Schema> {
+ VERIFY_SCHEMA.with(|s| unsafe { (*s.get()).as_ref().and_then(|state| state.schema) })
+}
+
+pub(crate) fn is_verifying() -> bool {
+ VERIFY_SCHEMA.with(|s| unsafe { (*s.get()).as_ref().is_some() })
+}
+
+fn join_path(a: &str, b: &str) -> String {
+ if a.is_empty() {
+ b.to_string()
+ } else {
+ format!("{}/{}", a, b)
+ }
+}
+
+fn push_errstr_path(err_path: &str, err: &str) {
+ if let Some(path) = get_path() {
+ push_err_do(join_path(&path, err_path), format_err!("{}", err));
+ }
+}
+
+fn push_err(err: impl fmt::Display) {
+ if let Some(path) = get_path() {
+ push_err_do(path, format_err!("{}", err));
+ }
+}
+
+fn push_err_do(path: String, err: anyhow::Error) {
+ ERRORS.with(move |errors| unsafe { (*errors.get()).push((path, err)) })
+}
+
+/// Helper to collect multiple deserialization errors for better reporting.
+///
+/// This is similar to [`IgnoredAny`] in that it implements [`Deserialize`]
+/// but does not actually deserialize to anything, however, when a deserialization error occurs,
+/// it'll try to continue and collect further errors.
+///
+/// This only makes sense with the [`SchemaDeserializer`](super::SchemaDeserializer).
+pub struct Verifier;
+
+impl<'de> Deserialize<'de> for Verifier {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ if let Some(schema) = get_schema() {
+ let visitor = Visitor(schema);
+ match schema {
+ Schema::Boolean(_) => deserializer.deserialize_bool(visitor),
+ Schema::Integer(_) => deserializer.deserialize_i64(visitor),
+ Schema::Number(_) => deserializer.deserialize_f64(visitor),
+ Schema::String(_) => deserializer.deserialize_str(visitor),
+ Schema::Object(_) => deserializer.deserialize_map(visitor),
+ Schema::AllOf(_) => deserializer.deserialize_map(visitor),
+ Schema::Array(_) => deserializer.deserialize_seq(visitor),
+ Schema::Null => deserializer.deserialize_unit(visitor),
+ }
+ } else {
+ Ok(Verifier)
+ }
+ }
+}
+
+pub fn verify(schema: &'static Schema, value: &str) -> Result<(), anyhow::Error> {
+ let guard = push_schema(Some(schema), None);
+ Verifier::deserialize(super::SchemaDeserializer::new(value, schema))?;
+
+ if let Some(errors) = guard.errors() {
+ Err(ParameterError::from_list(errors).into())
+ } else {
+ Ok(())
+ }
+}
+
+struct Visitor(&'static Schema);
+
+impl<'de> de::Visitor<'de> for Visitor {
+ type Value = Verifier;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ Schema::Boolean(_) => f.write_str("boolean"),
+ Schema::Integer(_) => f.write_str("integer"),
+ Schema::Number(_) => f.write_str("number"),
+ Schema::String(_) => f.write_str("string"),
+ Schema::Object(_) => f.write_str("object"),
+ Schema::AllOf(_) => f.write_str("allOf"),
+ Schema::Array(_) => f.write_str("Array"),
+ Schema::Null => f.write_str("null"),
+ }
+ }
+
+ fn visit_bool<E: de::Error>(self, v: bool) -> Result<Self::Value, E> {
+ match self.0 {
+ Schema::Boolean(_) => (),
+ _ => return Err(E::invalid_type(Unexpected::Bool(v), &self)),
+ }
+ Ok(Verifier)
+ }
+
+ fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
+ match self.0 {
+ Schema::Integer(schema) => match schema.check_constraints(v as isize) {
+ Ok(()) => Ok(Verifier),
+ Err(err) => Err(E::custom(err)),
+ },
+ _ => Err(E::invalid_type(Unexpected::Signed(v), &self)),
+ }
+ }
+
+ fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
+ match self.0 {
+ Schema::Integer(schema) => match schema.check_constraints(v as isize) {
+ Ok(()) => Ok(Verifier),
+ Err(err) => Err(E::custom(err)),
+ },
+ _ => Err(E::invalid_type(Unexpected::Unsigned(v), &self)),
+ }
+ }
+
+ fn visit_f64<E: de::Error>(self, v: f64) -> Result<Self::Value, E> {
+ match self.0 {
+ Schema::Number(schema) => match schema.check_constraints(v) {
+ Ok(()) => Ok(Verifier),
+ Err(err) => Err(E::custom(err)),
+ },
+ _ => Err(E::invalid_type(Unexpected::Float(v), &self)),
+ }
+ }
+
+ fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
+ use de::Error;
+
+ let schema = match self.0 {
+ Schema::Array(schema) => schema,
+ _ => return Err(A::Error::invalid_type(Unexpected::Seq, &self)),
+ };
+
+ let _guard = push_schema(Some(schema.items), None);
+
+ let mut count = 0;
+ loop {
+ match seq.next_element::<Verifier>() {
+ Ok(Some(_)) => count += 1,
+ Ok(None) => break,
+ Err(err) => push_err(err),
+ }
+ }
+
+ schema.check_length(count).map_err(de::Error::custom)?;
+
+ Ok(Verifier)
+ }
+
+ fn visit_map<A: de::MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
+ use de::Error;
+
+ let schema: &'static dyn crate::schema::ObjectSchemaType = match self.0 {
+ Schema::Object(schema) => schema,
+ Schema::AllOf(schema) => schema,
+ _ => return Err(A::Error::invalid_type(Unexpected::Map, &self)),
+ };
+
+ let mut required_keys = HashSet::<&'static str>::new();
+ for (key, optional, _schema) in schema.properties() {
+ if !optional {
+ required_keys.insert(key);
+ }
+ }
+
+ let mut other_keys = HashSet::<String>::new();
+ loop {
+ let key: Cow<'de, str> = match map.next_key()? {
+ Some(key) => key,
+ None => break,
+ };
+
+ let _guard = match schema.lookup(&key) {
+ Some((optional, schema)) => {
+ if !optional {
+ // required keys are only tracked in the required_keys hashset
+ if !required_keys.remove(key.as_ref()) {
+ // duplicate key
+ push_errstr_path(&key, "duplicate key");
+ }
+ } else {
+ // optional keys
+ if !other_keys.insert(key.clone().into_owned()) {
+ push_errstr_path(&key, "duplicate key");
+ }
+ }
+
+ push_schema(Some(schema), Some(&key))
+ }
+ None => {
+ if !schema.additional_properties() {
+ push_errstr_path(&key, "schema does not allow additional properties");
+ } else if !other_keys.insert(key.clone().into_owned()) {
+ push_errstr_path(&key, "duplicate key");
+ }
+
+ push_schema(None, Some(&key))
+ }
+ };
+
+ match map.next_value::<Verifier>() {
+ Ok(Verifier) => (),
+ Err(err) => push_err(err),
+ }
+ }
+
+ for key in required_keys {
+ push_errstr_path(key, "property is missing and it is not optional");
+ }
+
+ Ok(Verifier)
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
+ let schema = match self.0 {
+ Schema::String(schema) => schema,
+ _ => return Err(E::invalid_type(Unexpected::Str(value), &self)),
+ };
+
+ let _: () = schema.check_constraints(value).map_err(E::custom)?;
+
+ Ok(Verifier)
+ }
+}
pub mod de;
pub mod format;
+pub mod ser;
pub mod property_string;
//! strings.
use std::borrow::Cow;
+use std::fmt;
use std::mem;
-use anyhow::{bail, format_err, Error};
+use serde::{Deserialize, Serialize};
+
+use crate::de::Error;
+use crate::schema::ApiType;
/// Iterate over the `key=value` pairs of a property string.
///
type Item = Result<(Option<&'a str>, Cow<'a, str>), Error>;
fn next(&mut self) -> Option<Self::Item> {
- if self.data.is_empty() {
- return None;
- }
-
- let key = if self.data.starts_with('"') {
- // value without key and quoted
- None
- } else {
- let key = match self.data.find([',', '=']) {
- Some(pos) if self.data.as_bytes()[pos] == b',' => None,
- Some(pos) => Some(ascii_split_off(&mut self.data, pos)),
- None => None,
- };
-
- if !self.data.starts_with('"') {
- let value = match self.data.find(',') {
- Some(pos) => ascii_split_off(&mut self.data, pos),
- None => mem::take(&mut self.data),
- };
- return Some(Ok((key, Cow::Borrowed(value))));
+ Some(match next_property(self.data)? {
+ Ok((key, value, data)) => {
+ self.data = data;
+ Ok((key, value))
}
+ Err(err) => Err(err),
+ })
+ }
+}
- key
- };
+/// Returns an optional key, its value, and the remainder of `data`.
+pub(crate) fn next_property(
+ mut data: &str,
+) -> Option<Result<(Option<&str>, Cow<str>, &str), Error>> {
+ if data.is_empty() {
+ return None;
+ }
- let value = match parse_quoted_string(&mut self.data) {
- Ok(value) => value,
- Err(err) => return Some(Err(err)),
+ let key = if data.starts_with('"') {
+ // value without key and quoted
+ None
+ } else {
+ let key = match data.find([',', '=']) {
+ Some(pos) if data.as_bytes()[pos] == b',' => None,
+ Some(pos) => Some(ascii_split_off(&mut data, pos)),
+ None => None,
};
- if !self.data.is_empty() {
- if self.data.starts_with(',') {
- self.data = &self.data[1..];
- } else {
- return Some(Err(format_err!("garbage after quoted string")));
- }
+ if !data.starts_with('"') {
+ let value = match data.find(',') {
+ Some(pos) => ascii_split_off(&mut data, pos),
+ None => mem::take(&mut data),
+ };
+ return Some(Ok((key, Cow::Borrowed(value), data)));
}
- Some(Ok((key, value)))
+ key
+ };
+
+ let value = match parse_quoted_string(&mut data) {
+ Ok(value) => value,
+ Err(err) => return Some(Err(err)),
+ };
+
+ if !data.is_empty() {
+ if data.starts_with(',') {
+ data = &data[1..];
+ } else {
+ return Some(Err(Error::msg("garbage after quoted string")));
+ }
}
+
+ Some(Ok((key, value, data)))
}
impl<'a> std::iter::FusedIterator for PropertyIterator<'a> {}
let data = data_out.as_bytes();
if data[0] != b'"' {
- bail!("not a quoted string");
+ return Err(Error::msg("not a quoted string"));
}
let mut i = 1;
}
if i == data.len() {
// reached the end before reaching a quote
- bail!("unexpected end of string");
+ return Err(Error::msg("unexpected end of string"));
}
// we're now at the first backslash, don't include it in the output:
let mut was_backslash = true;
loop {
if i == data.len() {
- bail!("unexpected end of string");
+ return Err(Error::msg("unexpected end of string"));
}
match (data[i], mem::replace(&mut was_backslash, false)) {
(b'"', true) => out.push(b'"'),
(b'\\', true) => out.push(b'\\'),
(b'n', true) => out.push(b'\n'),
- (_, true) => bail!("unsupported escape sequence"),
+ (_, true) => return Err(Error::msg("unsupported escape sequence")),
(b'\\', false) => was_backslash = true,
(ch, false) => out.push(ch),
}
Ok(Cow::Owned(unsafe { String::from_utf8_unchecked(out) }))
}
+/// Counterpart to `parse_quoted_string`, only supporting the above-supported escape sequences.
+/// Returns `true`
+pub(crate) fn quote<T: fmt::Write>(s: &str, out: &mut T) -> fmt::Result {
+ for b in s.chars() {
+ match b {
+ '"' => out.write_str(r#"\""#)?,
+ '\\' => out.write_str(r#"\\"#)?,
+ '\n' => out.write_str(r#"\n"#)?,
+ b => out.write_char(b)?,
+ }
+ }
+ Ok(())
+}
+
/// Like `str::split_at` but with assumes `mid` points to an ASCII character and the 2nd slice
/// *excludes* `mid`.
fn ascii_split_around(s: &str, mid: usize) -> (&str, &str) {
.unwrap()
.is_err());
}
+
+/// A wrapper for a de/serializable type which is stored as a property string.
+#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd)]
+#[repr(transparent)]
+pub struct PropertyString<T>(T);
+
+impl<T> PropertyString<T> {
+ pub fn new(inner: T) -> Self {
+ Self(inner)
+ }
+
+ pub fn into_inner(self) -> T {
+ self.0
+ }
+}
+
+impl<T: Serialize> PropertyString<T> {
+ pub fn to_property_string(&self) -> Result<String, Error> {
+ print(&self.0)
+ }
+}
+
+impl<T> From<T> for PropertyString<T> {
+ fn from(inner: T) -> Self {
+ Self(inner)
+ }
+}
+
+impl<T> std::ops::Deref for PropertyString<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &self.0
+ }
+}
+
+impl<T> std::ops::DerefMut for PropertyString<T> {
+ fn deref_mut(&mut self) -> &mut T {
+ &mut self.0
+ }
+}
+
+impl<T> AsRef<T> for PropertyString<T> {
+ fn as_ref(&self) -> &T {
+ &self.0
+ }
+}
+
+impl<T> AsMut<T> for PropertyString<T> {
+ fn as_mut(&mut self) -> &mut T {
+ &mut self.0
+ }
+}
+
+impl<'de, T> Deserialize<'de> for PropertyString<T>
+where
+ T: Deserialize<'de> + ApiType,
+{
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ use std::marker::PhantomData;
+
+ struct V<T>(PhantomData<T>);
+
+ impl<'de, T> serde::de::Visitor<'de> for V<T>
+ where
+ T: Deserialize<'de> + ApiType,
+ {
+ type Value = T;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("a property string")
+ }
+
+ fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ self.visit_string(s.to_string())
+ }
+
+ fn visit_string<E>(self, s: String) -> Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ T::deserialize(crate::de::SchemaDeserializer::new(s, &T::API_SCHEMA))
+ .map_err(|err| E::custom(err.to_string()))
+ }
+
+ fn visit_borrowed_str<E>(self, s: &'de str) -> Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ T::deserialize(crate::de::SchemaDeserializer::new(s, &T::API_SCHEMA))
+ .map_err(|err| E::custom(err.to_string()))
+ }
+ }
+
+ deserializer.deserialize_string(V(PhantomData)).map(Self)
+ }
+}
+
+impl<T> std::str::FromStr for PropertyString<T>
+where
+ T: ApiType + for<'de> Deserialize<'de>,
+{
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result<Self, Error> {
+ T::deserialize(crate::de::SchemaDeserializer::new(s, &T::API_SCHEMA)).map(Self)
+ }
+}
+
+impl<T> Serialize for PropertyString<T>
+where
+ T: Serialize,
+{
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ use serde::ser::Error;
+
+ serializer.serialize_str(&print(&self.0).map_err(S::Error::custom)?)
+ }
+}
+
+/// Serialize a value as a property string.
+pub fn print<T: Serialize>(value: &T) -> Result<String, Error> {
+ value.serialize(crate::ser::PropertyStringSerializer::new(String::new()))
+}
+
+/// Deserialize a value from a property string.
+pub fn parse<T: ApiType>(value: &str) -> Result<T, Error>
+where
+ T: for<'de> Deserialize<'de>,
+{
+ parse_with_schema(value, &T::API_SCHEMA)
+}
+
+/// Deserialize a value from a property string.
+pub fn parse_with_schema<T>(value: &str, schema: &'static crate::Schema) -> Result<T, Error>
+where
+ T: for<'de> Deserialize<'de>,
+{
+ T::deserialize(crate::de::SchemaDeserializer::new(value, schema))
+}
+
+#[cfg(test)]
+mod test {
+ use serde::{Deserialize, Serialize};
+
+ use crate::schema::*;
+
+ impl ApiType for Object {
+ const API_SCHEMA: Schema = ObjectSchema::new(
+ "An object",
+ &[
+ // MUST BE SORTED
+ ("count", false, &IntegerSchema::new("name").schema()),
+ ("name", false, &StringSchema::new("name").schema()),
+ ("nested", true, &Nested::API_SCHEMA),
+ (
+ "optional",
+ true,
+ &BooleanSchema::new("an optional boolean").schema(),
+ ),
+ ],
+ )
+ .schema();
+ }
+
+ #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
+ pub struct Object {
+ name: String,
+ count: u32,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ optional: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ nested: Option<Nested>,
+ }
+
+ impl ApiType for Nested {
+ const API_SCHEMA: Schema = ObjectSchema::new(
+ "An object",
+ &[
+ // MUST BE SORTED
+ (
+ "count",
+ true,
+ &ArraySchema::new("count", &IntegerSchema::new("a value").schema()).schema(),
+ ),
+ ("name", false, &StringSchema::new("name").schema()),
+ ("third", true, &Third::API_SCHEMA),
+ ],
+ )
+ .schema();
+ }
+
+ #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
+ pub struct Nested {
+ name: String,
+
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ count: Vec<u32>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ third: Option<Third>,
+ }
+
+ impl ApiType for Third {
+ const API_SCHEMA: Schema = ObjectSchema::new(
+ "An object",
+ &[
+ // MUST BE SORTED
+ ("count", false, &IntegerSchema::new("name").schema()),
+ ("name", false, &StringSchema::new("name").schema()),
+ ],
+ )
+ .schema();
+ }
+
+ #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
+ pub struct Third {
+ name: String,
+ count: u32,
+ }
+
+ #[test]
+ fn test() -> Result<(), super::Error> {
+ let obj = Object {
+ name: "One \"Mo\\re\" Name".to_string(),
+ count: 12,
+ optional: Some(true),
+ nested: Some(Nested {
+ name: "a \"bobby\"".to_string(),
+ count: vec![22, 23, 24],
+ third: Some(Third {
+ name: "oh\\backslash".to_string(),
+ count: 37,
+ }),
+ }),
+ };
+
+ let s = super::print(&obj)?;
+
+ let deserialized: Object = super::parse(&s).expect("failed to parse property string");
+
+ assert_eq!(obj, deserialized, "deserialized does not equal original");
+
+ Ok(())
+ }
+}
Err(err) => self.push(prefix.to_string(), err),
}
}
+
+ pub(crate) fn from_list(error_list: Vec<(String, Error)>) -> Self {
+ Self { error_list }
+ }
}
impl fmt::Display for ParameterError {
Schema::Integer(self)
}
- fn check_constraints(&self, value: isize) -> Result<(), Error> {
+ pub fn check_constraints(&self, value: isize) -> Result<(), Error> {
if let Some(minimum) = self.minimum {
if value < minimum {
bail!(
Schema::Number(self)
}
- fn check_constraints(&self, value: f64) -> Result<(), Error> {
+ pub fn check_constraints(&self, value: f64) -> Result<(), Error> {
if let Some(minimum) = self.minimum {
if value < minimum {
bail!(
Schema::String(self)
}
- fn check_length(&self, length: usize) -> Result<(), Error> {
+ pub(crate) fn check_length(&self, length: usize) -> Result<(), Error> {
if let Some(min_length) = self.min_length {
if length < min_length {
bail!("value must be at least {} characters long", min_length);
Schema::Array(self)
}
- fn check_length(&self, length: usize) -> Result<(), Error> {
+ pub(crate) fn check_length(&self, length: usize) -> Result<(), Error> {
if let Some(min_length) = self.min_length {
if length < min_length {
bail!("array must contain at least {} elements", min_length);
fn lookup(&self, key: &str) -> Option<(bool, &Schema)>;
fn properties(&self) -> ObjectPropertyIterator;
fn additional_properties(&self) -> bool;
+ fn default_key(&self) -> Option<&'static str>;
/// Verify JSON value using an object schema.
fn verify_json(&self, data: &Value) -> Result<(), Error> {
fn additional_properties(&self) -> bool {
self.additional_properties
}
+
+ fn default_key(&self) -> Option<&'static str> {
+ self.default_key
+ }
}
impl ObjectSchemaType for AllOfSchema {
fn additional_properties(&self) -> bool {
true
}
+
+ fn default_key(&self) -> Option<&'static str> {
+ for schema in self.list {
+ let default_key = match schema {
+ Schema::Object(schema) => schema.default_key(),
+ Schema::AllOf(schema) => schema.default_key(),
+ _ => panic!("non-object-schema in `AllOfSchema`"),
+ };
+
+ if default_key.is_some() {
+ return default_key;
+ }
+ }
+
+ None
+ }
}
#[doc(hidden)]
ParameterSchema::AllOf(o) => o.additional_properties(),
}
}
+
+ fn default_key(&self) -> Option<&'static str> {
+ match self {
+ ParameterSchema::Object(o) => o.default_key(),
+ ParameterSchema::AllOf(o) => o.default_key(),
+ }
+ }
}
impl From<&'static ObjectSchema> for ParameterSchema {
--- /dev/null
+//! Property string serialization.
+
+use std::fmt;
+use std::mem;
+
+use serde::ser::{self, Serialize, Serializer};
+
+use crate::de::Error;
+
+impl serde::ser::Error for Error {
+ fn custom<T: fmt::Display>(msg: T) -> Self {
+ Self::msg(msg.to_string())
+ }
+}
+
+pub struct PropertyStringSerializer<T> {
+ inner: T,
+}
+
+impl<T> PropertyStringSerializer<T> {
+ pub fn new(inner: T) -> Self {
+ Self { inner }
+ }
+}
+
+macro_rules! not_an_object {
+ () => {};
+ ($name:ident($ty:ty) $($rest:tt)*) => {
+ fn $name(self, _v: $ty) -> Result<Self::Ok, Error> {
+ Err(Error::msg("property string serializer used with a non-object type"))
+ }
+
+ not_an_object! { $($rest)* }
+ };
+ ($name:ident($($args:tt)*) $($rest:tt)*) => {
+ fn $name(self, $($args)*) -> Result<Self::Ok, Error> {
+ Err(Error::msg("property string serializer used with a non-object type"))
+ }
+
+ not_an_object! { $($rest)* }
+ };
+ ($name:ident<($($gen:tt)*)>($($args:tt)*) $($rest:tt)*) => {
+ fn $name<$($gen)*>(self, $($args)*) -> Result<Self::Ok, Error> {
+ Err(Error::msg("property string serializer used with a non-object type"))
+ }
+
+ not_an_object! { $($rest)* }
+ };
+}
+
+macro_rules! same_impl {
+ (as impl<T: fmt::Write> _ for $struct:ident<T> { $($code:tt)* }) => {};
+ (
+ ser::$trait:ident
+ $(ser::$more_traits:ident)*
+ as impl<T: fmt::Write> _ for $struct:ident<T> { $($code:tt)* }
+ ) => {
+ impl<T: fmt::Write> ser::$trait for $struct<T> { $($code)* }
+ same_impl! {
+ $(ser::$more_traits)*
+ as impl<T: fmt::Write> _ for $struct<T> { $($code)* }
+ }
+ }
+}
+
+impl<T: fmt::Write> Serializer for PropertyStringSerializer<T> {
+ type Ok = T;
+ type Error = Error;
+
+ type SerializeSeq = SerializeSeq<T>;
+ type SerializeTuple = SerializeSeq<T>;
+ type SerializeTupleStruct = SerializeSeq<T>;
+ type SerializeTupleVariant = SerializeSeq<T>;
+ type SerializeMap = SerializeStruct<T>;
+ type SerializeStruct = SerializeStruct<T>;
+ type SerializeStructVariant = SerializeStruct<T>;
+
+ fn is_human_readable(&self) -> bool {
+ true
+ }
+
+ not_an_object! {
+ serialize_bool(bool)
+ serialize_i8(i8)
+ serialize_i16(i16)
+ serialize_i32(i32)
+ serialize_i64(i64)
+ serialize_u8(u8)
+ serialize_u16(u16)
+ serialize_u32(u32)
+ serialize_u64(u64)
+ serialize_f32(f32)
+ serialize_f64(f64)
+ serialize_char(char)
+ serialize_str(&str)
+ serialize_bytes(&[u8])
+ serialize_none()
+ serialize_some<(V: Serialize + ?Sized)>(_value: &V)
+ serialize_unit()
+ serialize_unit_struct(&'static str)
+ serialize_unit_variant(_name: &'static str, _index: u32, _var: &'static str)
+ }
+
+ fn serialize_newtype_struct<V>(self, _name: &'static str, value: &V) -> Result<T, Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ value.serialize(self)
+ }
+
+ fn serialize_newtype_variant<V>(
+ self,
+ name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _value: &V,
+ ) -> Result<T, Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ Err(Error::msg(format!(
+ "cannot serialize enum {name:?} with newtype variants"
+ )))
+ }
+
+ fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Error> {
+ Ok(SerializeSeq::new(self.inner))
+ }
+
+ fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Error> {
+ Ok(SerializeSeq::new(self.inner))
+ }
+
+ fn serialize_tuple_struct(
+ self,
+ _name: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeTupleStruct, Error> {
+ Ok(SerializeSeq::new(self.inner))
+ }
+
+ fn serialize_tuple_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeTupleVariant, Error> {
+ Ok(SerializeSeq::new(self.inner))
+ }
+
+ fn serialize_struct(
+ self,
+ _name: &'static str,
+ _len: usize,
+ ) -> Result<SerializeStruct<T>, Error> {
+ Ok(SerializeStruct::new(self.inner))
+ }
+
+ fn serialize_struct_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _len: usize,
+ ) -> Result<SerializeStruct<T>, Error> {
+ Ok(SerializeStruct::new(self.inner))
+ }
+
+ fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Error> {
+ Ok(SerializeStruct::new(self.inner))
+ }
+}
+
+pub struct SerializeStruct<T> {
+ inner: Option<T>,
+ comma: bool,
+}
+
+impl<T: fmt::Write> SerializeStruct<T> {
+ fn new(inner: T) -> Self {
+ Self {
+ inner: Some(inner),
+ comma: false,
+ }
+ }
+
+ fn field<V>(&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<T, Error> {
+ Ok(self.inner.take().unwrap())
+ }
+}
+
+same_impl! {
+ ser::SerializeStruct
+ ser::SerializeStructVariant
+ as impl<T: fmt::Write> _ for SerializeStruct<T> {
+ type Ok = T;
+ type Error = Error;
+
+ fn serialize_field<V>(&mut self, key: &'static str, value: &V) -> Result<(), Self::Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ self.field(key, value)
+ }
+
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ self.finish()
+ }
+ }
+}
+
+impl<T: fmt::Write> ser::SerializeMap for SerializeStruct<T> {
+ type Ok = T;
+ type Error = Error;
+
+ fn serialize_key<K>(&mut self, key: &K) -> Result<(), Self::Error>
+ 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(())
+ }
+
+ fn serialize_value<V>(&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(())
+ }
+
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ self.finish()
+ }
+}
+
+pub struct SerializeSeq<T: fmt::Write> {
+ inner: Option<T>,
+ comma: bool,
+}
+
+impl<T: fmt::Write> SerializeSeq<T> {
+ fn new(inner: T) -> Self {
+ Self {
+ inner: Some(inner),
+ comma: false,
+ }
+ }
+
+ fn element<V>(&mut self, 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(',')?;
+ }
+
+ inner = value.serialize(ElementSerializer::new(inner))?;
+ self.inner = Some(inner);
+ Ok(())
+ }
+
+ fn finish(mut self) -> Result<T, Error> {
+ Ok(self.inner.take().unwrap())
+ }
+}
+
+same_impl! {
+ ser::SerializeSeq
+ ser::SerializeTuple
+ as impl<T: fmt::Write> _ for SerializeSeq<T> {
+ type Ok = T;
+ type Error = Error;
+
+ fn serialize_element<V>(&mut self, value: &V) -> Result<(), Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ self.element(value)
+ }
+
+ fn end(self) -> Result<T, Error> {
+ self.finish()
+ }
+ }
+}
+
+same_impl! {
+ ser::SerializeTupleStruct
+ ser::SerializeTupleVariant
+ as impl<T: fmt::Write> _ for SerializeSeq<T> {
+ type Ok = T;
+ type Error = Error;
+
+ fn serialize_field<V>(&mut self, value: &V) -> Result<(), Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ self.element(value)
+ }
+
+ fn end(self) -> Result<T, Error> {
+ self.finish()
+ }
+ }
+}
+
+pub struct ElementSerializer<T> {
+ inner: T,
+}
+
+impl<T> ElementSerializer<T> {
+ fn new(inner: T) -> Self {
+ Self { inner }
+ }
+}
+
+impl<T: fmt::Write> ElementSerializer<T> {
+ fn serialize_with_display<V: fmt::Display>(mut self, v: V) -> Result<T, Error> {
+ write!(self.inner, "{v}")
+ .map_err(|err| Error::msg(format!("failed to write string: {err}")))?;
+ Ok(self.inner)
+ }
+}
+
+macro_rules! forward_to_display {
+ () => {};
+ ($name:ident($ty:ty) $($rest:tt)*) => {
+ fn $name(self, v: $ty) -> Result<Self::Ok, Error> {
+ self.serialize_with_display(v)
+ }
+
+ forward_to_display! { $($rest)* }
+ };
+}
+
+impl<T: fmt::Write> Serializer for ElementSerializer<T> {
+ type Ok = T;
+ type Error = Error;
+
+ type SerializeSeq = ElementSerializeSeq<T>;
+ type SerializeTuple = ElementSerializeSeq<T>;
+ type SerializeTupleStruct = ElementSerializeSeq<T>;
+ type SerializeTupleVariant = ElementSerializeSeq<T>;
+ type SerializeMap = ElementSerializeStruct<T>;
+ type SerializeStruct = ElementSerializeStruct<T>;
+ type SerializeStructVariant = ElementSerializeStruct<T>;
+
+ fn is_human_readable(&self) -> bool {
+ true
+ }
+
+ forward_to_display! {
+ serialize_bool(bool)
+ serialize_i8(i8)
+ serialize_i16(i16)
+ serialize_i32(i32)
+ serialize_i64(i64)
+ serialize_u8(u8)
+ serialize_u16(u16)
+ serialize_u32(u32)
+ serialize_u64(u64)
+ serialize_f32(f32)
+ serialize_f64(f64)
+ serialize_char(char)
+ }
+
+ fn serialize_str(mut self, v: &str) -> Result<Self::Ok, Error> {
+ if v.contains(&['"', '\\', '\n']) {
+ self.inner.write_char('"')?;
+ crate::property_string::quote(v, &mut self.inner)?;
+ self.inner.write_char('"')?;
+ } else {
+ self.inner.write_str(v)?;
+ }
+ Ok(self.inner)
+ }
+
+ fn serialize_bytes(self, _: &[u8]) -> Result<Self::Ok, Error> {
+ Err(Error::msg(
+ "raw byte value not supported in property string",
+ ))
+ }
+
+ fn serialize_none(self) -> Result<Self::Ok, Error> {
+ Err(Error::msg("tried to serialize 'None' value"))
+ }
+
+ fn serialize_some<V: Serialize + ?Sized>(self, v: &V) -> Result<Self::Ok, Error> {
+ v.serialize(self)
+ }
+
+ fn serialize_unit(self) -> Result<Self::Ok, Error> {
+ Err(Error::msg("tried to serialize a unit value"))
+ }
+
+ fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Error> {
+ Err(Error::msg(format!(
+ "tried to serialize a unit value (struct {name})"
+ )))
+ }
+
+ fn serialize_unit_variant(
+ self,
+ name: &'static str,
+ _index: u32,
+ variant: &'static str,
+ ) -> Result<Self::Ok, Error> {
+ Err(Error::msg(format!(
+ "tried to serialize a unit variant ({name}::{variant})"
+ )))
+ }
+
+ fn serialize_newtype_struct<V>(self, _name: &'static str, value: &V) -> Result<T, Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ value.serialize(self)
+ }
+
+ fn serialize_newtype_variant<V>(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ value: &V,
+ ) -> Result<T, Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ value.serialize(self)
+ }
+
+ fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Error> {
+ Ok(ElementSerializeSeq::new(self.inner))
+ }
+
+ fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Error> {
+ Ok(ElementSerializeSeq::new(self.inner))
+ }
+
+ fn serialize_tuple_struct(
+ self,
+ _name: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeTupleStruct, Error> {
+ Ok(ElementSerializeSeq::new(self.inner))
+ }
+
+ fn serialize_tuple_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeTupleVariant, Error> {
+ Ok(ElementSerializeSeq::new(self.inner))
+ }
+
+ fn serialize_struct(
+ self,
+ _name: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeStruct, Error> {
+ Ok(ElementSerializeStruct::new(self.inner))
+ }
+
+ fn serialize_struct_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeStructVariant, Error> {
+ Ok(ElementSerializeStruct::new(self.inner))
+ }
+
+ fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Error> {
+ Ok(ElementSerializeStruct::new(self.inner))
+ }
+}
+
+pub struct ElementSerializeStruct<T> {
+ output: T,
+ inner: SerializeStruct<String>,
+}
+
+impl<T: fmt::Write> ElementSerializeStruct<T> {
+ fn new(inner: T) -> Self {
+ Self {
+ output: inner,
+ inner: SerializeStruct::new(String::new()),
+ }
+ }
+
+ fn finish(mut self) -> Result<T, Error> {
+ let value = self.inner.finish()?;
+ self.output.write_char('"')?;
+ crate::property_string::quote(&value, &mut self.output)?;
+ self.output.write_char('"')?;
+ Ok(self.output)
+ }
+}
+
+same_impl! {
+ ser::SerializeStruct
+ ser::SerializeStructVariant
+ as impl<T: fmt::Write> _ for ElementSerializeStruct<T> {
+ type Ok = T;
+ type Error = Error;
+
+ fn serialize_field<V>(&mut self, key: &'static str, value: &V) -> Result<(), Self::Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ self.inner.field(key, value)
+ }
+
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ self.finish()
+ }
+ }
+}
+
+impl<T: fmt::Write> ser::SerializeMap for ElementSerializeStruct<T> {
+ type Ok = T;
+ type Error = Error;
+
+ fn serialize_key<K>(&mut self, key: &K) -> Result<(), Self::Error>
+ where
+ K: Serialize + ?Sized,
+ {
+ self.inner.serialize_key(key)
+ }
+
+ fn serialize_value<V>(&mut self, value: &V) -> Result<(), Self::Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ self.inner.serialize_value(value)
+ }
+
+ fn end(self) -> Result<Self::Ok, Self::Error> {
+ self.finish()
+ }
+}
+
+pub struct ElementSerializeSeq<T: fmt::Write> {
+ output: T,
+ inner: SerializeSeq<String>,
+}
+
+impl<T: fmt::Write> ElementSerializeSeq<T> {
+ fn new(inner: T) -> Self {
+ Self {
+ output: inner,
+ inner: SerializeSeq::new(String::new()),
+ }
+ }
+
+ fn finish(mut self) -> Result<T, Error> {
+ let value = self.inner.finish()?;
+ if value.contains(&[',', ';', ' ', '"', '\\', '\n']) {
+ self.output.write_char('"')?;
+ crate::property_string::quote(&value, &mut self.output)?;
+ self.output.write_char('"')?;
+ } else {
+ self.output.write_str(&value)?;
+ }
+ Ok(self.output)
+ }
+}
+
+same_impl! {
+ ser::SerializeSeq
+ ser::SerializeTuple
+ as impl<T: fmt::Write> _ for ElementSerializeSeq<T> {
+ type Ok = T;
+ type Error = Error;
+
+ fn serialize_element<V>(&mut self, value: &V) -> Result<(), Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ self.inner.serialize_element(value)
+ }
+
+ fn end(self) -> Result<T, Error> {
+ self.finish()
+ }
+ }
+}
+
+same_impl! {
+ ser::SerializeTupleStruct
+ ser::SerializeTupleVariant
+ as impl<T: fmt::Write> _ for ElementSerializeSeq<T> {
+ type Ok = T;
+ type Error = Error;
+
+ fn serialize_field<V>(&mut self, value: &V) -> Result<(), Error>
+ where
+ V: Serialize + ?Sized,
+ {
+ self.inner.element(value)
+ }
+
+ fn end(self) -> Result<T, Error> {
+ self.finish()
+ }
+ }
+}