From bdb2640c5a576da93d83e7e7d94a57e31bd8b503 Mon Sep 17 00:00:00 2001 From: clockwork-labs-bot Date: Tue, 12 May 2026 14:28:07 -0400 Subject: [PATCH 1/3] Drop serde_json arbitrary_precision from workspace --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 120ec24c2cf..6fd747fad80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" From a8f4018ef0243cee4ff7c710119f4c41295ae36d Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 13 May 2026 08:57:47 -0700 Subject: [PATCH 2/3] [bot/fix-4989-drop-arbitrary-precision]: fixes --- crates/bindings/tests/deps.rs | 33 +++++++++++++++++++++++++ crates/cli/Cargo.toml | 2 +- crates/cli/src/subcommands/subscribe.rs | 29 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/crates/bindings/tests/deps.rs b/crates/bindings/tests/deps.rs index 2383fdc7a1e..ccbb2b4b685 100644 --- a/crates/bindings/tests/deps.rs +++ b/crates/bindings/tests/deps.rs @@ -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 { diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index b2b8d7e1d89..bb71fb8b6e5 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -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 diff --git a/crates/cli/src/subcommands/subscribe.rs b/crates/cli/src/subcommands/subscribe.rs index 5b22b4cd8a5..1a8951c59c0 100644 --- a/crates/cli/src/subcommands/subscribe.rs +++ b/crates/cli/src/subcommands/subscribe.rs @@ -320,6 +320,35 @@ where Ok(()) } +#[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::(&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(()) + } +} + /// Print `num` [`ServerMessage::TransactionUpdate`] messages as JSON. /// If `num` is `None`, keep going indefinitely. async fn consume_transaction_updates(ws: &mut S, num: Option, module_def: &RawModuleDefV9) -> Result<(), Error> From 1253b3808c98645d240cf64b5d52090fc1c6d774 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 13 May 2026 14:26:20 -0700 Subject: [PATCH 3/3] [bot/fix-4989-drop-arbitrary-precision]: fix lint --- crates/cli/src/subcommands/subscribe.rs | 58 ++++++++++++------------- crates/lib/src/connection_id.rs | 11 +++-- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/crates/cli/src/subcommands/subscribe.rs b/crates/cli/src/subcommands/subscribe.rs index 1a8951c59c0..590cde75219 100644 --- a/crates/cli/src/subcommands/subscribe.rs +++ b/crates/cli/src/subcommands/subscribe.rs @@ -320,35 +320,6 @@ where Ok(()) } -#[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::(&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(()) - } -} - /// Print `num` [`ServerMessage::TransactionUpdate`] messages as JSON. /// If `num` is `None`, keep going indefinitely. async fn consume_transaction_updates(ws: &mut S, num: Option, module_def: &RawModuleDefV9) -> Result<(), Error> @@ -401,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::(&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(()) + } +} diff --git a/crates/lib/src/connection_id.rs b/crates/lib/src/connection_id.rs index 2055e389127..13fda72362f 100644 --- a/crates/lib/src/connection_id.rs +++ b/crates/lib/src/connection_id.rs @@ -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); @@ -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::(&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); }