diff --git a/libs/deer/desert/src/array.rs b/libs/deer/desert/src/array.rs index 23deb297846..ca15c135925 100644 --- a/libs/deer/desert/src/array.rs +++ b/libs/deer/desert/src/array.rs @@ -9,6 +9,7 @@ use error_stack::{Report, Result, ResultExt}; use crate::{ deserializer::{Deserializer, DeserializerNone}, + skip::skip_tokens, token::Token, }; @@ -29,31 +30,6 @@ impl<'a, 'b, 'de> ArrayAccess<'a, 'b, 'de> { remaining: None, } } - - fn scan_end(&self) -> Option { - let mut objects: usize = 0; - let mut arrays: usize = 0; - - let mut n = 0; - - loop { - let token = self.deserializer.peek_n(n)?; - - match token { - Token::Array { .. } => arrays += 1, - Token::ArrayEnd if arrays == 0 && objects == 0 => { - // we're at the outer layer, meaning we can know where we end - return Some(n); - } - Token::ArrayEnd => arrays = arrays.saturating_sub(1), - Token::Object { .. } => objects += 1, - Token::ObjectEnd => objects = objects.saturating_sub(1), - _ => {} - } - - n += 1; - } - } } impl<'de> deer::ArrayAccess<'de> for ArrayAccess<'_, '_, 'de> { @@ -133,10 +109,7 @@ impl<'de> deer::ArrayAccess<'de> for ArrayAccess<'_, '_, 'de> { }; // bump until the very end, which ensures that deserialize calls after this might succeed! - let bump = self - .scan_end() - .map_or_else(|| self.deserializer.tape().remaining(), |index| index + 1); - self.deserializer.tape_mut().bump_n(bump); + skip_tokens(self.deserializer, &Token::Array { length: None }); if let Some(remaining) = self.remaining { if remaining > 0 { diff --git a/libs/deer/desert/src/deserializer.rs b/libs/deer/desert/src/deserializer.rs index a5bcf054828..649f501fb70 100644 --- a/libs/deer/desert/src/deserializer.rs +++ b/libs/deer/desert/src/deserializer.rs @@ -1,9 +1,9 @@ use alloc::borrow::ToOwned; use deer::{ - error::{DeserializerError, TypeError, Variant}, + error::{DeserializerError, ExpectedType, MissingError, ReceivedType, TypeError, Variant}, value::NoneDeserializer, - Context, EnumVisitor, OptionalVisitor, Visitor, + Context, EnumVisitor, OptionalVisitor, StructVisitor, Visitor, }; use error_stack::{Report, Result, ResultExt}; @@ -134,6 +134,26 @@ impl<'a, 'de> deer::Deserializer<'de> for &mut Deserializer<'a, 'de> { Ok(value) } + + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>, + { + let token = self.next(); + + match token { + Token::Array { length } => visitor + .visit_array(ArrayAccess::new(self, length)) + .change_context(DeserializerError), + Token::Object { length } => visitor + .visit_object(ObjectAccess::new(self, length)) + .change_context(DeserializerError), + other => Err(Report::new(TypeError.into_error()) + .attach(ExpectedType::new(visitor.expecting())) + .attach(ReceivedType::new(other.schema())) + .change_context(DeserializerError)), + } + } } impl<'a, 'de> Deserializer<'a, 'de> { @@ -157,10 +177,6 @@ impl<'a, 'de> Deserializer<'a, 'de> { self.tape.next().expect("should have token to deserialize") } - pub(crate) const fn tape(&self) -> &Tape<'de> { - &self.tape - } - pub(crate) fn tape_mut(&mut self) -> &mut Tape<'de> { &mut self.tape } @@ -224,4 +240,13 @@ impl<'de> deer::Deserializer<'de> for DeserializerNone<'_> { .visit_value(discriminant, self) .change_context(DeserializerError) } + + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>, + { + Err(Report::new(MissingError.into_error()) + .attach(ExpectedType::new(visitor.expecting())) + .change_context(DeserializerError)) + } } diff --git a/libs/deer/desert/src/lib.rs b/libs/deer/desert/src/lib.rs index d6c922615bf..ecf4306fb4f 100644 --- a/libs/deer/desert/src/lib.rs +++ b/libs/deer/desert/src/lib.rs @@ -7,6 +7,7 @@ mod assert; mod deserializer; pub mod error; pub(crate) mod object; +mod skip; pub(crate) mod tape; mod token; diff --git a/libs/deer/desert/src/object.rs b/libs/deer/desert/src/object.rs index dea443e2cec..c8b1a9278b1 100644 --- a/libs/deer/desert/src/object.rs +++ b/libs/deer/desert/src/object.rs @@ -9,6 +9,7 @@ use error_stack::{Report, Result, ResultExt}; use crate::{ deserializer::{Deserializer, DeserializerNone}, + skip::skip_tokens, token::Token, }; @@ -29,31 +30,6 @@ impl<'a, 'b, 'de: 'a> ObjectAccess<'a, 'b, 'de> { consumed: 0, } } - - fn scan_end(&self) -> Option { - let mut objects: usize = 0; - let mut arrays: usize = 0; - - let mut n = 0; - - loop { - let token = self.deserializer.peek_n(n)?; - - match token { - Token::Array { .. } => arrays += 1, - Token::ArrayEnd => arrays = arrays.saturating_sub(1), - Token::Object { .. } => objects += 1, - Token::ObjectEnd if arrays == 0 && objects == 0 => { - // we're at the outer layer, meaning we can know where we end - return Some(n); - } - Token::ObjectEnd => objects = objects.saturating_sub(1), - _ => {} - } - - n += 1; - } - } } impl<'de> deer::ObjectAccess<'de> for ObjectAccess<'_, '_, 'de> { @@ -115,6 +91,12 @@ impl<'de> deer::ObjectAccess<'de> for ObjectAccess<'_, '_, 'de> { } else { let key = access.visit_key(&mut *self.deserializer); + if key.is_err() { + // the key is an error, we need to swallow the value + let next = self.deserializer.next(); + skip_tokens(self.deserializer, &next); + } + key.and_then(|key| access.visit_value(key, &mut *self.deserializer)) }; @@ -141,11 +123,7 @@ impl<'de> deer::ObjectAccess<'de> for ObjectAccess<'_, '_, 'de> { }; // bump until the very end, which ensures that deserialize calls after this might succeed! - let bump = self - .scan_end() - .unwrap_or_else(|| self.deserializer.tape().remaining()); - - self.deserializer.tape_mut().bump_n(bump + 1); + skip_tokens(self.deserializer, &Token::Object { length: None }); if let Some(remaining) = self.remaining { if remaining > 0 { diff --git a/libs/deer/desert/src/skip.rs b/libs/deer/desert/src/skip.rs new file mode 100644 index 00000000000..f6e1b36057c --- /dev/null +++ b/libs/deer/desert/src/skip.rs @@ -0,0 +1,45 @@ +use crate::{deserializer::Deserializer, Token}; + +fn scan_object(deserializer: &Deserializer, stop: &Token) -> usize { + let mut objects: usize = 0; + let mut arrays: usize = 0; + + let mut n = 0; + + loop { + let Some(token) = deserializer.peek_n(n) else { + // we're at the end + return n; + }; + + if token == *stop && arrays == 0 && objects == 0 { + // we're at the outer layer, meaning we can know where we end + // need to increment by one as we want to also skip the ObjectEnd + return n + 1; + } + + match token { + Token::Array { .. } => arrays += 1, + Token::ArrayEnd => arrays = arrays.saturating_sub(1), + Token::Object { .. } => objects += 1, + Token::ObjectEnd => objects = objects.saturating_sub(1), + _ => {} + } + + n += 1; + } +} + +/// Skips all tokens required for the start token, be aware that the start token should no longer be +/// on the tape +pub(crate) fn skip_tokens(deserializer: &mut Deserializer, start: &Token) { + let n = match start { + Token::Array { .. } => scan_object(&*deserializer, &Token::ArrayEnd), + Token::Object { .. } => scan_object(&*deserializer, &Token::ObjectEnd), + _ => 0, + }; + + if n > 0 { + deserializer.tape_mut().bump_n(n); + } +} diff --git a/libs/deer/desert/src/token.rs b/libs/deer/desert/src/token.rs index 4398810c05b..aa6c088babd 100644 --- a/libs/deer/desert/src/token.rs +++ b/libs/deer/desert/src/token.rs @@ -1,6 +1,6 @@ use core::fmt::{Debug, Display, Formatter}; -use deer::Number; +use deer::{Deserialize, Document, Number, Reflection, Schema}; // TODO: test // TODO: this should be `Copy`, but `Number` has no &'static constructor @@ -181,3 +181,41 @@ impl Display for Token { Debug::fmt(self, f) } } + +struct AnyArray; + +impl Reflection for AnyArray { + fn schema(_: &mut Document) -> Schema { + Schema::new("array") + } +} + +struct AnyObject; + +impl Reflection for AnyObject { + fn schema(_: &mut Document) -> Schema { + Schema::new("object") + } +} + +impl Token { + pub(crate) fn schema(&self) -> Document { + match self { + Self::Bool(_) => Document::new::(), + Self::Number(_) => Document::new::(), + Self::U128(_) => Document::new::(), + Self::I128(_) => Document::new::(), + Self::Char(_) => Document::new::(), + Self::Str(_) | Self::BorrowedStr(_) | Self::String(_) => Document::new::(), + Self::Bytes(_) | Self::BorrowedBytes(_) | Self::BytesBuf(_) => Document::new::<[u8]>(), + Self::Array { .. } | Self::ArrayEnd => Document::new::(), + Self::Object { .. } | Self::ObjectEnd => Document::new::(), + Self::Null => Document::new::<<() as Deserialize>::Reflection>(), + } + } +} + +// TODO: maybe number +// TODO: IdentifierVisitor (u8, u64, str, borrowed_str, string, +// bytes, bytes_buf, borrowed_bytes) +// TODO: test diff --git a/libs/deer/json/src/lib.rs b/libs/deer/json/src/lib.rs index 8a0d089a93b..e1d01a9cf06 100644 --- a/libs/deer/json/src/lib.rs +++ b/libs/deer/json/src/lib.rs @@ -19,13 +19,13 @@ use std::any::Demand; use deer::{ error::{ ArrayAccessError, ArrayLengthError, BoundedContractViolationError, DeserializeError, - DeserializerError, ExpectedLength, ExpectedType, ObjectAccessError, ObjectItemsExtraError, - ObjectLengthError, ReceivedKey, ReceivedLength, ReceivedType, ReceivedValue, TypeError, - ValueError, Variant, + DeserializerError, ExpectedLength, ExpectedType, MissingError, ObjectAccessError, + ObjectItemsExtraError, ObjectLengthError, ReceivedKey, ReceivedLength, ReceivedType, + ReceivedValue, TypeError, ValueError, Variant, }, value::NoneDeserializer, Context, Deserialize, DeserializeOwned, Document, EnumVisitor, FieldVisitor, OptionalVisitor, - Reflection, Schema, Visitor, + Reflection, Schema, StructVisitor, Visitor, }; use error_stack::{IntoReport, Report, Result, ResultExt}; use serde_json::{Map, Value}; @@ -427,6 +427,25 @@ impl<'a, 'de> deer::Deserializer<'de> for Deserializer<'a> { .change_context(DeserializerError) } } + + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>, + { + match self.value { + None => Err(Report::new(MissingError.into_error()) + .attach(ExpectedType::new(visitor.expecting())) + .change_context(DeserializerError)), + Some(Value::Object(object)) => visitor + .visit_object(ObjectAccess::new(object, self.context)) + .change_context(DeserializerError), + // we do not allow arrays as struct, only objects are allowed for structs + Some(value) => Err(Report::new(TypeError.into_error()) + .attach(ExpectedType::new(visitor.expecting())) + .attach(ReceivedType::new(into_document(&value))) + .change_context(DeserializerError)), + } + } } #[must_use] diff --git a/libs/deer/src/lib.rs b/libs/deer/src/lib.rs index 91d5ba52e42..604aff10747 100644 --- a/libs/deer/src/lib.rs +++ b/libs/deer/src/lib.rs @@ -367,6 +367,36 @@ pub trait OptionalVisitor<'de>: Sized { } } +#[allow(unused_variables)] +pub trait StructVisitor<'de>: Sized { + type Value; + + fn expecting(&self) -> Document; + + // visit_none and visit_null are not implemented, as they can be used more expressively using + // `OptionalVisitor` + + fn visit_array(self, array: A) -> Result + where + A: ArrayAccess<'de>, + { + Err(Report::new(TypeError.into_error()) + .attach(ReceivedType::new(visitor::ArraySchema::document())) + .attach(ExpectedType::new(self.expecting())) + .change_context(VisitorError)) + } + + fn visit_object(self, object: A) -> Result + where + A: ObjectAccess<'de>, + { + Err(Report::new(TypeError.into_error()) + .attach(ReceivedType::new(visitor::ObjectSchema::document())) + .attach(ExpectedType::new(self.expecting())) + .change_context(VisitorError)) + } +} + // internal visitor, which is used during the default implementation of the `deserialize_i*` and // `deserialize_u*` methods. struct NumberVisitor(PhantomData *const T>); @@ -602,6 +632,10 @@ pub trait Deserializer<'de>: Sized { where V: EnumVisitor<'de>; + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>; + derive_from_number![ deserialize_i8(to_i8: i8) -> visit_i8, deserialize_i16(to_i16: i16) -> visit_i16, diff --git a/libs/deer/src/value.rs b/libs/deer/src/value.rs index d1067afd423..6f03c1905b6 100644 --- a/libs/deer/src/value.rs +++ b/libs/deer/src/value.rs @@ -5,12 +5,14 @@ mod string; pub use array::ArrayAccessDeserializer; pub use bytes::{BorrowedBytesDeserializer, BytesBufferDeserializer, BytesDeserializer}; -use error_stack::{Result, ResultExt}; +use error_stack::{Report, Result, ResultExt}; pub use object::ObjectAccessDeserializer; pub use string::{BorrowedStrDeserializer, StrDeserializer, StringDeserializer}; use crate::{ - error::DeserializerError, Context, Deserializer, EnumVisitor, Number, OptionalVisitor, Visitor, + error::{DeserializerError, ExpectedType, TypeError, Variant}, + Context, Deserialize, Deserializer, EnumVisitor, Number, OptionalVisitor, StructVisitor, + Visitor, }; macro_rules! impl_owned { @@ -75,6 +77,20 @@ macro_rules! impl_owned { { $crate::value::EnumUnitDeserializer::new(self.context, self).deserialize_enum(visitor) } + + fn deserialize_struct(self, visitor: V) -> error_stack::Result + where + V: StructVisitor<'de> + { + Err( + Report::new(TypeError.into_error()) + .attach(ExpectedType::new(visitor.expecting())) + // TODO: enable once String and Vec have reflection + // (or we rework this macro c:) + // .attach(ReceivedType::new(<$ty>::reflection())) + .change_context(DeserializerError) + ) + } } impl<'de> IntoDeserializer<'de> for $ty { @@ -105,6 +121,8 @@ macro_rules! impl_owned { use impl_owned; +use crate::error::{MissingError, ReceivedType}; + pub trait IntoDeserializer<'de> { type Deserializer<'a>: Deserializer<'de> where @@ -205,6 +223,15 @@ impl<'de> Deserializer<'de> for NoneDeserializer<'_> { .visit_value(discriminant, self) .change_context(DeserializerError) } + + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>, + { + Err(Report::new(MissingError.into_error()) + .attach(ExpectedType::new(visitor.expecting())) + .change_context(DeserializerError)) + } } #[derive(Debug, Copy, Clone)] @@ -256,6 +283,16 @@ impl<'de> Deserializer<'de> for NullDeserializer<'_> { { EnumUnitDeserializer::new(self.context, self).deserialize_enum(visitor) } + + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>, + { + Err(Report::new(TypeError.into_error()) + .attach(ExpectedType::new(visitor.expecting())) + .attach(ReceivedType::new(<()>::reflection())) + .change_context(DeserializerError)) + } } impl_owned!(bool, BoolDeserializer, visit_bool); diff --git a/libs/deer/src/value/array.rs b/libs/deer/src/value/array.rs index bf4fe2c8400..9b4961ffe9b 100644 --- a/libs/deer/src/value/array.rs +++ b/libs/deer/src/value/array.rs @@ -2,7 +2,7 @@ use error_stack::{Result, ResultExt}; use crate::{ error::DeserializerError, value::EnumUnitDeserializer, ArrayAccess, Context, Deserializer, - EnumVisitor, OptionalVisitor, Visitor, + EnumVisitor, OptionalVisitor, StructVisitor, Visitor, }; // TODO: SliceDeserializer/IteratorDeserializer @@ -62,4 +62,13 @@ where { EnumUnitDeserializer::new(self.context, self).deserialize_enum(visitor) } + + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>, + { + visitor + .visit_array(self.value) + .change_context(DeserializerError) + } } diff --git a/libs/deer/src/value/bytes.rs b/libs/deer/src/value/bytes.rs index a5da5704e7a..08dfd1adcdb 100644 --- a/libs/deer/src/value/bytes.rs +++ b/libs/deer/src/value/bytes.rs @@ -1,11 +1,11 @@ use alloc::vec::Vec; -use error_stack::{Result, ResultExt}; +use error_stack::{Report, Result, ResultExt}; use crate::{ - error::DeserializerError, + error::{DeserializerError, ExpectedType, ReceivedType, TypeError, Variant}, value::{impl_owned, EnumUnitDeserializer, IntoDeserializer}, - Context, Deserializer, EnumVisitor, OptionalVisitor, Visitor, + Context, Deserializer, EnumVisitor, OptionalVisitor, Reflection, StructVisitor, Visitor, }; #[derive(Debug, Copy, Clone)] @@ -38,7 +38,7 @@ impl<'de> Deserializer<'de> for BytesDeserializer<'_, '_> { self.context } - fn deserialize_any(self, visitor: V) -> error_stack::Result + fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de>, { @@ -47,7 +47,7 @@ impl<'de> Deserializer<'de> for BytesDeserializer<'_, '_> { .change_context(DeserializerError) } - fn deserialize_optional(self, visitor: V) -> error_stack::Result + fn deserialize_optional(self, visitor: V) -> Result where V: OptionalVisitor<'de>, { @@ -60,6 +60,16 @@ impl<'de> Deserializer<'de> for BytesDeserializer<'_, '_> { { EnumUnitDeserializer::new(self.context, self).deserialize_enum(visitor) } + + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>, + { + Err(Report::new(TypeError.into_error()) + .attach(ReceivedType::new(<[u8]>::document())) + .attach(ExpectedType::new(visitor.expecting())) + .change_context(DeserializerError)) + } } impl<'de, 'b> IntoDeserializer<'de> for &'b [u8] { @@ -103,7 +113,7 @@ impl<'de> Deserializer<'de> for BorrowedBytesDeserializer<'_, 'de> { self.context } - fn deserialize_any(self, visitor: V) -> error_stack::Result + fn deserialize_any(self, visitor: V) -> Result where V: Visitor<'de>, { @@ -112,7 +122,7 @@ impl<'de> Deserializer<'de> for BorrowedBytesDeserializer<'_, 'de> { .change_context(DeserializerError) } - fn deserialize_optional(self, visitor: V) -> error_stack::Result + fn deserialize_optional(self, visitor: V) -> Result where V: OptionalVisitor<'de>, { @@ -125,6 +135,16 @@ impl<'de> Deserializer<'de> for BorrowedBytesDeserializer<'_, 'de> { { EnumUnitDeserializer::new(self.context, self).deserialize_enum(visitor) } + + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>, + { + Err(Report::new(TypeError.into_error()) + .attach(ReceivedType::new(<[u8]>::document())) + .attach(ExpectedType::new(visitor.expecting())) + .change_context(DeserializerError)) + } } impl_owned!(!copy: Vec, BytesBufferDeserializer, visit_bytes_buffer); diff --git a/libs/deer/src/value/object.rs b/libs/deer/src/value/object.rs index a93b9d752cc..79d23991bfd 100644 --- a/libs/deer/src/value/object.rs +++ b/libs/deer/src/value/object.rs @@ -4,7 +4,8 @@ use crate::{ error::{ DeserializerError, ExpectedLength, ObjectLengthError, ReceivedLength, Variant, VisitorError, }, - Context, Deserializer, EnumVisitor, FieldVisitor, ObjectAccess, OptionalVisitor, Visitor, + Context, Deserializer, EnumVisitor, FieldVisitor, ObjectAccess, OptionalVisitor, StructVisitor, + Visitor, }; // TODO: MapDeserializer/IteratorDeserializer @@ -109,4 +110,13 @@ where self.value.end().change_context(DeserializerError)?; value.change_context(DeserializerError) } + + fn deserialize_struct(self, visitor: V) -> Result + where + V: StructVisitor<'de>, + { + visitor + .visit_object(self.value) + .change_context(DeserializerError) + } } diff --git a/libs/deer/src/value/string.rs b/libs/deer/src/value/string.rs index bc30788a1cd..3947007c811 100644 --- a/libs/deer/src/value/string.rs +++ b/libs/deer/src/value/string.rs @@ -1,11 +1,11 @@ use alloc::string::String; -use error_stack::ResultExt; +use error_stack::{Report, ResultExt}; use crate::{ - error::DeserializerError, + error::{DeserializerError, ExpectedType, ReceivedType, TypeError, Variant}, value::{impl_owned, EnumUnitDeserializer, IntoDeserializer, NoneDeserializer}, - Context, Deserializer, EnumVisitor, OptionalVisitor, Visitor, + Context, Deserializer, EnumVisitor, OptionalVisitor, Reflection, StructVisitor, Visitor, }; #[derive(Debug, Copy, Clone)] @@ -68,6 +68,16 @@ impl<'de> Deserializer<'de> for StrDeserializer<'_, '_> { .visit_value(discriminant, NoneDeserializer::new(context)) .change_context(DeserializerError) } + + fn deserialize_struct(self, visitor: V) -> error_stack::Result + where + V: StructVisitor<'de>, + { + Err(Report::new(TypeError.into_error()) + .attach(ReceivedType::new(str::document())) + .attach(ExpectedType::new(visitor.expecting())) + .change_context(DeserializerError)) + } } impl<'de, 'b> IntoDeserializer<'de> for &'b str { @@ -133,6 +143,16 @@ impl<'de> Deserializer<'de> for BorrowedStrDeserializer<'_, 'de> { { EnumUnitDeserializer::new(self.context, self).deserialize_enum(visitor) } + + fn deserialize_struct(self, visitor: V) -> error_stack::Result + where + V: StructVisitor<'de>, + { + Err(Report::new(TypeError.into_error()) + .attach(ReceivedType::new(str::document())) + .attach(ExpectedType::new(visitor.expecting())) + .change_context(DeserializerError)) + } } impl_owned!(!copy: String, StringDeserializer, visit_string); diff --git a/libs/deer/tests/common.rs b/libs/deer/tests/common.rs index 8b137891791..e3d45e8e017 100644 --- a/libs/deer/tests/common.rs +++ b/libs/deer/tests/common.rs @@ -1 +1,69 @@ +//! Temporary helper trait for folding reports until [#2377](https://github.com/hashintel/hash/discussions/2377) +//! is resolved and implemented. +use error_stack::{Context, Report}; + +pub(crate) trait TupleExt { + type Context: Context; + type Ok; + + fn fold_reports(self) -> Result>; +} + +#[rustfmt::skip] +macro_rules! all_the_tuples { + ($name:ident) => { + $name!([T1], T2); + $name!([T1, T2], T3); + $name!([T1, T2, T3], T4); + $name!([T1, T2, T3, T4], T5); + $name!([T1, T2, T3, T4, T5], T6); + $name!([T1, T2, T3, T4, T5, T6], T7); + $name!([T1, T2, T3, T4, T5, T6, T7], T8); + $name!([T1, T2, T3, T4, T5, T6, T7, T8], T9); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15); + $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16); + }; +} + +impl TupleExt for (Result>,) { + type Context = C; + type Ok = (T1,); + + fn fold_reports(self) -> Result> { + self.0.map(|value| (value,)) + } +} + +macro_rules! impl_tuple_ext { + ([$($elem:ident),*], $other:ident) => { + #[allow(non_snake_case)] + impl TupleExt for ($(Result<$elem, Report>, )* Result<$other, Report>) { + type Context = C; + type Ok = ($($elem ,)* $other); + + fn fold_reports(self) -> Result> { + let ( $($elem ,)* $other ) = self; + + let lhs = ( $($elem ,)* ).fold_reports(); + + match (lhs, $other) { + (Ok(( $($elem ,)* )), Ok(rhs)) => Ok(($($elem ,)* rhs)), + (Ok(_), Err(err)) | (Err(err), Ok(_)) => Err(err), + (Err(mut lhs), Err(rhs)) => { + lhs.extend_one(rhs); + + Err(lhs) + } + } + } + } + }; +} + +all_the_tuples!(impl_tuple_ext); diff --git a/libs/deer/tests/test_struct_visitor.rs b/libs/deer/tests/test_struct_visitor.rs new file mode 100644 index 00000000000..7fbd00c9879 --- /dev/null +++ b/libs/deer/tests/test_struct_visitor.rs @@ -0,0 +1,515 @@ +use deer::{ + error::{ArrayAccessError, DeserializeError, Variant, VisitorError}, + ArrayAccess, Deserialize, Deserializer, Document, FieldVisitor, ObjectAccess, Reflection, + Schema, StructVisitor, Visitor, +}; +use error_stack::{Report, Result, ResultExt}; +use serde::{ser::SerializeMap, Serialize, Serializer}; +use serde_json::json; + +mod common; + +use common::TupleExt; +use deer::{ + error::{ExpectedField, Location, ObjectAccessError, ReceivedField, UnknownFieldError}, + schema::Reference, + value::NoneDeserializer, +}; +use deer_desert::{assert_tokens, assert_tokens_error, error, Token}; + +#[derive(Debug, Eq, PartialEq)] +struct Example { + a: u8, + b: u16, + c: u32, +} + +struct ExampleFieldVisitor<'a> { + a: &'a mut Option, + b: &'a mut Option, + c: &'a mut Option, +} + +enum ExampleFieldDiscriminator { + A, + B, + C, +} + +impl Reflection for ExampleFieldDiscriminator { + fn schema(_: &mut Document) -> Schema { + Schema::new("string").with("enum", ["a", "b", "c"]) + } +} + +impl<'de> Deserialize<'de> for ExampleFieldDiscriminator { + type Reflection = Self; + + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct IdentVisitor; + + impl<'de> Visitor<'de> for IdentVisitor { + type Value = ExampleFieldDiscriminator; + + fn expecting(&self) -> Document { + Self::Value::reflection() + } + + fn visit_str(self, v: &str) -> Result { + match v { + "a" => Ok(ExampleFieldDiscriminator::A), + "b" => Ok(ExampleFieldDiscriminator::B), + "c" => Ok(ExampleFieldDiscriminator::C), + other => Err(Report::new(UnknownFieldError.into_error()) + .attach(ExpectedField::new("a")) + .attach(ExpectedField::new("b")) + .attach(ExpectedField::new("c")) + .attach(ReceivedField::new(other)) + .change_context(VisitorError)), + } + } + + fn visit_bytes(self, v: &[u8]) -> Result { + match v { + b"a" => Ok(ExampleFieldDiscriminator::A), + b"b" => Ok(ExampleFieldDiscriminator::B), + b"c" => Ok(ExampleFieldDiscriminator::C), + other => { + let mut error = Report::new(UnknownFieldError.into_error()) + .attach(ExpectedField::new("a")) + .attach(ExpectedField::new("b")) + .attach(ExpectedField::new("c")); + + if let Ok(other) = core::str::from_utf8(other) { + error = error.attach(ReceivedField::new(other)); + } + + Err(error.change_context(VisitorError)) + } + } + } + + fn visit_u64(self, v: u64) -> Result { + match v { + 0 => Ok(ExampleFieldDiscriminator::A), + 1 => Ok(ExampleFieldDiscriminator::B), + 2 => Ok(ExampleFieldDiscriminator::C), + // TODO: accommodate for numeric identifier, bytes identifier + _ => { + Err(Report::new(UnknownFieldError.into_error()) + .change_context(VisitorError)) + } + } + } + } + + deserializer + .deserialize_str(IdentVisitor) + .change_context(DeserializeError) + } +} + +impl<'de> FieldVisitor<'de> for ExampleFieldVisitor<'_> { + type Key = ExampleFieldDiscriminator; + type Value = (); + + fn visit_value(self, key: Self::Key, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // doing this instead of using an enum as return value resolves the issue that the enum + // would take always take the size of the biggest element in the enum itself, + // which is not ideal. + match key { + ExampleFieldDiscriminator::A => { + let value = u8::deserialize(deserializer) + .attach(Location::Field("a")) + .change_context(VisitorError)?; + + match self.a { + Some(_) => unimplemented!("planned in follow up PR"), + other => *other = Some(value), + }; + + Ok(()) + } + ExampleFieldDiscriminator::B => { + let value = u16::deserialize(deserializer) + .attach(Location::Field("b")) + .change_context(VisitorError)?; + + match self.b { + Some(_) => unimplemented!("planned in follow up PR"), + other => *other = Some(value), + } + + Ok(()) + } + ExampleFieldDiscriminator::C => { + let value = u32::deserialize(deserializer) + .attach(Location::Field("c")) + .change_context(VisitorError)?; + + match self.c { + Some(_) => unimplemented!("panned in follow up PR"), + other => *other = Some(value), + } + + Ok(()) + } + } + } +} + +struct ExampleVisitor; + +impl<'de> StructVisitor<'de> for ExampleVisitor { + type Value = Example; + + fn expecting(&self) -> Document { + Example::reflection() + } + + fn visit_array(self, mut array: A) -> Result + where + A: ArrayAccess<'de>, + { + array.set_bounded(3).change_context(VisitorError)?; + + // while the contract states that we're guaranteed to always `Some` for the first 3 + // due to set_bounded we make sure that even if implementations are not correct we are still + // correct but doing `unwrap_or_else`. + // TODO: we might be able to expose that through the type system? + // TODO: instead of doing this we need to use `NoneDeserializer`, + // this needs `.context()` on access rules to ensure proper ownership + let a = array + .next() + .unwrap_or_else(|| { + Deserialize::deserialize(NoneDeserializer::new(array.context())) + .attach(Location::Tuple(0)) + .change_context(ArrayAccessError) + }) + .attach(Location::Tuple(0)); + + let b = array + .next() + .unwrap_or_else(|| { + Deserialize::deserialize(NoneDeserializer::new(array.context())) + .attach(Location::Tuple(1)) + .change_context(ArrayAccessError) + }) + .attach(Location::Tuple(1)); + + let c = array + .next() + .unwrap_or_else(|| { + Deserialize::deserialize(NoneDeserializer::new(array.context())) + .attach(Location::Tuple(2)) + .change_context(ArrayAccessError) + }) + .attach(Location::Tuple(2)); + + let (a, b, c, _) = (a, b, c, array.end()) + .fold_reports() + .change_context(VisitorError)?; + + Ok(Example { a, b, c }) + } + + fn visit_object(self, mut object: A) -> Result + where + A: ObjectAccess<'de>, + { + let mut a = None; + let mut b = None; + let mut c = None; + + let mut errors: Result<(), ObjectAccessError> = Ok(()); + + while let Some(field) = object.field(ExampleFieldVisitor { + a: &mut a, + b: &mut b, + c: &mut c, + }) { + if let Err(error) = field { + match &mut errors { + Err(errors) => { + errors.extend_one(error); + } + errors => *errors = Err(error), + } + } + } + + let a = a.map_or_else( + || { + Deserialize::deserialize(NoneDeserializer::new(object.context())) + .attach(Location::Field("a")) + .change_context(ObjectAccessError) + }, + Ok, + ); + + let b = b.map_or_else( + || { + Deserialize::deserialize(NoneDeserializer::new(object.context())) + .attach(Location::Field("b")) + .change_context(ObjectAccessError) + }, + Ok, + ); + + let c = c.map_or_else( + || { + Deserialize::deserialize(NoneDeserializer::new(object.context())) + .attach(Location::Field("c")) + .change_context(ObjectAccessError) + }, + Ok, + ); + + let (a, b, c, ..) = (a, b, c, errors, object.end()) + .fold_reports() + .change_context(VisitorError)?; + + Ok(Example { a, b, c }) + } +} + +struct Properties([(&'static str, Reference); N]); + +impl Serialize for Properties { + fn serialize(&self, serializer: S) -> core::result::Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(self.0.len()))?; + + for (key, value) in self.0 { + map.serialize_entry(key, &value)?; + } + + map.end() + } +} + +impl Reflection for Example { + fn schema(doc: &mut Document) -> Schema { + // TODO: we cannot express or constraints right now + // we would need to say: object if e.g. JSON-schema, other format might do both + // blueprint must support "struct" + Schema::new("object").with( + "properties", + Properties([ + ("a", doc.add::()), + ("b", doc.add::()), + ("c", doc.add::()), + ]), + ) + } +} + +impl<'de> Deserialize<'de> for Example { + type Reflection = Self; + + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer + .deserialize_struct(ExampleVisitor) + .change_context(DeserializeError) + } +} + +#[test] +fn struct_object_ok() { + assert_tokens(&Example { a: 2, b: 3, c: 4 }, &[ + Token::Object { length: Some(3) }, + Token::Str("a"), + Token::Number(2.into()), + Token::Str("b"), + Token::Number(3.into()), + Token::Str("c"), + Token::Number(4.into()), + Token::ObjectEnd, + ]); +} + +#[test] +fn struct_object_out_of_order_ok() { + assert_tokens(&Example { a: 2, b: 3, c: 4 }, &[ + Token::Object { length: Some(3) }, + Token::Str("c"), + Token::Number(4.into()), + Token::Str("b"), + Token::Number(3.into()), + Token::Str("a"), + Token::Number(2.into()), + Token::ObjectEnd, + ]); +} + +// TODO: key missing instead of value missing (or discriminant missing) ~> only possible with +// IdentifierVisitor? +#[test] +fn struct_object_missing_err() { + assert_tokens_error::( + &error!([{ + ns: "deer", + id: ["value", "missing"], + properties: { + "expected": u16::reflection(), + "location": [{"type": "field", "value": "b"}] + } + }]), + &[ + Token::Object { length: Some(2) }, + Token::Str("a"), + Token::Number(2.into()), + Token::Str("c"), + Token::Number(4.into()), + Token::ObjectEnd, + ], + ); +} + +#[test] +fn struct_object_missing_multiple_err() { + assert_tokens_error::( + &error!([{ + ns: "deer", + id: ["value", "missing"], + properties: { + "expected": u16::reflection(), + "location": [{"type": "field", "value": "b"}] + } + },{ + ns: "deer", + id: ["value", "missing"], + properties: { + "expected": u32::reflection(), + "location": [{"type": "field", "value": "c"}] + } + }]), + &[ + Token::Object { length: Some(1) }, + Token::Str("a"), + Token::Number(2.into()), + Token::ObjectEnd, + ], + ); +} + +#[test] +fn struct_object_too_many_err() { + assert_tokens_error::( + &error!([{ + ns: "deer", + id: ["unknown", "field"], + properties: { + "expected": ["a", "b", "c"], + "received": ["d"], + "location": [] + } + }]), + &[ + Token::Object { length: Some(4) }, + Token::Str("a"), + Token::Number(2.into()), + Token::Str("b"), + Token::Number(3.into()), + Token::Str("c"), + Token::Number(4.into()), + Token::Str("d"), + Token::Number(5.into()), + Token::ObjectEnd, + ], + ); +} + +#[test] +#[ignore = "not yet implemented"] +fn struct_object_duplicate_err() { + // for now we cannot test this because there's no error for us to use + + // this will fail (this is on purpose) + assert_tokens_error::( + &error!([{ + ns: "deer", + id: ["unknown", "field"], + properties: { + "expected": ["a", "b", "c"], + "received": ["d"], + "location": [] + } + }]), + &[ + Token::Object { length: Some(3) }, + Token::Str("a"), + Token::Number(2.into()), + Token::Str("b"), + Token::Number(3.into()), + Token::Str("b"), + Token::Number(4.into()), + Token::ObjectEnd, + ], + ); +} + +#[test] +fn struct_array_ok() { + assert_tokens(&Example { a: 2, b: 3, c: 4 }, &[ + Token::Array { length: Some(3) }, + Token::Number(2.into()), + Token::Number(3.into()), + Token::Number(4.into()), + Token::ArrayEnd, + ]); +} + +#[test] +fn struct_array_missing_err() { + assert_tokens_error::( + &error!([{ + ns: "deer", + id: ["value", "missing"], + properties: { + "expected": u32::reflection(), + "location": [{"type": "tuple", "value": 2}] + } + }]), + &[ + Token::Array { length: Some(2) }, + Token::Number(2.into()), + Token::Number(3.into()), + Token::ArrayEnd, + ], + ); +} + +#[test] +fn struct_array_too_many_err() { + assert_tokens_error::( + &error!([{ + ns: "deer", + id: ["array", "length"], + properties: { + "expected": 3, + "received": 4, + "location": [] + } + }]), + &[ + Token::Array { length: Some(4) }, + Token::Number(2.into()), + Token::Number(3.into()), + Token::Number(4.into()), + Token::Number(5.into()), + Token::ArrayEnd, + ], + ); +}