Skip to content

Commit 2743d0b

Browse files
authored
SATS: impl ser/de for tuples (#3292)
# Description of Changes Implements `Serialize` and `Deserialize` for tuples. Extracted from #3276. # API and ABI breaking changes None # Expected complexity level and risk 2 # Testing A test `roundtrip_tuples_in_different_data_formats` is added.
1 parent 3114627 commit 2743d0b

File tree

10 files changed

+180
-38
lines changed

10 files changed

+180
-38
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings/src/rt.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::table::IndexAlgo;
44
use crate::{sys, IterBuf, ReducerContext, ReducerResult, SpacetimeType, Table};
55
pub use spacetimedb_lib::db::raw_def::v9::Lifecycle as LifecycleReducer;
66
use spacetimedb_lib::db::raw_def::v9::{RawIndexAlgorithm, RawModuleDefV9Builder, TableType};
7-
use spacetimedb_lib::de::{self, Deserialize, SeqProductAccess};
7+
use spacetimedb_lib::de::{self, Deserialize, Error as _, SeqProductAccess};
88
use spacetimedb_lib::sats::typespace::TypespaceBuilder;
99
use spacetimedb_lib::sats::{impl_deserialize, impl_serialize, ProductTypeElement};
1010
use spacetimedb_lib::ser::{Serialize, SerializeSeqProduct};
@@ -202,7 +202,7 @@ impl<'de, A: Args<'de>> de::ProductVisitor<'de> for ArgsVisitor<A> {
202202
A::visit_seq_product(prod)
203203
}
204204
fn visit_named_product<Acc: de::NamedProductAccess<'de>>(self, _prod: Acc) -> Result<Self::Output, Acc::Error> {
205-
Err(de::Error::custom("named products not supported"))
205+
Err(Acc::Error::named_products_not_supported())
206206
}
207207
}
208208

crates/bindings/tests/ui/tables.stderr

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ error[E0277]: the trait bound `Test: Deserialize<'de>` is not satisfied
6161
&'de [u8]
6262
&'de str
6363
()
64-
AlgebraicType
65-
AlgebraicTypeRef
66-
Alpha
67-
Arc<[T]>
68-
ArrayType
64+
(T0, T1)
65+
(T0, T1, T2)
66+
(T0, T1, T2, T3)
67+
(T0, T1, T2, T3, T4)
68+
(T0, T1, T2, T3, T4, T5)
6969
and $N others
7070
note: required by a bound in `spacetimedb::spacetimedb_lib::de::SeqProductAccess::next_element`
7171
--> $WORKSPACE/crates/sats/src/de.rs
@@ -83,11 +83,11 @@ error[E0277]: the trait bound `Test: Deserialize<'_>` is not satisfied
8383
&'de [u8]
8484
&'de str
8585
()
86-
AlgebraicType
87-
AlgebraicTypeRef
88-
Alpha
89-
Arc<[T]>
90-
ArrayType
86+
(T0, T1)
87+
(T0, T1, T2)
88+
(T0, T1, T2, T3)
89+
(T0, T1, T2, T3, T4)
90+
(T0, T1, T2, T3, T4, T5)
9191
and $N others
9292
note: required by a bound in `get_field_value`
9393
--> $WORKSPACE/crates/sats/src/de.rs
@@ -104,12 +104,12 @@ error[E0277]: the trait bound `Test: Serialize` is not satisfied
104104
= help: the following other types implement trait `Serialize`:
105105
&T
106106
()
107-
AlgebraicType
108-
AlgebraicTypeRef
109-
AlgebraicValue
110-
Alpha
111-
Arc<T>
112-
ArrayType
107+
(T0, T1)
108+
(T0, T1, T2)
109+
(T0, T1, T2, T3)
110+
(T0, T1, T2, T3, T4)
111+
(T0, T1, T2, T3, T4, T5)
112+
(T0, T1, T2, T3, T4, T5, T6)
113113
and $N others
114114
note: required by a bound in `spacetimedb::spacetimedb_lib::ser::SerializeNamedProduct::serialize_element`
115115
--> $WORKSPACE/crates/sats/src/ser.rs

crates/sats/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ rand.workspace = true
6969
# Also as dev-dependencies for use in _this_ crate's tests.
7070
proptest.workspace = true
7171
proptest-derive.workspace = true
72+
serde_json.workspace = true
73+
serde.workspace = true
7274

7375
[lints]
7476
workspace = true

crates/sats/src/de.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// See `serde` version `v1.0.169` for the parts where MIT / Apache-2.0 applies.
33

44
mod impls;
5-
#[cfg(feature = "serde")]
5+
#[cfg(any(test, feature = "serde"))]
66
pub mod serde;
77

88
#[doc(hidden)]
@@ -159,6 +159,11 @@ pub trait Error: Sized {
159159
/// Raised when there is general error when deserializing a type.
160160
fn custom(msg: impl fmt::Display) -> Self;
161161

162+
/// Deserializing named products are not supported for this visitor.
163+
fn named_products_not_supported() -> Self {
164+
Self::custom("named products not supported")
165+
}
166+
162167
/// The product length was not as promised.
163168
fn invalid_product_length<'de, T: ProductVisitor<'de>>(len: usize, expected: &T) -> Self {
164169
Self::custom(format_args!(

crates/sats/src/de/impls.rs

Lines changed: 120 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
SumValue, WithTypespace, F32, F64,
99
};
1010
use crate::{i256, u256};
11-
use core::{marker::PhantomData, ops::Bound};
11+
use core::{iter, marker::PhantomData, ops::Bound};
1212
use smallvec::SmallVec;
1313
use spacetimedb_primitives::{ColId, ColList};
1414
use std::{borrow::Cow, rc::Rc, sync::Arc};
@@ -52,31 +52,103 @@ impl_prim! {
5252
(f32, deserialize_f32) (f64, deserialize_f64)
5353
}
5454

55-
impl_deserialize!([] (), de => de.deserialize_product(UnitVisitor));
55+
struct TupleVisitor<A>(PhantomData<A>);
56+
#[derive(Copy, Clone)]
57+
struct TupleNameVisitorMax(usize);
5658

57-
/// The `UnitVisitor` looks for a unit product.
58-
/// That is, it consumes nothing from the input.
59-
struct UnitVisitor;
60-
impl<'de> ProductVisitor<'de> for UnitVisitor {
61-
type Output = ();
59+
impl FieldNameVisitor<'_> for TupleNameVisitorMax {
60+
// The index of the field name.
61+
type Output = usize;
6262

63-
fn product_name(&self) -> Option<&str> {
64-
None
63+
fn field_names(&self) -> impl '_ + Iterator<Item = Option<&str>> {
64+
iter::repeat_n(None, self.0)
6565
}
6666

67-
fn product_len(&self) -> usize {
68-
0
67+
fn kind(&self) -> ProductKind {
68+
ProductKind::Normal
6969
}
7070

71-
fn visit_seq_product<A: SeqProductAccess<'de>>(self, _prod: A) -> Result<Self::Output, A::Error> {
72-
Ok(())
71+
fn visit<E: Error>(self, name: &str) -> Result<Self::Output, E> {
72+
let err = || Error::unknown_field_name(name, &self);
73+
// Convert `name` to an index.
74+
let Ok(index) = name.parse() else {
75+
return Err(err());
76+
};
77+
// Confirm that the index exists or error.
78+
if index < self.0 {
79+
Ok(index)
80+
} else {
81+
Err(err())
82+
}
7383
}
7484

75-
fn visit_named_product<A: super::NamedProductAccess<'de>>(self, _prod: A) -> Result<Self::Output, A::Error> {
76-
Ok(())
85+
fn visit_seq(self, index: usize) -> Self::Output {
86+
// Assert that the index exists.
87+
assert!(index < self.0);
88+
index
7789
}
7890
}
7991

92+
macro_rules! impl_deserialize_tuple {
93+
($($ty_name:ident => $const_val:literal),*) => {
94+
impl<'de, $($ty_name: Deserialize<'de>),*> ProductVisitor<'de> for TupleVisitor<($($ty_name,)*)> {
95+
type Output = ($($ty_name,)*);
96+
fn product_name(&self) -> Option<&str> { None }
97+
fn product_len(&self) -> usize { crate::count!($($ty_name)*) }
98+
fn visit_seq_product<A: SeqProductAccess<'de>>(self, mut _prod: A) -> Result<Self::Output, A::Error> {
99+
$(
100+
#[allow(non_snake_case)]
101+
let $ty_name = _prod
102+
.next_element()?
103+
.ok_or_else(|| Error::invalid_product_length($const_val, &self))?;
104+
)*
105+
106+
Ok(($($ty_name,)*))
107+
}
108+
fn visit_named_product<A: super::NamedProductAccess<'de>>(self, mut prod: A) -> Result<Self::Output, A::Error> {
109+
$(
110+
#[allow(non_snake_case)]
111+
let mut $ty_name = None;
112+
)*
113+
114+
let visit = TupleNameVisitorMax(self.product_len());
115+
while let Some(index) = prod.get_field_ident(visit)? {
116+
match index {
117+
$($const_val => {
118+
if $ty_name.is_some() {
119+
return Err(A::Error::duplicate_field($const_val, None, &self))
120+
}
121+
$ty_name = Some(prod.get_field_value()?);
122+
})*
123+
index => return Err(Error::invalid_product_length(index, &self)),
124+
}
125+
}
126+
Ok(($(
127+
$ty_name.ok_or_else(|| A::Error::missing_field($const_val, None, &self))?,
128+
)*))
129+
}
130+
}
131+
132+
impl_deserialize!([$($ty_name: Deserialize<'de>),*] ($($ty_name,)*), de => {
133+
de.deserialize_product(TupleVisitor::<($($ty_name,)*)>(PhantomData))
134+
});
135+
};
136+
}
137+
138+
impl_deserialize_tuple!();
139+
impl_deserialize_tuple!(T0 => 0);
140+
impl_deserialize_tuple!(T0 => 0, T1 => 1);
141+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2);
142+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2, T3 => 3);
143+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2, T3 => 3, T4 => 4);
144+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2, T3 => 3, T4 => 4, T5 => 5);
145+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2, T3 => 3, T4 => 4, T5 => 5, T6 => 6);
146+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2, T3 => 3, T4 => 4, T5 => 5, T6 => 6, T7 => 7);
147+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2, T3 => 3, T4 => 4, T5 => 5, T6 => 6, T7 => 7, T8 => 8);
148+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2, T3 => 3, T4 => 4, T5 => 5, T6 => 6, T7 => 7, T8 => 8, T9 => 9);
149+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2, T3 => 3, T4 => 4, T5 => 5, T6 => 6, T7 => 7, T8 => 8, T9 => 9, T10 => 10);
150+
impl_deserialize_tuple!(T0 => 0, T1 => 1, T2 => 2, T3 => 3, T4 => 4, T5 => 5, T6 => 6, T7 => 7, T8 => 8, T9 => 9, T10 => 10, T11 => 11);
151+
80152
impl<'de> Deserialize<'de> for u8 {
81153
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
82154
deserializer.deserialize_u8()
@@ -705,3 +777,36 @@ impl_deserialize!([] bytes::Bytes, de => <Vec<u8>>::deserialize(de).map(Into::in
705777

706778
#[cfg(feature = "bytestring")]
707779
impl_deserialize!([] bytestring::ByteString, de => <String>::deserialize(de).map(Into::into));
780+
781+
#[cfg(test)]
782+
mod test {
783+
use crate::{
784+
algebraic_value::{de::ValueDeserializer, ser::value_serialize},
785+
bsatn,
786+
serde::SerdeWrapper,
787+
Deserialize, Serialize,
788+
};
789+
use core::fmt::Debug;
790+
791+
#[test]
792+
fn roundtrip_tuples_in_different_data_formats() {
793+
fn test<T: Serialize + for<'de> Deserialize<'de> + Eq + Debug>(x: T) {
794+
let bsatn = bsatn::to_vec(&x).unwrap();
795+
let y: T = bsatn::from_slice(&bsatn).unwrap();
796+
assert_eq!(x, y);
797+
798+
let val = value_serialize(&x);
799+
let y = T::deserialize(ValueDeserializer::new(val)).unwrap();
800+
assert_eq!(x, y);
801+
802+
let json = serde_json::to_string(SerdeWrapper::from_ref(&x)).unwrap();
803+
let SerdeWrapper(y) = serde_json::from_str::<SerdeWrapper<T>>(&json).unwrap();
804+
assert_eq!(x, y);
805+
}
806+
807+
test(());
808+
test((true,));
809+
test((1337u64, false));
810+
test(((7331u64, false), 42u32, 24u8));
811+
}
812+
}

crates/sats/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub mod typespace;
3434
#[cfg(any(test, feature = "proptest"))]
3535
pub mod proptest;
3636

37-
#[cfg(feature = "serde")]
37+
#[cfg(any(test, feature = "serde"))]
3838
pub mod serde {
3939
pub use crate::de::serde::{deserialize_from as deserialize, SerdeDeserializer};
4040
pub use crate::ser::serde::{serialize_to as serialize, SerdeSerializer};

crates/sats/src/ser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// See `serde` version `v1.0.169` for the parts where MIT / Apache-2.0 applies.
33

44
mod impls;
5-
#[cfg(feature = "serde")]
5+
#[cfg(any(test, feature = "serde"))]
66
pub mod serde;
77

88
use crate::de::DeserializeSeed;

crates/sats/src/ser/impls.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,36 @@ macro_rules! impl_prim {
4444
};
4545
}
4646

47-
impl_serialize!([] (), (self, ser) => ser.serialize_seq_product(0)?.end());
47+
// All the tuple types:
48+
#[macro_export]
49+
macro_rules! count {
50+
() => (0usize);
51+
( $x:tt $($xs:tt)* ) => (1usize + $crate::count!($($xs)*));
52+
}
53+
macro_rules! impl_serialize_tuple {
54+
($($ty_name:ident),*) => {
55+
impl_serialize!([$($ty_name: Serialize),*] ($($ty_name,)*), (self, ser) => {
56+
let mut _tup = ser.serialize_seq_product(count!($($ty_name)*))?;
57+
#[allow(non_snake_case)]
58+
let ($($ty_name,)*) = self;
59+
$(_tup.serialize_element($ty_name)?;)*
60+
_tup.end()
61+
});
62+
};
63+
}
64+
impl_serialize_tuple!();
65+
impl_serialize_tuple!(T0);
66+
impl_serialize_tuple!(T0, T1);
67+
impl_serialize_tuple!(T0, T1, T2);
68+
impl_serialize_tuple!(T0, T1, T2, T3);
69+
impl_serialize_tuple!(T0, T1, T2, T3, T4);
70+
impl_serialize_tuple!(T0, T1, T2, T3, T4, T5);
71+
impl_serialize_tuple!(T0, T1, T2, T3, T4, T5, T6);
72+
impl_serialize_tuple!(T0, T1, T2, T3, T4, T5, T6, T7);
73+
impl_serialize_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
74+
impl_serialize_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
75+
impl_serialize_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
76+
impl_serialize_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
4877

4978
// `u8` is implemented below as we wish to provide different `__serialize_array` impl (see below).
5079
impl_prim! {

crates/table/src/page_pool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ impl<'de> ProductVisitor<'de> for &PagePool {
117117
}
118118

119119
fn visit_named_product<A: NamedProductAccess<'de>>(self, _: A) -> Result<Self::Output, A::Error> {
120-
unreachable!()
120+
Err(A::Error::named_products_not_supported())
121121
}
122122
}
123123

0 commit comments

Comments
 (0)