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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ second-stack = "0.3"
self-replace = "1.5"
semver = "1"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = { version = "1.0.128", features = ["raw_value", "arbitrary_precision"] }
serde_json = { version = "1.0.128", features = ["raw_value"] }
serde_path_to_error = "0.1.9"
serde_with = { version = "3.3.0", features = ["base64", "hex"] }
serial_test = "2.0.0"
Expand Down
33 changes: 33 additions & 0 deletions crates/bindings/tests/deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,39 @@ fn deptree_snapshot() -> std::io::Result<()> {
Ok(())
}

#[test]
fn serde_json_arbitrary_precision_feature_boundaries() {
// https://github.com/clockworklabs/SpacetimeDB/issues/4989
// `serde_json/arbitrary_precision` is fine for internal tooling like the CLI,
// but it should not be forced onto users compiling the Rust SDK or module
// bindings. Cargo features are additive, so guard those public dependency
// graphs explicitly.

// The CLI opts into it because `spacetime subscribe` reformats JSON rows
// through `serde_json::Value`; without arbitrary precision, large SATS
// integers like `ConnectionId` can be rounded before typed deserialization.
assert_serde_json_arbitrary_precision("cargo tree -p spacetimedb-cli -e features,no-dev -i serde_json", true);
assert_serde_json_arbitrary_precision(
"cargo tree -p spacetimedb -e features,no-dev --target wasm32-unknown-unknown -i serde_json",
false,
);
assert_serde_json_arbitrary_precision("cargo tree -p spacetimedb-sdk -e features,no-dev -i serde_json", false);
assert_serde_json_arbitrary_precision(
"cargo tree -p spacetimedb-sdk -e features,no-dev --features browser --target wasm32-unknown-unknown -i serde_json",
false,
);
}

#[track_caller]
fn assert_serde_json_arbitrary_precision(cmd: &str, expected: bool) {
let tree = run_cmd(cmd);
assert_eq!(
tree.contains("serde_json feature \"arbitrary_precision\""),
expected,
"`arbitrary_precision` expectation failed for `{cmd}`:\n{tree}"
);
}

// runs a command string, split on spaces
#[track_caller]
fn run_cmd(cmd: &str) -> String {
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ regex.workspace = true
reqwest.workspace = true
rustyline.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value", "preserve_order"] }
serde_json = { workspace = true, features = ["raw_value", "preserve_order", "arbitrary_precision"] }
serde_with = { workspace = true, features = ["chrono_0_4"] }
slab.workspace = true
syntect.workspace = true
Expand Down
29 changes: 29 additions & 0 deletions crates/cli/src/subcommands/subscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,32 @@ fn format_output_json(

Ok(output)
}

#[cfg(test)]
mod tests {
use super::*;
use spacetimedb_lib::sats::{
algebraic_value::de::ValueDeserializer, de::Deserialize, GroundSpacetimeType, Typespace, WithTypespace,
};
use spacetimedb_lib::ConnectionId;

#[test]
fn serde_json_value_preserves_connection_id_u128() -> anyhow::Result<()> {
// `spacetime subscribe` reformats JSON rows through `serde_json::Value`
// before typed SATS deserialization. The CLI enables
// `serde_json/arbitrary_precision` so large `ConnectionId` values do not
// get rounded while inside `Value`.
let conn_id = ConnectionId::from_u128(u64::MAX as u128 + 1);
let json = serde_json::to_string(&SerializeWrapper::new(&conn_id))?;
let row = serde_json::from_str::<Value>(&json)?;

let typespace = Typespace::default();
let conn_id_ty = ConnectionId::get_type();
let conn_id_ty = WithTypespace::new(&typespace, &conn_id_ty);
let de = serde::de::DeserializeSeed::deserialize(SeedWrapper(conn_id_ty), row)?;
let de = ConnectionId::deserialize(ValueDeserializer::new(de)).unwrap();

assert_eq!(conn_id, de);
Ok(())
}
}
11 changes: 5 additions & 6 deletions crates/lib/src/connection_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,9 @@ mod tests {
use crate::WithTypespace;

proptest! {
/// Tests the round-trip used when using the `spacetime subscribe`
/// CLI command.
/// Somewhat confusingly, this is distinct from the ser-de path
/// in `test_serde_roundtrip`.
/// Tests deserialization through the serde seed adapter without
/// relying on `serde_json::Value` to preserve arbitrary-precision
/// numbers.
#[test]
fn test_wrapper_roundtrip(val: u128) {
let conn_id = ConnectionId::from_u128(val);
Expand All @@ -313,12 +312,12 @@ mod tests {
let empty = Typespace::default();
let conn_id_ty = ConnectionId::get_type();
let conn_id_ty = WithTypespace::new(&empty, &conn_id_ty);
let row = serde_json::from_str::<serde_json::Value>(&ser[..])?;
let mut de = serde_json::Deserializer::from_str(&ser);
let de = ::serde::de::DeserializeSeed::deserialize(
crate::de::serde::SeedWrapper(
conn_id_ty
),
row)?;
&mut de)?;
let de = ConnectionId::deserialize(ValueDeserializer::new(de)).unwrap();
prop_assert_eq!(conn_id, de);
}
Expand Down
Loading