From 4c6f77b999d566e715f3094fcb46a9548f361e1c Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Tue, 2 Jun 2026 07:44:21 +0200 Subject: [PATCH] test(domain): pin audit serde contracts; loosen time version; note null caveat Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 54 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- crates/domain/src/audit.rs | 37 ++++++++++++++++++++++---- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d173a28..6f8dab2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + [[package]] name = "digest" version = "0.10.7" @@ -389,6 +399,8 @@ name = "domain" version = "0.0.0" dependencies = [ "serde", + "serde_json", + "time", "uuid", ] @@ -1067,6 +1079,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -1201,6 +1219,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2013,6 +2037,36 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "time-macros" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index d82e493..4580caa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "postgres", uuid = { version = "1", features = ["v4", "serde"] } serde = { version = "1", features = ["derive"] } serde_json = "1" -time = { version = "0.3.44", features = ["serde"] } +time = { version = "0.3", features = ["serde"] } clap = { version = "4", features = ["derive", "env"] } utoipa = { version = "5", features = ["uuid"] } anyhow = "1" diff --git a/crates/domain/src/audit.rs b/crates/domain/src/audit.rs index 5b360a6..f9d7114 100644 --- a/crates/domain/src/audit.rs +++ b/crates/domain/src/audit.rs @@ -44,6 +44,10 @@ pub enum AuditActor { } /// One field's before/after values within a change. +/// +/// Note: after a JSON round-trip, `Some(Value::Null)` is indistinguishable from +/// `None`. Use `None` to mean "no value"; do not encode an absent value as +/// `Some(Value::Null)`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct FieldChange { /// Field name (catalogue field key or column name). @@ -111,10 +115,33 @@ mod tests { } #[test] - fn actor_is_adjacently_tagged() { - let v = serde_json::to_value(AuditActor::User(Uuid::nil())).unwrap(); - assert_eq!(v["kind"], "user"); - let v2 = serde_json::to_value(AuditActor::System).unwrap(); - assert_eq!(v2["kind"], "system"); + fn actor_serde_round_trips() { + for actor in [AuditActor::User(Uuid::nil()), AuditActor::System] { + let v = serde_json::to_value(actor).unwrap(); + let back: AuditActor = serde_json::from_value(v).unwrap(); + assert_eq!(back, actor); + } + assert_eq!( + serde_json::to_value(AuditActor::User(Uuid::nil())).unwrap()["kind"], + "user" + ); + assert_eq!( + serde_json::to_value(AuditActor::System).unwrap()["kind"], + "system" + ); + } + + #[test] + fn action_serde_matches_as_str() { + for a in [ + AuditAction::Created, + AuditAction::Updated, + AuditAction::Deleted, + ] { + assert_eq!( + serde_json::to_value(a).unwrap(), + serde_json::Value::String(a.as_str().to_owned()) + ); + } } }