From 850e9594ff1ad2d8b42b7cb2b1acad5a791189f1 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Thu, 23 Jan 2025 23:10:52 +0100 Subject: [PATCH] chore(candid-utils): Factor out Candid pretty printing function (#3572) This PR moves Candid pretty printing into a separate function `printing::pretty`, adding a few unit tests to specify its expected behavior. This function will be used in a new crate that will be added in a follow-up PR. < [Previous PR](https://github.com/dfinity/ic/pull/3488) | [Next PR](https://github.com/dfinity/ic/pull/3439) > --- Cargo.lock | 2 + rs/nervous_system/candid_utils/BUILD.bazel | 4 +- rs/nervous_system/candid_utils/Cargo.toml | 3 + rs/nervous_system/candid_utils/src/lib.rs | 1 + .../candid_utils/src/printing.rs | 44 +++++++++++ .../candid_utils/src/printing/tests.rs | 76 +++++++++++++++++++ rs/sns/cli/BUILD.bazel | 1 + rs/sns/cli/Cargo.toml | 1 + .../cli/src/neuron_id_to_candid_subaccount.rs | 6 +- 9 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 rs/nervous_system/candid_utils/src/printing.rs create mode 100644 rs/nervous_system/candid_utils/src/printing/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 309d964bd17..431e8abd5cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1780,6 +1780,7 @@ version = "0.9.0" dependencies = [ "candid", "candid_parser", + "pretty_assertions", ] [[package]] @@ -11847,6 +11848,7 @@ dependencies = [ "anyhow", "base64 0.13.1", "candid", + "candid-utils", "clap 4.5.27", "futures", "hex", diff --git a/rs/nervous_system/candid_utils/BUILD.bazel b/rs/nervous_system/candid_utils/BUILD.bazel index 18e9d7cdd31..c6021e6f800 100644 --- a/rs/nervous_system/candid_utils/BUILD.bazel +++ b/rs/nervous_system/candid_utils/BUILD.bazel @@ -11,7 +11,9 @@ DEPENDENCIES = [ MACRO_DEPENDENCIES = [] -DEV_DEPENDENCIES = [] +DEV_DEPENDENCIES = [ + "@crate_index//:pretty_assertions", +] MACRO_DEV_DEPENDENCIES = [] diff --git a/rs/nervous_system/candid_utils/Cargo.toml b/rs/nervous_system/candid_utils/Cargo.toml index 6ea81ec4a4b..1854fe5022b 100644 --- a/rs/nervous_system/candid_utils/Cargo.toml +++ b/rs/nervous_system/candid_utils/Cargo.toml @@ -12,3 +12,6 @@ path = "src/lib.rs" [dependencies] candid = { workspace = true } candid_parser = { workspace = true } + +[dev-dependencies] +pretty_assertions = { workspace = true } diff --git a/rs/nervous_system/candid_utils/src/lib.rs b/rs/nervous_system/candid_utils/src/lib.rs index 8695201df0e..5b94aa1fb5b 100644 --- a/rs/nervous_system/candid_utils/src/lib.rs +++ b/rs/nervous_system/candid_utils/src/lib.rs @@ -1 +1,2 @@ +pub mod printing; pub mod validation; diff --git a/rs/nervous_system/candid_utils/src/printing.rs b/rs/nervous_system/candid_utils/src/printing.rs new file mode 100644 index 00000000000..7b40b0ef4a8 --- /dev/null +++ b/rs/nervous_system/candid_utils/src/printing.rs @@ -0,0 +1,44 @@ +use candid::{CandidType, IDLValue}; + +/// Pretty print `value` into a Candid string. +/// +/// Example: +/// +/// ```rust +/// #[derive(CandidType)] +/// struct DummyCandidStruct { +/// pub status: Option, +/// pub module_hash: Vec, +/// pub controllers: String, +/// pub memory_size: Option, +/// pub cycles: Option, +/// } +/// +/// let dummy = DummyCandidStruct { +/// status: Some(42), +/// module_hash: vec![1, 2, 3, 4], +/// controllers: "foo".to_string(), +/// memory_size: Some(100), +/// cycles: Some(123), +/// }; +/// println!("{}", pretty(&dummy)); +/// ``` +/// +/// Output: +/// ```candid +/// record { +/// status = opt (42 : int32); +/// controllers = "foo"; +/// memory_size = opt (100 : nat64); +/// cycles = opt (123 : nat64); +/// module_hash = blob "\01\02\03\04"; +/// } +/// ``` +pub fn pretty(value: &T) -> Result { + let value = IDLValue::try_from_candid_type(value).map_err(|err| err.to_string())?; + + Ok(value.to_string()) +} + +#[cfg(test)] +mod tests; diff --git a/rs/nervous_system/candid_utils/src/printing/tests.rs b/rs/nervous_system/candid_utils/src/printing/tests.rs new file mode 100644 index 00000000000..4402dbebb9b --- /dev/null +++ b/rs/nervous_system/candid_utils/src/printing/tests.rs @@ -0,0 +1,76 @@ +use super::*; +use pretty_assertions::{assert_eq, assert_str_eq}; + +#[derive(CandidType)] +struct DummyCandidStruct { + pub status: Option, + pub module_hash: Vec, + pub controllers: String, + pub memory_size: Option, + pub cycles: Option, +} + +#[derive(CandidType)] +enum DummyCandidVariant { + Foo(String), + Bar { abc: String, xyz: String }, +} + +#[derive(CandidType)] +struct DummyCandidContainer { + foo: DummyCandidVariant, + bar: Result, +} + +#[track_caller] +fn assert_expectation(value: &T, expected_result: Result) { + let observed_result = pretty(value); + + match (observed_result, expected_result) { + (Ok(observed), Ok(expected)) => { + assert_str_eq!(observed, expected); + } + (observed, expected) => { + assert_eq!(observed, expected); + } + } +} + +#[test] +fn test_pretty_printing_simple_struct() { + assert_expectation( + &DummyCandidStruct { + status: Some(42), + module_hash: vec![1, 2, 3, 4], + controllers: "foo".to_string(), + memory_size: Some(100), + cycles: Some(123), + }, + Ok(r#"record { + status = opt (42 : int32); + controllers = "foo"; + memory_size = opt (100 : nat64); + cycles = opt (123 : nat64); + module_hash = blob "\01\02\03\04"; +}"# + .to_string()), + ); +} + +#[test] +fn test_pretty_printing_complex_struct() { + assert_expectation( + &DummyCandidContainer { + foo: DummyCandidVariant::Foo("hello".to_string()), + bar: Ok(DummyCandidVariant::Bar { + abc: "abc".to_string(), + xyz: "xyz".to_string(), + }), + }, + Ok(r#"record { + bar = variant { Ok = variant { Bar = record { abc = "abc"; xyz = "xyz" } } }; + foo = variant { Foo = "hello" }; +}"# + .to_string()), + ); +} diff --git a/rs/sns/cli/BUILD.bazel b/rs/sns/cli/BUILD.bazel index 4b71f72f1a7..25cc6597049 100644 --- a/rs/sns/cli/BUILD.bazel +++ b/rs/sns/cli/BUILD.bazel @@ -7,6 +7,7 @@ DEPENDENCIES = [ # Keep sorted. "//rs/crypto/sha2", "//rs/nervous_system/agent", + "//rs/nervous_system/candid_utils", "//rs/nervous_system/common", "//rs/nervous_system/common/test_keys", "//rs/nervous_system/humanize", diff --git a/rs/sns/cli/Cargo.toml b/rs/sns/cli/Cargo.toml index 6df0cf7bd47..7d97b85aaf9 100644 --- a/rs/sns/cli/Cargo.toml +++ b/rs/sns/cli/Cargo.toml @@ -17,6 +17,7 @@ path = "src/lib.rs" anyhow = { workspace = true } base64 = { workspace = true } candid = { workspace = true } +candid-utils = { path = "../../nervous_system/candid_utils" } clap = { workspace = true } futures = { workspace = true } hex = { workspace = true } diff --git a/rs/sns/cli/src/neuron_id_to_candid_subaccount.rs b/rs/sns/cli/src/neuron_id_to_candid_subaccount.rs index 2b1be5cea17..54561c15bd8 100644 --- a/rs/sns/cli/src/neuron_id_to_candid_subaccount.rs +++ b/rs/sns/cli/src/neuron_id_to_candid_subaccount.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use anyhow::{anyhow, Result}; -use candid::IDLValue; +use candid_utils::printing; use clap::Parser; use ic_sns_governance::pb::v1::NeuronId; @@ -37,9 +37,7 @@ pub fn neuron_id_to_subaccount(args: NeuronIdToCandidSubaccountArgs) -> Result