Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 70 additions & 6 deletions crates/bindings-macro/src/sats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,8 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream {
de_generics.params.insert(0, de_lt_param.into());
let (de_impl_generics, _, de_where_clause) = de_generics.split_for_impl();

let (iter_n, iter_n2, iter_n3, iter_n4) = (0usize.., 0usize.., 0usize.., 0usize..);
let (iter_n, iter_n2, iter_n3, iter_n4, iter_n5, iter_n6, iter_n7) =
(0usize.., 0usize.., 0usize.., 0usize.., 0usize.., 0usize.., 0usize..);

match &ty.data {
SatsTypeData::Product(fields) => {
Expand Down Expand Up @@ -382,8 +383,10 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream {

let field_names = fields.iter().map(|f| f.ident.unwrap()).collect::<Vec<_>>();
let field_strings = fields.iter().map(|f| f.name.as_deref().unwrap()).collect::<Vec<_>>();
let field_types = fields.iter().map(|f| &f.ty);
let field_types = fields.iter().map(|f| f.ty);
let field_types2 = field_types.clone();
let field_types3 = field_types.clone();
let field_types4 = field_types.clone();
quote! {
#[allow(non_camel_case_types)]
#[allow(clippy::all)]
Expand All @@ -396,6 +399,12 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream {
_marker: std::marker::PhantomData::<fn() -> #name #ty_generics>,
})
}

fn validate<D: #spacetimedb_lib::de::Deserializer<'de>>(deserializer: D) -> Result<(), D::Error> {
deserializer.validate_product(__ProductVisitor {
_marker: std::marker::PhantomData::<fn() -> #name #ty_generics>,
})
}
}

struct __ProductVisitor #impl_generics #where_clause {
Expand All @@ -419,6 +428,13 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream {
.ok_or_else(|| #spacetimedb_lib::de::Error::invalid_product_length(#iter_n, &self))?,)*
})
}
fn validate_seq_product<A: #spacetimedb_lib::de::SeqProductAccess<'de>>(self, mut tup: A) -> Result<(), A::Error> {
#(
tup.validate_next_element::<#field_types2>()?
.ok_or_else(|| #spacetimedb_lib::de::Error::invalid_product_length(#iter_n2, &self))?;
)*
Ok(())
}
fn visit_named_product<A: #spacetimedb_lib::de::NamedProductAccess<'de>>(self, mut __prod: A) -> Result<Self::Output, A::Error> {
#(let mut #field_names = None;)*
while let Some(__field) = #spacetimedb_lib::de::NamedProductAccess::get_field_ident(&mut __prod, Self {
Expand All @@ -427,17 +443,39 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream {
match __field {
#(__ProductFieldIdent::#field_names => {
if #field_names.is_some() {
return Err(#spacetimedb_lib::de::Error::duplicate_field(#iter_n2, Some(#field_strings), &self))
return Err(#spacetimedb_lib::de::Error::duplicate_field(#iter_n3, Some(#field_strings), &self))
}
#field_names = Some(#spacetimedb_lib::de::NamedProductAccess::get_field_value::<#field_types2>(&mut __prod)?)
#field_names = Some(#spacetimedb_lib::de::NamedProductAccess::get_field_value::<#field_types3>(&mut __prod)?)
})*
}
}
Ok(#name {
#(#field_names:
#field_names.ok_or_else(|| #spacetimedb_lib::de::Error::missing_field(#iter_n3, Some(#field_strings), &self))?,)*
#field_names.ok_or_else(|| #spacetimedb_lib::de::Error::missing_field(#iter_n4, Some(#field_strings), &self))?,)*
})
}
fn validate_named_product<A: #spacetimedb_lib::de::NamedProductAccess<'de>>(self, mut __prod: A) -> Result<(), A::Error> {
#(let mut #field_names = false;)*
while let Some(__field) = #spacetimedb_lib::de::NamedProductAccess::get_field_ident(&mut __prod, Self {
_marker: std::marker::PhantomData,
})? {
match __field {
#(__ProductFieldIdent::#field_names => {
if #field_names {
return Err(#spacetimedb_lib::de::Error::duplicate_field(#iter_n5, Some(#field_strings), &self))
}
#spacetimedb_lib::de::NamedProductAccess::validate_field_value::<#field_types4>(&mut __prod)?;
#field_names = true;
})*
}
}
#(
if !#field_names {
return Err(#spacetimedb_lib::de::Error::missing_field(#iter_n6, Some(#field_strings), &self));
}
)*
Ok(())
}
}

impl #de_impl_generics #spacetimedb_lib::de::FieldNameVisitor<'de> for __ProductVisitor #ty_generics #de_where_clause {
Expand All @@ -456,7 +494,7 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream {

fn visit_seq(self, index: usize) -> Self::Output {
match index {
#(#iter_n4 => __ProductFieldIdent::#field_names,)*
#(#iter_n7 => __ProductFieldIdent::#field_names,)*
_ => core::unreachable!(),
}
}
Expand Down Expand Up @@ -488,6 +526,18 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream {
}
}
});
let arms_validate = variants.iter().map(|var| {
let ident = var.ident;
if let Some(ty) = var.ty {
quote! {
__Variant::#ident => #spacetimedb_lib::de::VariantAccess::validate::<#ty>(__access)?,
}
} else {
quote! {
__Variant::#ident => #spacetimedb_lib::de::VariantAccess::validate::<()>(__access)?,
}
}
});
quote! {
#[allow(clippy::all)]
const _: () = {
Expand All @@ -497,6 +547,12 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream {
_marker: std::marker::PhantomData::<fn() -> #name #ty_generics>,
})
}

fn validate<D: #spacetimedb_lib::de::Deserializer<'de>>(deserializer: D) -> Result<(), D::Error> {
deserializer.validate_sum(__SumVisitor {
_marker: std::marker::PhantomData::<fn() -> #name #ty_generics>,
})
}
}

struct __SumVisitor #impl_generics #where_clause {
Expand All @@ -516,6 +572,14 @@ pub(crate) fn derive_deserialize(ty: &SatsType<'_>) -> TokenStream {
#(#arms)*
}
}

fn validate_sum<A: #spacetimedb_lib::de::SumAccess<'de>>(self, __data: A) -> Result<(), A::Error> {
let (__variant, __access) = __data.variant(self)?;
match __variant {
#(#arms_validate)*
}
Ok(())
}
}

#[allow(non_camel_case_types)]
Expand Down
57 changes: 57 additions & 0 deletions crates/bindings/tests/ui/tables.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,36 @@ note: required by a bound in `spacetimedb::spacetimedb_lib::de::SeqProductAccess
| fn next_element<T: Deserialize<'de>>(&mut self) -> Result<Option<T>, Self::Error> {
| ^^^^^^^^^^^^^^^^ required by this bound in `SeqProductAccess::next_element`

error[E0277]: the trait bound `Test: Deserialize<'de>` is not satisfied
--> tests/ui/tables.rs:5:8
|
3 | #[spacetimedb::table(accessor = table)]
| --------------------------------------- required by a bound introduced by this call
4 | struct Table {
5 | x: Test,
| ^^^^ unsatisfied trait bound
|
help: the trait `Deserialize<'de>` is not implemented for `Test`
--> tests/ui/tables.rs:1:1
|
1 | struct Test;
| ^^^^^^^^^^^
= help: the following other types implement trait `Deserialize<'de>`:
&'de [u8]
&'de str
()
(T0, T1)
(T0, T1, T2)
(T0, T1, T2, T3)
(T0, T1, T2, T3, T4)
(T0, T1, T2, T3, T4, T5)
and $N others
note: required by a bound in `spacetimedb::spacetimedb_lib::de::SeqProductAccess::validate_next_element`
--> $WORKSPACE/crates/sats/src/de.rs
|
| fn validate_next_element<T: Deserialize<'de>>(&mut self) -> Result<Option<()>, Self::Error> {
| ^^^^^^^^^^^^^^^^ required by this bound in `SeqProductAccess::validate_next_element`

error[E0277]: the trait bound `Test: Deserialize<'_>` is not satisfied
--> tests/ui/tables.rs:5:8
|
Expand Down Expand Up @@ -90,6 +120,33 @@ note: required by a bound in `get_field_value`
| fn get_field_value<T: Deserialize<'de>>(&mut self) -> Result<T, Self::Error> {
| ^^^^^^^^^^^^^^^^ required by this bound in `NamedProductAccess::get_field_value`

error[E0277]: the trait bound `Test: Deserialize<'_>` is not satisfied
--> tests/ui/tables.rs:5:8
|
5 | x: Test,
| ^^^^ unsatisfied trait bound
|
help: the trait `Deserialize<'_>` is not implemented for `Test`
--> tests/ui/tables.rs:1:1
|
1 | struct Test;
| ^^^^^^^^^^^
= help: the following other types implement trait `Deserialize<'de>`:
&'de [u8]
&'de str
()
(T0, T1)
(T0, T1, T2)
(T0, T1, T2, T3)
(T0, T1, T2, T3, T4)
(T0, T1, T2, T3, T4, T5)
and $N others
note: required by a bound in `validate_field_value`
--> $WORKSPACE/crates/sats/src/de.rs
|
| fn validate_field_value<T: Deserialize<'de>>(&mut self) -> Result<(), Self::Error> {
| ^^^^^^^^^^^^^^^^ required by this bound in `NamedProductAccess::validate_field_value`

error[E0277]: the trait bound `Test: Serialize` is not satisfied
--> tests/ui/tables.rs:5:8
|
Expand Down
76 changes: 58 additions & 18 deletions crates/core/src/host/v8/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,28 @@ impl<'de, 'this, 'scope: 'de> de::Deserializer<'de> for Deserializer<'this, 'sco
})
}

fn validate_product<V: ProductVisitor<'de>>(self, visitor: V) -> Result<(), Self::Error> {
// In `ProductType.serializeValue()` in the TS SDK, null/undefined is accepted for the unit type.
if visitor.product_len() == 0 && self.input.is_null_or_undefined() {
return visitor.validate_seq_product(de::UnitAccess::new());
}

let object = cast!(
self.common.scope,
self.input,
Object,
"object for product type `{}`",
visitor.product_name().unwrap_or("<unknown>")
)?;

visitor.validate_named_product(ProductAccess {
common: self.common,
object,
next_value: None,
index: 0,
})
}

fn deserialize_sum<V: SumVisitor<'de>>(self, visitor: V) -> Result<V::Output, Self::Error> {
let scope = &*self.common.scope;

Expand Down Expand Up @@ -302,6 +324,17 @@ impl<'de, 'scope: 'de> de::NamedProductAccess<'de> for ProductAccess<'_, 'scope,
// Deserialize the field's value.
seed.deserialize(Deserializer { common, input })
}

fn validate_field_value_seed<T: DeserializeSeed<'de>>(&mut self, seed: T) -> Result<(), Self::Error> {
let common = self.common.reborrow();
// Extract the field's value.
let input = self
.next_value
.take()
.expect("Call next_key_seed before next_value_seed");
// Deserialize the field's value.
seed.validate(Deserializer { common, input })
}
}

/// Used in `Deserializer::deserialize_sum` to translate a `tag` property of a JS object
Expand Down Expand Up @@ -367,31 +400,38 @@ where
index: 0,
}
}

fn next_elem<'a>(&'a mut self) -> Option<Result<(T, Deserializer<'a, 'scope, 'isolate>), Error<'scope>>> {
self.seeds.next().map(move |seed| {
// Extract the array element.
let input = self
.arr
.get_index(self.common.scope, self.index)
.ok_or_else(exception_already_thrown)?;

// Make the deserializer.
let common = self.common.reborrow();
let de = Deserializer { common, input };

self.index += 1;
Ok((seed, de))
})
}
}

impl<'de, 'scope: 'de, T: DeserializeSeed<'de> + Clone> de::ArrayAccess<'de> for ArrayAccess<'_, 'scope, '_, T> {
type Element = T::Output;
type Error = Error<'scope>;

fn next_element(&mut self) -> Result<Option<Self::Element>, Self::Error> {
self.seeds
.next()
.map(|seed| {
// Extract the array element.
let val = self
.arr
.get_index(self.common.scope, self.index)
.ok_or_else(exception_already_thrown)?;

// Deserialize the element.
let val = seed.deserialize(Deserializer {
common: self.common.reborrow(),
input: val,
})?;

self.index += 1;
Ok(val)
})
self.next_elem()
.map(|res| res.and_then(|(seed, de)| seed.deserialize(de)))
.transpose()
}

fn validate_next_element(&mut self) -> Result<Option<()>, Self::Error> {
self.next_elem()
.map(|res| res.and_then(|(seed, de)| seed.validate(de)))
.transpose()
}

Expand Down
Loading
Loading