Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deer implement Deserialize for built-in types (Part 1) #1516

Merged
merged 69 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
ab07079
init: framework crate
indietyp Dec 20, 2022
225619f
feat: token + deserialize (initial)
indietyp Dec 20, 2022
03eb3bf
feat: implement `ArrayAccess`
indietyp Dec 20, 2022
cfa0506
feat: `ObjectAccess` skeleton
indietyp Dec 20, 2022
b09de56
feat: `peek` ignore trivia
indietyp Dec 20, 2022
74d9776
drive-by: move `SetBoundedError` into `deer`
indietyp Dec 21, 2022
5ac60a0
feat: start error recovery
indietyp Dec 21, 2022
d9e4806
todo: oversight
indietyp Dec 21, 2022
01f0fc6
feat: introduction of trivia tape
indietyp Dec 21, 2022
e976b35
feat: `Cow` equivalent for `Trivia` to avoid allocation
indietyp Dec 21, 2022
acd1909
feat: rework `.end` for array
indietyp Dec 21, 2022
0c2e0f0
feat: `ObjectLengthError`
indietyp Dec 21, 2022
6f0c870
feat: reorganize code
indietyp Dec 21, 2022
b2b29f4
feat: fix compiler error
indietyp Dec 21, 2022
78ee04a
feat: example test
indietyp Dec 21, 2022
b614981
Merge branch 'main' into bm/deer/de-test
indietyp Dec 21, 2022
0089452
fix: lint
indietyp Dec 21, 2022
050658b
Merge branch 'bm/deer/de-test' of github.com:hashintel/hash into bm/d…
indietyp Dec 21, 2022
10de168
Merge branch 'main' into bm/deer/de-test
indietyp Dec 22, 2022
832a402
feat: `*Access` transpose `next()` output
indietyp Nov 26, 2022
d5fa1e8
feat: add schema to `Deserialize`, start lib
indietyp Nov 26, 2022
d66a246
feat: implement integral types
indietyp Dec 6, 2022
87e65b5
feat: implement floating types
indietyp Dec 6, 2022
1049a56
feat: char + string
indietyp Dec 6, 2022
6cee39d
feat: `BorrowReflection` for `!'static` types
indietyp Dec 6, 2022
4efd349
feat: `BorrowReflection` for `!'static` types
indietyp Dec 6, 2022
054417f
feat: `array` implementation move
indietyp Dec 6, 2022
5ba438e
feat: lift `Sized` requirement for `Reflection`
indietyp Dec 6, 2022
7eba1e5
feat: implement atomics
indietyp Dec 6, 2022
446952b
fix: atomics are unconditionally present :face_palm:
indietyp Dec 6, 2022
caeb7d4
feat: unit type
indietyp Dec 6, 2022
1d19b65
feat: NonZero
indietyp Dec 6, 2022
1f1a827
chore: edit TODO
indietyp Dec 6, 2022
e23b0a1
fix: types
indietyp Dec 6, 2022
40e8030
feat: rework `ArrayVisitor`
indietyp Dec 20, 2022
1bc3f4d
chore: done docs
indietyp Dec 20, 2022
e51be35
fix: `Token` is not longer `Copy`, start test
indietyp Dec 26, 2022
13dacf3
test: `u8` `Deserialize`
indietyp Dec 26, 2022
f92583a
feat: remove `U8Schema`
indietyp Dec 26, 2022
8f7961e
feat: remove unneeded polyfill schema types
indietyp Dec 26, 2022
031571b
test: happy path u* i*
indietyp Dec 26, 2022
ab93b64
fix: import
indietyp Dec 26, 2022
f75e472
feat: build test infrastructure
indietyp Dec 28, 2022
486e1f6
feat: improve error infrastructure
indietyp Dec 28, 2022
0e1869a
feat: improve error infrastructure
indietyp Dec 28, 2022
fef0413
chore: TODO
indietyp Jan 2, 2023
2259432
experiment: test i128 via new token
indietyp Jan 6, 2023
284a430
feat: `i128`, `isize`, `u128`, `usize` try to fit into `Number`
indietyp Jan 11, 2023
d3961dc
feat: test float and non-zero
indietyp Jan 11, 2023
7466bb0
feat: test unit + non-zero I128
indietyp Jan 11, 2023
ac3aa79
test: string implementation
indietyp Jan 11, 2023
011ba58
feat: test array
indietyp Jan 11, 2023
778c25f
feat: impl and test bool
indietyp Jan 11, 2023
de553ba
feat: docs examples for token variants
indietyp Jan 11, 2023
6cee84a
Merge branch 'main' into bm/deer/stdlib
indietyp Jan 11, 2023
d2913f6
fix: merge oversight
indietyp Jan 11, 2023
14b8fce
Merge branch 'main' into bm/deer/stdlib
indietyp Jan 11, 2023
3bd3ad0
fix: atomic presence on machines
indietyp Jan 11, 2023
d9e63ba
feat: impl for `AtomicBool`
indietyp Jan 11, 2023
b291c8b
fix: clippy
indietyp Jan 11, 2023
acbce74
fix: clippy?
indietyp Jan 11, 2023
0a2954a
chore: remove redundant comment
indietyp Jan 16, 2023
6f6abd0
Update packages/libs/deer/desert/src/token.rs
indietyp Jan 16, 2023
4835afc
chore: fix docs
indietyp Jan 16, 2023
e3e6b8d
Merge branch 'bm/deer/stdlib' of github.com:hashintel/hash into bm/de…
indietyp Jan 16, 2023
af34989
feat: move `Error` into `desert` and use for token assertion
indietyp Jan 16, 2023
273f5c4
Merge branch 'main' into bm/deer/stdlib
indietyp Jan 16, 2023
d264111
Merge branch 'main' into bm/deer/stdlib
indietyp Jan 17, 2023
28942ce
Merge branch 'main' into bm/deer/stdlib
indietyp Jan 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/libs/deer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ serde = { version = "1.0.152", default_features = false, features = ['alloc', 'd
erased-serde = { version = "0.3.24", default_features = false, features = ['alloc'] }

[dev-dependencies]
serde_json = "1.0.91"
serde_json = { version = "1.0.91", features = ['arbitrary_precision'] }
similar-asserts = { version = "1.4.2", features = ['serde'] }
deer-desert = { path = "./desert", features = ['pretty'] }
proptest = "1.0.0"
indietyp marked this conversation as resolved.
Show resolved Hide resolved
paste = "1.0.11"

[build-dependencies]
rustc_version = "0.4.0"
Expand Down
7 changes: 7 additions & 0 deletions packages/libs/deer/desert/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ publish = false
deer = { path = ".." }
error-stack = { version = "0.2.4", default_features = false }
serde_json = { version = "1.0.91", default_features = false, features = ['alloc'] }
serde = { version = "1.0.151", default_features = false, features = ['alloc'] }
bitvec = { version = "1", default_features = false, features = ['alloc', 'atomic'] }

similar-asserts = { version = "1.4.2", default_features = false, features = ['serde'], optional = true }

[features]
default = ['pretty']
pretty = ['dep:similar-asserts']
27 changes: 18 additions & 9 deletions packages/libs/deer/desert/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,32 @@ impl<'de> deer::ArrayAccess<'de> for ArrayAccess<'_, '_, 'de> {
where
T: Deserialize<'de>,
{
self.consumed += 1;
if let Some(remaining) = &mut self.remaining {
if *remaining == 0 {
return None;
}

*remaining = remaining.saturating_sub(1);
}

if self.deserializer.peek() == Token::ArrayEnd {
if matches!(self.deserializer.peek(), Token::ArrayEnd) {
// we have reached the ending, if `self.remaining` is set we use the `DeserializerNone`
// to deserialize any values that require `None`
if let Some(remaining) = &mut self.remaining {
if *remaining == 0 {
return None;
}

*remaining = remaining.saturating_sub(1);

if self.remaining.is_some() {
// previous statement ensures that remaining is decremented and wasn't 0
let value = T::deserialize(DeserializerNone {
context: self.deserializer.context(),
});

self.consumed += 1;
Some(value.change_context(ArrayAccessError))
} else {
None
}
} else {
let value = T::deserialize(&mut *self.deserializer);
self.consumed += 1;

Some(value.change_context(ArrayAccessError))
}
}
Expand All @@ -129,6 +133,11 @@ 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(
// we need to convert the index (peek index) into an amount to bump
// which means we need to increment
|index| index + 1,
)
.unwrap_or_else(|| self.deserializer.tape().remaining());
self.deserializer.tape_mut().bump_n(bump);

Expand Down
15 changes: 11 additions & 4 deletions packages/libs/deer/desert/src/assert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use core::fmt::Debug;

use deer::{error::ReportExt, Context, Deserialize};
use serde_json::to_value;
#[cfg(feature = "pretty")]
use similar_asserts::{assert_eq, assert_serde_eq};

use crate::{deserializer::Deserializer, token::Token};
use crate::{deserializer::Deserializer, error::ErrorVec, token::Token};

pub fn assert_tokens_with_context<'de, T>(expected: &T, tokens: &'de [Token], context: &Context)
where
Expand All @@ -27,7 +29,7 @@ where
}

pub fn assert_tokens_with_context_error<'de, T>(
error: &serde_json::Value,
error: &ErrorVec,
tokens: &'de [Token],
context: &Context,
) where
Expand All @@ -38,11 +40,16 @@ pub fn assert_tokens_with_context_error<'de, T>(

let received = received.export();
let received = to_value(received).expect("error should serialize");
let errors = ErrorVec::from_value(&received).expect("well-formed error object");

assert_eq!(received, *error)
#[cfg(not(feature = "pretty"))]
assert_eq!(errors, *error);

#[cfg(feature = "pretty")]
assert_serde_eq!(errors, *error);
}

pub fn assert_tokens_error<'de, T>(error: &serde_json::Value, tokens: &'de [Token])
pub fn assert_tokens_error<'de, T>(error: &ErrorVec, tokens: &'de [Token])
where
T: Deserialize<'de> + Debug,
{
Expand Down
11 changes: 10 additions & 1 deletion packages/libs/deer/desert/src/deserializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ impl<'a, 'de> deer::Deserializer<'de> for &mut Deserializer<'a, 'de> {
deserialize_null,
deserialize_bool,
deserialize_number,
deserialize_u128,
deserialize_i128,
deserialize_usize,
deserialize_isize,
deserialize_char,
deserialize_string,
deserialize_str,
Expand All @@ -57,7 +61,11 @@ impl<'a, 'de> deer::Deserializer<'de> for &mut Deserializer<'a, 'de> {

match token {
Token::Bool(value) => visitor.visit_bool(value),
Token::Number(value) => visitor.visit_number(value.clone()),
Token::Number(value) => visitor.visit_number(value),
Token::I128(value) => visitor.visit_i128(value),
Token::U128(value) => visitor.visit_u128(value),
Token::ISize(value) => visitor.visit_isize(value),
Token::USize(value) => visitor.visit_usize(value),
Token::Char(value) => visitor.visit_char(value),
Token::Str(value) => visitor.visit_str(value),
Token::BorrowedStr(value) => visitor.visit_borrowed_str(value),
Expand All @@ -67,6 +75,7 @@ impl<'a, 'de> deer::Deserializer<'de> for &mut Deserializer<'a, 'de> {
Token::BytesBuf(value) => visitor.visit_bytes_buffer(value.to_vec()),
Token::Array { length } => visitor.visit_array(ArrayAccess::new(self, length)),
Token::Object { length } => visitor.visit_object(ObjectAccess::new(self, length)),
Token::Null => visitor.visit_null(),
_ => {
panic!("Deserializer did not expect {token}");
}
Expand Down
121 changes: 121 additions & 0 deletions packages/libs/deer/desert/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use alloc::{borrow::Cow, vec, vec::Vec};
use core::ops::Deref;

use serde_json::Value;

#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
enum Id<'a> {
Static(&'static [&'static str]),
Vector(Vec<&'a str>),
}

impl<'a> Deref for Id<'a> {
type Target = [&'a str];

fn deref(&self) -> &Self::Target {
match self {
Id::Static(slice) => slice,
Id::Vector(slice) => slice.as_slice(),
}
}
}

impl PartialEq<Self> for Id<'_> {
fn eq(&self, other: &Self) -> bool {
**self == **other
}
}

impl Eq for Id<'_> {}

#[derive(Debug, serde::Serialize, Eq, PartialEq)]
pub struct BareError<'a> {
namespace: &'a str,
id: Id<'a>,
properties: Cow<'a, Value>,
}

impl BareError<'static> {
pub fn new_static(
namespace: &'static str,
id: &'static [&'static str],
properties: Value,
) -> Self {
Self {
namespace,
id: Id::Static(id),
properties: Cow::Owned(properties),
}
}
}

impl<'a> BareError<'a> {
fn from_value(value: &'a Value) -> Option<Self> {
let object = value.as_object()?;
let namespace = object.get("namespace")?.as_str()?;

let id = object
.get("id")?
.as_array()?
.iter()
.filter_map(|value| value.as_str())
.collect::<Vec<_>>();

let properties = object.get("properties")?;

let _ = object.get("message")?;

// ensure that there are exactly 4 properties
if object.len() != 4 {
return None;
}

Some(Self {
namespace,
id: Id::Vector(id),
properties: Cow::Borrowed(properties),
})
}
}

#[derive(Debug, serde::Serialize, Eq, PartialEq)]
pub struct ErrorVec<'a>(Vec<BareError<'a>>);

impl<'a> ErrorVec<'a> {
pub fn new<T: Into<BareError<'a>>>(errors: impl IntoIterator<Item = T>) -> Self {
Self(errors.into_iter().map(Into::into).collect())
}

pub(crate) fn from_value(value: &'a Value) -> Option<Self> {
let array = value.as_array()?;

let mut errors = vec![];
for value in array {
errors.push(BareError::from_value(value)?);
}

Some(Self(errors))
}
}

#[macro_export]
macro_rules! error {
([$($tt:tt),*]) => {
deer_desert::error::ErrorVec::new([$(error!(@internal $tt)),*])
};
{
ns: $namespace:literal,
id: [$($id:literal),*],
properties: $($properties:tt)*
} => {
error!([{ns: $namespace, id: [$($id),*], properties: $($properties)*}])
};
(@internal {
ns: $namespace:literal,
id: [$($id:literal),*],
properties: $($properties:tt)*
}) => {
deer_desert::error::BareError::new_static($namespace, &[$($id),*], json!($($properties)*))
}
}
1 change: 1 addition & 0 deletions packages/libs/deer/desert/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ extern crate alloc;
pub(crate) mod array;
mod assert;
mod deserializer;
pub mod error;
pub(crate) mod object;
pub(crate) mod tape;
mod token;
Expand Down
4 changes: 2 additions & 2 deletions packages/libs/deer/desert/src/tape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl Tape<'_, '_> {
impl<'a, 'de> Tape<'a, 'de> {
// also includes trivia
fn peek_all_n(&self, n: usize) -> Option<Token> {
self.tokens.get(n).copied()
self.tokens.get(n).cloned()
}

fn is_trivia_n(&self, n: usize) -> Option<bool> {
Expand Down Expand Up @@ -127,7 +127,7 @@ impl<'a, 'de> Tape<'a, 'de> {
self.trivia.to_mut().shift_left(1);
self.tokens = tokens;

Some((*token, is_trivia))
Some((token.clone(), is_trivia))
}

pub(crate) fn bump_n(&mut self, i: usize) {
Expand Down
Loading