diff --git a/Cargo.lock b/Cargo.lock index 8b80e6dcc03..33bcc76f18c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,7 +120,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -131,7 +131,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -857,7 +857,7 @@ checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ "clap", "heck 0.4.1", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "proc-macro2", "quote", @@ -876,7 +876,7 @@ checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799" dependencies = [ "clap", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "proc-macro2", "quote", @@ -889,9 +889,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.58" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -1114,7 +1114,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1620,7 +1620,7 @@ dependencies = [ "futures", "hex", "hickory-resolver", - "indexmap 2.13.0", + "indexmap 2.13.1", "key-wallet", "key-wallet-manager", "log", @@ -1927,7 +1927,7 @@ dependencies = [ "getrandom 0.2.17", "grovedb-commitment-tree", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "itertools 0.13.0", "json-schema-compatibility-validator", @@ -1991,7 +1991,7 @@ dependencies = [ "grovedb-storage", "grovedb-version", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "intmap", "itertools 0.13.0", @@ -2035,7 +2035,7 @@ dependencies = [ "file-rotate", "grovedb-commitment-tree", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "itertools 0.13.0", "lazy_static", @@ -2078,7 +2078,7 @@ dependencies = [ "dpp", "drive", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "platform-serialization", "platform-serialization-derive", "serde", @@ -2285,7 +2285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2466,9 +2466,12 @@ dependencies = [ [[package]] name = "fragile" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] [[package]] name = "fs_extra" @@ -2714,7 +2717,7 @@ dependencies = [ "grovedbg-types", "hex", "hex-literal", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "intmap", "itertools 0.14.0", @@ -2829,7 +2832,7 @@ dependencies = [ "grovedb-version", "grovedb-visualize", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "num_cpus", "rand 0.10.0", @@ -2866,7 +2869,7 @@ dependencies = [ "grovedb-costs", "grovedb-storage", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "integer-encoding", "thiserror 2.0.18", ] @@ -2929,7 +2932,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.13.1", "slab", "tokio", "tokio-util", @@ -3262,9 +3265,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -3277,7 +3280,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -3347,7 +3349,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -3381,12 +3383,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -3394,9 +3397,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -3407,9 +3410,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -3421,15 +3424,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -3441,15 +3444,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -3531,9 +3534,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -3603,7 +3606,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3923,9 +3926,9 @@ checksum = "744a4c881f502e98c2241d2e5f50040ac73b30194d64452bb6260393b53f0dc9" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libloading" @@ -3993,9 +3996,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -4132,7 +4135,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "indexmap 2.13.0", + "indexmap 2.13.1", "ipnet", "metrics", "metrics-util", @@ -4337,7 +4340,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4700,7 +4703,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.13.1", ] [[package]] @@ -4767,12 +4770,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkcs8" version = "0.10.2" @@ -4827,7 +4824,7 @@ dependencies = [ "bs58", "ciborium", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "platform-serialization", "platform-version", "rand 0.8.5", @@ -4872,7 +4869,7 @@ dependencies = [ "dash-sdk", "dashcore", "dpp", - "indexmap 2.13.0", + "indexmap 2.13.1", "key-wallet", "key-wallet-manager", "platform-encryption", @@ -4950,9 +4947,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -5034,7 +5031,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.8+spec-1.1.0", + "toml_edit 0.25.10+spec-1.1.0", ] [[package]] @@ -5109,7 +5106,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "itertools 0.14.0", "log", "multimap", @@ -5268,7 +5265,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -5306,7 +5303,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -5853,7 +5850,6 @@ dependencies = [ "dpp", "hex", "platform-version", - "serde_json", ] [[package]] @@ -6032,7 +6028,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6091,7 +6087,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6372,7 +6368,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "itoa", "memchr", "serde", @@ -6413,9 +6409,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -6458,7 +6454,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.13.1", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -6680,7 +6676,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6912,7 +6908,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7110,9 +7106,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -7155,9 +7151,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", @@ -7173,9 +7169,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -7271,9 +7267,9 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "serde_core", - "serde_spanned 1.1.0", + "serde_spanned 1.1.1", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -7300,9 +7296,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -7313,7 +7309,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "toml_datetime 0.6.11", "winnow 0.5.40", ] @@ -7324,7 +7320,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.13.1", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -7334,21 +7330,21 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.1.0+spec-1.1.0", + "indexmap 2.13.1", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow 1.0.1", ] @@ -7361,9 +7357,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tonic" @@ -7519,7 +7515,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.13.0", + "indexmap 2.13.1", "pin-project-lite", "slab", "sync_wrapper", @@ -8142,7 +8138,7 @@ dependencies = [ "dpp", "drive", "hex", - "indexmap 2.13.0", + "indexmap 2.13.1", "js-sys", "serde", "serde-wasm-bindgen 0.6.5", @@ -8180,7 +8176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.13.1", "wasm-encoder", "wasmparser", ] @@ -8242,7 +8238,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.13.1", "semver", ] @@ -8324,7 +8320,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -8689,7 +8685,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.13.1", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -8720,7 +8716,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "serde", "serde_derive", @@ -8739,7 +8735,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.13.1", "log", "semver", "serde", @@ -8764,9 +8760,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wyz" @@ -8791,9 +8787,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -8802,9 +8798,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -8855,18 +8851,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -8920,9 +8916,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -8931,9 +8927,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -8942,9 +8938,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -8964,7 +8960,7 @@ dependencies = [ "flate2", "getrandom 0.3.4", "hmac", - "indexmap 2.13.0", + "indexmap 2.13.1", "lzma-rust2", "memchr", "pbkdf2", @@ -8981,7 +8977,7 @@ checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ "crc32fast", "flate2", - "indexmap 2.13.0", + "indexmap 2.13.1", "memchr", "typed-path", "zopfli", diff --git a/packages/rs-platform-value/src/btreemap_extensions/btreemap_field_replacement.rs b/packages/rs-platform-value/src/btreemap_extensions/btreemap_field_replacement.rs index c4e1f2fc1d4..f93a1b79832 100644 --- a/packages/rs-platform-value/src/btreemap_extensions/btreemap_field_replacement.rs +++ b/packages/rs-platform-value/src/btreemap_extensions/btreemap_field_replacement.rs @@ -238,3 +238,519 @@ impl BTreeValueMapReplacementPathHelper for BTreeMap { .try_for_each(|path| self.replace_at_path(path.as_str(), replacement_type)) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::value_map::ValueMapHelper; + use crate::{Error, Value}; + use base64::prelude::BASE64_STANDARD; + use base64::Engine; + use std::collections::BTreeMap; + + // ----------------------------------------------------------------------- + // IntegerReplacementType::replace_for_value — each variant + // ----------------------------------------------------------------------- + + #[test] + fn integer_replacement_u8() { + let result = IntegerReplacementType::U8 + .replace_for_value(Value::U64(200)) + .unwrap(); + assert_eq!(result, Value::U8(200)); + } + + #[test] + fn integer_replacement_i8() { + let result = IntegerReplacementType::I8 + .replace_for_value(Value::I64(-100)) + .unwrap(); + assert_eq!(result, Value::I8(-100)); + } + + #[test] + fn integer_replacement_u16() { + let result = IntegerReplacementType::U16 + .replace_for_value(Value::U64(60000)) + .unwrap(); + assert_eq!(result, Value::U16(60000)); + } + + #[test] + fn integer_replacement_i16() { + let result = IntegerReplacementType::I16 + .replace_for_value(Value::I64(-30000)) + .unwrap(); + assert_eq!(result, Value::I16(-30000)); + } + + #[test] + fn integer_replacement_u32() { + let result = IntegerReplacementType::U32 + .replace_for_value(Value::U64(3_000_000)) + .unwrap(); + assert_eq!(result, Value::U32(3_000_000)); + } + + #[test] + fn integer_replacement_i32() { + let result = IntegerReplacementType::I32 + .replace_for_value(Value::I64(-3_000_000)) + .unwrap(); + assert_eq!(result, Value::I32(-3_000_000)); + } + + #[test] + fn integer_replacement_u64() { + let result = IntegerReplacementType::U64 + .replace_for_value(Value::U64(u64::MAX)) + .unwrap(); + assert_eq!(result, Value::U64(u64::MAX)); + } + + #[test] + fn integer_replacement_i64() { + let result = IntegerReplacementType::I64 + .replace_for_value(Value::I64(i64::MIN)) + .unwrap(); + assert_eq!(result, Value::I64(i64::MIN)); + } + + #[test] + fn integer_replacement_u128() { + let result = IntegerReplacementType::U128 + .replace_for_value(Value::U64(42)) + .unwrap(); + assert_eq!(result, Value::U128(42)); + } + + #[test] + fn integer_replacement_i128() { + let result = IntegerReplacementType::I128 + .replace_for_value(Value::I64(-42)) + .unwrap(); + assert_eq!(result, Value::I128(-42)); + } + + #[test] + fn integer_replacement_overflow_error() { + // Trying to fit a large u64 into u8 should error + let result = IntegerReplacementType::U8.replace_for_value(Value::U64(300)); + assert!(result.is_err()); + } + + #[test] + fn integer_replacement_non_integer_error() { + // Non-integer value should fail + let result = + IntegerReplacementType::U64.replace_for_value(Value::Text("not a number".into())); + assert!(result.is_err()); + } + + // ----------------------------------------------------------------------- + // ReplacementType::replace_for_bytes + // ----------------------------------------------------------------------- + + #[test] + fn replace_for_bytes_identifier_32_bytes_ok() { + let bytes = vec![0xABu8; 32]; + let result = ReplacementType::Identifier + .replace_for_bytes(bytes.clone()) + .unwrap(); + let expected: [u8; 32] = bytes.try_into().unwrap(); + assert_eq!(result, Value::Identifier(expected)); + } + + #[test] + fn replace_for_bytes_identifier_wrong_size() { + let bytes = vec![0xABu8; 31]; // not 32 bytes + let result = ReplacementType::Identifier.replace_for_bytes(bytes); + assert!(matches!(result, Err(Error::ByteLengthNot32BytesError(_)))); + } + + #[test] + fn replace_for_bytes_identifier_too_long() { + let bytes = vec![0xABu8; 33]; + let result = ReplacementType::Identifier.replace_for_bytes(bytes); + assert!(matches!(result, Err(Error::ByteLengthNot32BytesError(_)))); + } + + #[test] + fn replace_for_bytes_binary_bytes() { + let bytes = vec![1, 2, 3, 4, 5]; + let result = ReplacementType::BinaryBytes + .replace_for_bytes(bytes.clone()) + .unwrap(); + assert_eq!(result, Value::Bytes(bytes)); + } + + #[test] + fn replace_for_bytes_text_base58() { + let bytes = vec![0x01, 0x02, 0x03]; + let expected = bs58::encode(&bytes).into_string(); + let result = ReplacementType::TextBase58 + .replace_for_bytes(bytes) + .unwrap(); + assert_eq!(result, Value::Text(expected)); + } + + #[test] + fn replace_for_bytes_text_base64() { + let bytes = vec![0xDE, 0xAD, 0xBE, 0xEF]; + let expected = BASE64_STANDARD.encode(&bytes); + let result = ReplacementType::TextBase64 + .replace_for_bytes(bytes) + .unwrap(); + assert_eq!(result, Value::Text(expected)); + } + + // ----------------------------------------------------------------------- + // replace_for_bytes_20: correct size and wrong replacement type + // ----------------------------------------------------------------------- + + #[test] + fn replace_for_bytes_20_binary() { + let bytes = [0xFFu8; 20]; + let result = ReplacementType::BinaryBytes + .replace_for_bytes_20(bytes) + .unwrap(); + assert_eq!(result, Value::Bytes20(bytes)); + } + + #[test] + fn replace_for_bytes_20_text_base58() { + let bytes = [0x01u8; 20]; + let expected = bs58::encode(bytes).into_string(); + let result = ReplacementType::TextBase58 + .replace_for_bytes_20(bytes) + .unwrap(); + assert_eq!(result, Value::Text(expected)); + } + + #[test] + fn replace_for_bytes_20_text_base64() { + let bytes = [0x02u8; 20]; + let expected = BASE64_STANDARD.encode(bytes); + let result = ReplacementType::TextBase64 + .replace_for_bytes_20(bytes) + .unwrap(); + assert_eq!(result, Value::Text(expected)); + } + + #[test] + fn replace_for_bytes_20_identifier_error() { + let bytes = [0xAAu8; 20]; + let result = ReplacementType::Identifier.replace_for_bytes_20(bytes); + assert!(matches!(result, Err(Error::ByteLengthNot36BytesError(_)))); + } + + // ----------------------------------------------------------------------- + // replace_for_bytes_32: correct size and all replacement types + // ----------------------------------------------------------------------- + + #[test] + fn replace_for_bytes_32_identifier() { + let bytes = [0xBBu8; 32]; + let result = ReplacementType::Identifier + .replace_for_bytes_32(bytes) + .unwrap(); + assert_eq!(result, Value::Identifier(bytes)); + } + + #[test] + fn replace_for_bytes_32_binary() { + let bytes = [0xCCu8; 32]; + let result = ReplacementType::BinaryBytes + .replace_for_bytes_32(bytes) + .unwrap(); + assert_eq!(result, Value::Bytes32(bytes)); + } + + #[test] + fn replace_for_bytes_32_text_base58() { + let bytes = [0x01u8; 32]; + let expected = bs58::encode(bytes).into_string(); + let result = ReplacementType::TextBase58 + .replace_for_bytes_32(bytes) + .unwrap(); + assert_eq!(result, Value::Text(expected)); + } + + #[test] + fn replace_for_bytes_32_text_base64() { + let bytes = [0x02u8; 32]; + let expected = BASE64_STANDARD.encode(bytes); + let result = ReplacementType::TextBase64 + .replace_for_bytes_32(bytes) + .unwrap(); + assert_eq!(result, Value::Text(expected)); + } + + // ----------------------------------------------------------------------- + // replace_for_bytes_36: correct size and wrong replacement type + // ----------------------------------------------------------------------- + + #[test] + fn replace_for_bytes_36_binary() { + let bytes = [0xDDu8; 36]; + let result = ReplacementType::BinaryBytes + .replace_for_bytes_36(bytes) + .unwrap(); + assert_eq!(result, Value::Bytes36(bytes)); + } + + #[test] + fn replace_for_bytes_36_text_base58() { + let bytes = [0x03u8; 36]; + let expected = bs58::encode(bytes).into_string(); + let result = ReplacementType::TextBase58 + .replace_for_bytes_36(bytes) + .unwrap(); + assert_eq!(result, Value::Text(expected)); + } + + #[test] + fn replace_for_bytes_36_text_base64() { + let bytes = [0x04u8; 36]; + let expected = BASE64_STANDARD.encode(bytes); + let result = ReplacementType::TextBase64 + .replace_for_bytes_36(bytes) + .unwrap(); + assert_eq!(result, Value::Text(expected)); + } + + #[test] + fn replace_for_bytes_36_identifier_error() { + let bytes = [0xEEu8; 36]; + let result = ReplacementType::Identifier.replace_for_bytes_36(bytes); + assert!(matches!(result, Err(Error::ByteLengthNot36BytesError(_)))); + } + + // ----------------------------------------------------------------------- + // replace_at_path — single segment + // ----------------------------------------------------------------------- + + #[test] + fn replace_at_path_single_segment_bytes32() { + let bytes = [0xABu8; 32]; + let mut map = BTreeMap::new(); + map.insert("id".to_string(), Value::Bytes32(bytes)); + + map.replace_at_path("id", ReplacementType::Identifier) + .unwrap(); + assert_eq!(map.get("id"), Some(&Value::Identifier(bytes))); + } + + #[test] + fn replace_at_path_single_segment_bytes20() { + let bytes = [0x11u8; 20]; + let mut map = BTreeMap::new(); + map.insert("addr".to_string(), Value::Bytes20(bytes)); + + map.replace_at_path("addr", ReplacementType::BinaryBytes) + .unwrap(); + assert_eq!(map.get("addr"), Some(&Value::Bytes20(bytes))); + } + + #[test] + fn replace_at_path_single_segment_bytes36() { + let bytes = [0x22u8; 36]; + let mut map = BTreeMap::new(); + map.insert("outpoint".to_string(), Value::Bytes36(bytes)); + + map.replace_at_path("outpoint", ReplacementType::BinaryBytes) + .unwrap(); + assert_eq!(map.get("outpoint"), Some(&Value::Bytes36(bytes))); + } + + #[test] + fn replace_at_path_single_segment_identifier_to_base58() { + let bytes = [0xCCu8; 32]; + let mut map = BTreeMap::new(); + map.insert("id".to_string(), Value::Identifier(bytes)); + + map.replace_at_path("id", ReplacementType::TextBase58) + .unwrap(); + let expected = bs58::encode(bytes).into_string(); + assert_eq!(map.get("id"), Some(&Value::Text(expected))); + } + + // ----------------------------------------------------------------------- + // replace_at_path — multi-segment nested path + // ----------------------------------------------------------------------- + + #[test] + fn replace_at_path_nested() { + let bytes = [0xFFu8; 32]; + let inner_map = vec![(Value::Text("nested_id".into()), Value::Bytes32(bytes))]; + let mut map = BTreeMap::new(); + map.insert("parent".to_string(), Value::Map(inner_map)); + + map.replace_at_path("parent.nested_id", ReplacementType::Identifier) + .unwrap(); + + let parent = map.get("parent").unwrap(); + if let Value::Map(inner) = parent { + let val = inner.get_optional_key("nested_id").unwrap(); + assert_eq!(*val, Value::Identifier(bytes)); + } else { + panic!("expected Map"); + } + } + + #[test] + fn replace_at_path_deep_nested() { + let bytes = [0xAAu8; 32]; + let level2 = vec![(Value::Text("deep_id".into()), Value::Bytes32(bytes))]; + let level1 = vec![(Value::Text("level2".into()), Value::Map(level2))]; + let mut map = BTreeMap::new(); + map.insert("level1".to_string(), Value::Map(level1)); + + map.replace_at_path("level1.level2.deep_id", ReplacementType::Identifier) + .unwrap(); + + let l1 = map.get("level1").unwrap(); + if let Value::Map(l1_map) = l1 { + let l2 = l1_map.get_optional_key("level2").unwrap(); + if let Value::Map(l2_map) = l2 { + let val = l2_map.get_optional_key("deep_id").unwrap(); + assert_eq!(*val, Value::Identifier(bytes)); + } else { + panic!("expected Map at level2"); + } + } else { + panic!("expected Map at level1"); + } + } + + // ----------------------------------------------------------------------- + // replace_at_path — array traversal + // ----------------------------------------------------------------------- + + #[test] + fn replace_at_path_through_array_applies_to_elements() { + // When replace_down encounters an array at a non-terminal path component, + // it expands the array elements into the next recursion level. The path + // component consumed at the array level is effectively discarded (since + // arrays don't have named keys). The NEXT component is then applied to + // each array element. + // + // Structure: + // top-level BTreeMap: "wrapper" -> Map { "arr" -> Array [ Map{"id": Bytes32}, ... ] } + // Path: "wrapper.arr.placeholder.id" + // - "wrapper" handled by replace_at_path (first component) + // - replace_down gets ["arr", "placeholder", "id"] + // - "arr" consumed: looks up in wrapper map, finds Array, returns it + // - "placeholder" consumed: current is Array, expands to array items (Maps) + // - "id" consumed: terminal component, looks up in each item Map, performs replacement + let bytes1 = [0x11u8; 32]; + let bytes2 = [0x22u8; 32]; + let item1 = Value::Map(vec![(Value::Text("id".into()), Value::Bytes32(bytes1))]); + let item2 = Value::Map(vec![(Value::Text("id".into()), Value::Bytes32(bytes2))]); + let wrapper_map = vec![(Value::Text("arr".into()), Value::Array(vec![item1, item2]))]; + let mut map = BTreeMap::new(); + map.insert("wrapper".to_string(), Value::Map(wrapper_map)); + + // "placeholder" is consumed by the array level and discarded + map.replace_at_path("wrapper.arr.placeholder.id", ReplacementType::Identifier) + .unwrap(); + + if let Value::Map(wrapper) = map.get("wrapper").unwrap() { + let arr_val = wrapper.get_optional_key("arr").unwrap(); + if let Value::Array(arr) = arr_val { + assert_eq!(arr.len(), 2); + for (i, item) in arr.iter().enumerate() { + if let Value::Map(m) = item { + let val = m.get_optional_key("id").unwrap(); + let expected_bytes = if i == 0 { bytes1 } else { bytes2 }; + assert_eq!(*val, Value::Identifier(expected_bytes)); + } else { + panic!("expected Map in array"); + } + } + } else { + panic!("expected Array"); + } + } else { + panic!("expected Map at wrapper"); + } + } + + // ----------------------------------------------------------------------- + // Error paths + // ----------------------------------------------------------------------- + + #[test] + fn replace_at_path_empty_path_error() { + let mut map = BTreeMap::new(); + map.insert("key".to_string(), Value::U64(1)); + let result = map.replace_at_path("", ReplacementType::Identifier); + // Empty string splits to [""] which is a single component, not truly empty + // The path "" will try to look up key "" in the map, which doesn't exist + // So it returns Ok(()) because missing key is not an error + assert!(result.is_ok()); + } + + #[test] + fn replace_at_path_missing_key_returns_ok() { + let mut map = BTreeMap::new(); + map.insert("key".to_string(), Value::U64(1)); + // Nonexistent key -> returns Ok(()) + let result = map.replace_at_path("nonexistent", ReplacementType::BinaryBytes); + assert!(result.is_ok()); + } + + #[test] + fn replace_at_path_non_map_value_in_nested_path_error() { + let mut map = BTreeMap::new(); + map.insert("key".to_string(), Value::U64(42)); + // Trying to traverse into a non-map/non-array value + let result = map.replace_at_path("key.sub", ReplacementType::BinaryBytes); + assert!(matches!(result, Err(Error::PathError(_)))); + } + + // ----------------------------------------------------------------------- + // replace_at_paths — multiple paths + // ----------------------------------------------------------------------- + + #[test] + fn replace_at_paths_multiple() { + let bytes1 = [0xAAu8; 32]; + let bytes2 = [0xBBu8; 32]; + let mut map = BTreeMap::new(); + map.insert("id1".to_string(), Value::Bytes32(bytes1)); + map.insert("id2".to_string(), Value::Bytes32(bytes2)); + + let paths = vec!["id1".to_string(), "id2".to_string()]; + map.replace_at_paths(&paths, ReplacementType::Identifier) + .unwrap(); + + assert_eq!(map.get("id1"), Some(&Value::Identifier(bytes1))); + assert_eq!(map.get("id2"), Some(&Value::Identifier(bytes2))); + } + + // ----------------------------------------------------------------------- + // replace_consume_value and replace_value_in_place + // ----------------------------------------------------------------------- + + #[test] + fn replace_consume_value_identifier_to_base58() { + let bytes = [0xCCu8; 32]; + let val = Value::Identifier(bytes); + let result = ReplacementType::TextBase58 + .replace_consume_value(val) + .unwrap(); + let expected = bs58::encode(bytes).into_string(); + assert_eq!(result, Value::Text(expected)); + } + + #[test] + fn replace_value_in_place_identifier_to_binary() { + let bytes = [0xDDu8; 32]; + let mut val = Value::Identifier(bytes); + ReplacementType::BinaryBytes + .replace_value_in_place(&mut val) + .unwrap(); + assert_eq!(val, Value::Bytes(bytes.to_vec())); + } +} diff --git a/packages/rs-platform-value/src/converter/ciborium.rs b/packages/rs-platform-value/src/converter/ciborium.rs index 8321330dbbc..65c5f5b3766 100644 --- a/packages/rs-platform-value/src/converter/ciborium.rs +++ b/packages/rs-platform-value/src/converter/ciborium.rs @@ -143,3 +143,415 @@ impl TryInto> for Box { (*self).try_into().map(Box::new) } } + +#[cfg(test)] +mod tests { + use crate::{Error, Value}; + use ciborium::value::Integer; + use ciborium::Value as CborValue; + + // ----------------------------------------------------------------------- + // Round-trip: Value -> CborValue -> Value for basic types + // ----------------------------------------------------------------------- + + #[test] + fn round_trip_null() { + let original = Value::Null; + let cbor: CborValue = original.clone().try_into().unwrap(); + assert_eq!(cbor, CborValue::Null); + let back: Value = cbor.try_into().unwrap(); + assert_eq!(back, Value::Null); + } + + #[test] + fn round_trip_bool_true() { + let original = Value::Bool(true); + let cbor: CborValue = original.clone().try_into().unwrap(); + assert_eq!(cbor, CborValue::Bool(true)); + let back: Value = cbor.try_into().unwrap(); + // Comes back as I128(1) since CBOR integers are unified + assert_eq!(back, Value::Bool(true)); + } + + #[test] + fn round_trip_bool_false() { + let original = Value::Bool(false); + let cbor: CborValue = original.clone().try_into().unwrap(); + let back: Value = cbor.try_into().unwrap(); + assert_eq!(back, Value::Bool(false)); + } + + #[test] + fn round_trip_text() { + let original = Value::Text("hello world".into()); + let cbor: CborValue = original.clone().try_into().unwrap(); + assert_eq!(cbor, CborValue::Text("hello world".into())); + let back: Value = cbor.try_into().unwrap(); + assert_eq!(back, original); + } + + #[test] + fn round_trip_float() { + let original = Value::Float(3.14); + let cbor: CborValue = original.clone().try_into().unwrap(); + assert_eq!(cbor, CborValue::Float(3.14)); + let back: Value = cbor.try_into().unwrap(); + assert_eq!(back, original); + } + + #[test] + fn round_trip_bytes() { + let original = Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]); + let cbor: CborValue = original.clone().try_into().unwrap(); + assert_eq!(cbor, CborValue::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF])); + let back: Value = cbor.try_into().unwrap(); + assert_eq!(back, original); + } + + #[test] + fn round_trip_u64() { + let original = Value::U64(42); + let cbor: CborValue = original.clone().try_into().unwrap(); + // Comes back as Integer + let back: Value = cbor.try_into().unwrap(); + // CBOR integers come back as I128 + assert_eq!(back, Value::I128(42)); + } + + #[test] + fn round_trip_i64_negative() { + let original = Value::I64(-99); + let cbor: CborValue = original.clone().try_into().unwrap(); + let back: Value = cbor.try_into().unwrap(); + assert_eq!(back, Value::I128(-99)); + } + + // ----------------------------------------------------------------------- + // Tag rejection in TryFrom + // ----------------------------------------------------------------------- + + #[test] + fn cbor_tag_rejected() { + let tagged = CborValue::Tag(42, Box::new(CborValue::Null)); + let result: Result = tagged.try_into(); + assert!(matches!(result, Err(Error::Unsupported(_)))); + } + + #[test] + fn cbor_tag_rejection_message() { + let tagged = CborValue::Tag(0, Box::new(CborValue::Text("date".into()))); + let err = Value::try_from(tagged).unwrap_err(); + match err { + Error::Unsupported(msg) => { + assert!(msg.contains("tag"), "error message should mention tags"); + } + _ => panic!("expected Unsupported error"), + } + } + + // ----------------------------------------------------------------------- + // Byte-array heuristic boundary (10 vs 11 integer elements) + // Note: the CBOR heuristic uses > 10 (strictly greater), unlike JSON's >= 10 + // ----------------------------------------------------------------------- + + #[test] + fn cbor_array_10_integers_stays_array() { + // Exactly 10 elements -> stays as Array (boundary: > 10 needed for bytes) + let arr: Vec = (0..10) + .map(|i| CborValue::Integer(Integer::from(i as u8))) + .collect(); + let cbor = CborValue::Array(arr); + let val: Value = cbor.try_into().unwrap(); + assert!( + matches!(val, Value::Array(_)), + "10 elements should stay as Array in CBOR heuristic" + ); + } + + #[test] + fn cbor_array_11_integers_becomes_bytes() { + // 11 elements, all in u8 range -> becomes Bytes + let arr: Vec = (0..11) + .map(|i| CborValue::Integer(Integer::from(i as u8))) + .collect(); + let cbor = CborValue::Array(arr); + let val: Value = cbor.try_into().unwrap(); + assert!( + matches!(val, Value::Bytes(_)), + "11 elements of u8-range integers should become Bytes" + ); + if let Value::Bytes(bytes) = val { + assert_eq!(bytes.len(), 11); + assert_eq!(bytes[0], 0); + assert_eq!(bytes[10], 10); + } + } + + #[test] + fn cbor_array_mixed_types_stays_array() { + // 12 elements but mixed types -> stays as Array + let mut arr: Vec = (0..11) + .map(|i| CborValue::Integer(Integer::from(i as u8))) + .collect(); + arr.push(CborValue::Text("not an int".into())); + let cbor = CborValue::Array(arr); + let val: Value = cbor.try_into().unwrap(); + assert!(matches!(val, Value::Array(_))); + } + + #[test] + fn cbor_array_negative_values_stays_array() { + // Negative values are not in 0..=u8::MAX range + let arr: Vec = (0..12) + .map(|i| CborValue::Integer(Integer::from(-(i as i64)))) + .collect(); + let cbor = CborValue::Array(arr); + let val: Value = cbor.try_into().unwrap(); + // First element is 0 which is fine, but most are negative -> fails the ge(0) check + assert!(matches!(val, Value::Array(_))); + } + + // ----------------------------------------------------------------------- + // Map key sorting in TryInto + // ----------------------------------------------------------------------- + + #[test] + fn map_keys_sorted_in_cbor_output() { + // Keys inserted in reverse order should be sorted in output + let map = vec![ + (Value::Text("z".into()), Value::U64(1)), + (Value::Text("a".into()), Value::U64(2)), + (Value::Text("m".into()), Value::U64(3)), + ]; + let val = Value::Map(map); + let cbor: CborValue = val.try_into().unwrap(); + if let CborValue::Map(pairs) = cbor { + let keys: Vec = pairs + .iter() + .map(|(k, _)| { + if let CborValue::Text(s) = k { + s.clone() + } else { + panic!("expected text key") + } + }) + .collect(); + assert_eq!(keys, vec!["a", "m", "z"]); + } else { + panic!("expected CborValue::Map"); + } + } + + // ----------------------------------------------------------------------- + // EnumU8 / EnumString error paths + // ----------------------------------------------------------------------- + + #[test] + fn enum_u8_to_cbor_error() { + let val = Value::EnumU8(vec![1, 2, 3]); + let result: Result = val.try_into(); + assert!(matches!(result, Err(Error::Unsupported(_)))); + } + + #[test] + fn enum_string_to_cbor_error() { + let val = Value::EnumString(vec!["a".into(), "b".into()]); + let result: Result = val.try_into(); + assert!(matches!(result, Err(Error::Unsupported(_)))); + } + + // ----------------------------------------------------------------------- + // U128 / I128 narrowing in TryInto + // ----------------------------------------------------------------------- + + #[test] + fn u128_narrowed_to_u64_in_cbor() { + // U128 is cast to u64 when converting to CborValue::Integer + let val = Value::U128(42); + let cbor: CborValue = val.try_into().unwrap(); + assert_eq!(cbor, CborValue::Integer(42u64.into())); + } + + #[test] + fn i128_narrowed_to_i64_in_cbor() { + // I128 is cast to i64 when converting to CborValue::Integer + let val = Value::I128(-99); + let cbor: CborValue = val.try_into().unwrap(); + assert_eq!(cbor, CborValue::Integer((-99i64).into())); + } + + // ----------------------------------------------------------------------- + // Integer variant from CBOR -> Value + // ----------------------------------------------------------------------- + + #[test] + fn cbor_positive_integer_to_i128() { + let cbor = CborValue::Integer(Integer::from(255u8)); + let val: Value = cbor.try_into().unwrap(); + assert_eq!(val, Value::I128(255)); + } + + #[test] + fn cbor_negative_integer_to_i128() { + let cbor = CborValue::Integer(Integer::from(-1i64)); + let val: Value = cbor.try_into().unwrap(); + assert_eq!(val, Value::I128(-1)); + } + + #[test] + fn cbor_zero_integer_to_i128() { + let cbor = CborValue::Integer(Integer::from(0)); + let val: Value = cbor.try_into().unwrap(); + assert_eq!(val, Value::I128(0)); + } + + // ----------------------------------------------------------------------- + // Bytes20 / Bytes32 / Bytes36 / Identifier -> CborValue::Bytes + // ----------------------------------------------------------------------- + + #[test] + fn bytes20_to_cbor_bytes() { + let bytes = [0xAAu8; 20]; + let val = Value::Bytes20(bytes); + let cbor: CborValue = val.try_into().unwrap(); + assert_eq!(cbor, CborValue::Bytes(bytes.to_vec())); + } + + #[test] + fn bytes32_to_cbor_bytes() { + let bytes = [0xBBu8; 32]; + let val = Value::Bytes32(bytes); + let cbor: CborValue = val.try_into().unwrap(); + assert_eq!(cbor, CborValue::Bytes(bytes.to_vec())); + } + + #[test] + fn bytes36_to_cbor_bytes() { + let bytes = [0xCCu8; 36]; + let val = Value::Bytes36(bytes); + let cbor: CborValue = val.try_into().unwrap(); + assert_eq!(cbor, CborValue::Bytes(bytes.to_vec())); + } + + #[test] + fn identifier_to_cbor_bytes() { + let bytes = [0x01u8; 32]; + let val = Value::Identifier(bytes); + let cbor: CborValue = val.try_into().unwrap(); + assert_eq!(cbor, CborValue::Bytes(bytes.to_vec())); + } + + // ----------------------------------------------------------------------- + // Integer types round through CBOR + // ----------------------------------------------------------------------- + + #[test] + fn all_integer_types_to_cbor() { + let cases: Vec = vec![ + Value::U8(255), + Value::I8(-128), + Value::U16(65535), + Value::I16(-32768), + Value::U32(u32::MAX), + Value::I32(i32::MIN), + Value::U64(u64::MAX), + Value::I64(i64::MIN), + ]; + for val in cases { + let cbor: CborValue = val.clone().try_into().unwrap(); + assert!( + matches!(cbor, CborValue::Integer(_)), + "expected Integer for {:?}", + val + ); + } + } + + // ----------------------------------------------------------------------- + // Box TryInto> + // ----------------------------------------------------------------------- + + #[test] + fn boxed_value_to_boxed_cbor() { + let val = Box::new(Value::Text("boxed".into())); + let cbor: Box = val.try_into().unwrap(); + assert_eq!(*cbor, CborValue::Text("boxed".into())); + } + + // ----------------------------------------------------------------------- + // CBOR Map conversion + // ----------------------------------------------------------------------- + + #[test] + fn cbor_map_to_value_map() { + let cbor = CborValue::Map(vec![ + ( + CborValue::Text("key".into()), + CborValue::Integer(42u64.into()), + ), + (CborValue::Text("flag".into()), CborValue::Bool(true)), + ]); + let val: Value = cbor.try_into().unwrap(); + assert!(val.is_map()); + } + + // ----------------------------------------------------------------------- + // convert_from_cbor_map / convert_to_cbor_map + // ----------------------------------------------------------------------- + + #[test] + fn convert_from_cbor_map_basic() { + let pairs = vec![ + ("a".to_string(), CborValue::Bool(true)), + ("b".to_string(), CborValue::Text("hello".into())), + ]; + let result: std::collections::BTreeMap = + Value::convert_from_cbor_map(pairs).unwrap(); + assert_eq!(result.get("a"), Some(&Value::Bool(true))); + assert_eq!(result.get("b"), Some(&Value::Text("hello".into()))); + } + + #[test] + fn convert_to_cbor_map_basic() { + let pairs = vec![ + ("x".to_string(), Value::U64(10)), + ("y".to_string(), Value::Bool(false)), + ]; + let result: std::collections::BTreeMap = + Value::convert_to_cbor_map(pairs).unwrap(); + assert_eq!(result.get("x"), Some(&CborValue::Integer(10u64.into()))); + assert_eq!(result.get("y"), Some(&CborValue::Bool(false))); + } + + // ----------------------------------------------------------------------- + // to_cbor_buffer + // ----------------------------------------------------------------------- + + #[test] + fn to_cbor_buffer_roundtrip() { + let val = Value::Text("cbor buffer test".into()); + let buf = val.to_cbor_buffer().unwrap(); + assert!(!buf.is_empty()); + } + + // ----------------------------------------------------------------------- + // CBOR array with nested values + // ----------------------------------------------------------------------- + + #[test] + fn cbor_array_with_nested_map() { + let cbor = CborValue::Array(vec![ + CborValue::Text("item".into()), + CborValue::Map(vec![( + CborValue::Text("inner".into()), + CborValue::Bool(true), + )]), + ]); + let val: Value = cbor.try_into().unwrap(); + assert!(matches!(val, Value::Array(_))); + if let Value::Array(arr) = &val { + assert_eq!(arr.len(), 2); + assert!(arr[1].is_map()); + } + } +} diff --git a/packages/rs-platform-value/src/converter/serde_json.rs b/packages/rs-platform-value/src/converter/serde_json.rs index d33561913f6..9fd88fad254 100644 --- a/packages/rs-platform-value/src/converter/serde_json.rs +++ b/packages/rs-platform-value/src/converter/serde_json.rs @@ -423,8 +423,12 @@ impl From<&BTreeMap> for Value { #[cfg(test)] mod tests { - use crate::Value; - use serde_json::json; + use crate::converter::serde_json::BTreeValueJsonConverter; + use crate::{Error, Value}; + use base64::prelude::BASE64_STANDARD; + use base64::Engine; + use serde_json::{json, Value as JsonValue}; + use std::collections::BTreeMap; #[test] fn test_json_array() { @@ -462,4 +466,658 @@ mod tests { .unwrap(); assert_eq!(array.len(), 1); } + + // ----------------------------------------------------------------------- + // try_into_validating_json — all Value variants + // ----------------------------------------------------------------------- + + #[test] + fn validating_json_null() { + let result = Value::Null.try_into_validating_json().unwrap(); + assert_eq!(result, JsonValue::Null); + } + + #[test] + fn validating_json_bool() { + assert_eq!( + Value::Bool(true).try_into_validating_json().unwrap(), + JsonValue::Bool(true) + ); + assert_eq!( + Value::Bool(false).try_into_validating_json().unwrap(), + JsonValue::Bool(false) + ); + } + + #[test] + fn validating_json_u8() { + let result = Value::U8(42).try_into_validating_json().unwrap(); + assert_eq!(result, json!(42)); + } + + #[test] + fn validating_json_i8() { + let result = Value::I8(-5).try_into_validating_json().unwrap(); + assert_eq!(result, json!(-5)); + } + + #[test] + fn validating_json_u16() { + let result = Value::U16(1000).try_into_validating_json().unwrap(); + assert_eq!(result, json!(1000)); + } + + #[test] + fn validating_json_i16() { + let result = Value::I16(-1000).try_into_validating_json().unwrap(); + assert_eq!(result, json!(-1000)); + } + + #[test] + fn validating_json_u32() { + let result = Value::U32(100_000).try_into_validating_json().unwrap(); + assert_eq!(result, json!(100_000)); + } + + #[test] + fn validating_json_i32() { + let result = Value::I32(-100_000).try_into_validating_json().unwrap(); + assert_eq!(result, json!(-100_000)); + } + + #[test] + fn validating_json_u64() { + let result = Value::U64(u64::MAX).try_into_validating_json().unwrap(); + assert_eq!(result, json!(u64::MAX)); + } + + #[test] + fn validating_json_i64() { + let result = Value::I64(i64::MIN).try_into_validating_json().unwrap(); + assert_eq!(result, json!(i64::MIN)); + } + + #[test] + fn validating_json_float() { + let result = Value::Float(3.14).try_into_validating_json().unwrap(); + assert_eq!(result, json!(3.14)); + } + + #[test] + fn validating_json_text() { + let result = Value::Text("hello".into()) + .try_into_validating_json() + .unwrap(); + assert_eq!(result, json!("hello")); + } + + #[test] + fn validating_json_u128_fits_u64() { + let val = u64::MAX as u128; + let result = Value::U128(val).try_into_validating_json().unwrap(); + assert_eq!(result, json!(u64::MAX)); + } + + #[test] + fn validating_json_u128_too_large() { + let val = u64::MAX as u128 + 1; + let err = Value::U128(val).try_into_validating_json().unwrap_err(); + assert_eq!(err, Error::IntegerSizeError); + } + + #[test] + fn validating_json_i128_fits_i64_positive() { + let val = i64::MAX as i128; + let result = Value::I128(val).try_into_validating_json().unwrap(); + assert_eq!(result, json!(i64::MAX)); + } + + #[test] + fn validating_json_i128_fits_i64_negative() { + let val = i64::MIN as i128; + let result = Value::I128(val).try_into_validating_json().unwrap(); + assert_eq!(result, json!(i64::MIN)); + } + + #[test] + fn validating_json_i128_too_large_positive() { + let val = i64::MAX as i128 + 1; + let err = Value::I128(val).try_into_validating_json().unwrap_err(); + assert_eq!(err, Error::IntegerSizeError); + } + + #[test] + fn validating_json_i128_too_small_negative() { + let val = i64::MIN as i128 - 1; + let err = Value::I128(val).try_into_validating_json().unwrap_err(); + assert_eq!(err, Error::IntegerSizeError); + } + + #[test] + fn validating_json_bytes() { + let result = Value::Bytes(vec![1, 2, 3]) + .try_into_validating_json() + .unwrap(); + assert_eq!(result, json!([1, 2, 3])); + } + + #[test] + fn validating_json_bytes20() { + let bytes = [7u8; 20]; + let result = Value::Bytes20(bytes).try_into_validating_json().unwrap(); + let arr: Vec = bytes.iter().map(|b| json!(*b)).collect(); + assert_eq!(result, JsonValue::Array(arr)); + } + + #[test] + fn validating_json_bytes32() { + let bytes = [9u8; 32]; + let result = Value::Bytes32(bytes).try_into_validating_json().unwrap(); + let arr: Vec = bytes.iter().map(|b| json!(*b)).collect(); + assert_eq!(result, JsonValue::Array(arr)); + } + + #[test] + fn validating_json_bytes36() { + let bytes = [11u8; 36]; + let result = Value::Bytes36(bytes).try_into_validating_json().unwrap(); + let arr: Vec = bytes.iter().map(|b| json!(*b)).collect(); + assert_eq!(result, JsonValue::Array(arr)); + } + + #[test] + fn validating_json_identifier() { + let bytes = [0xABu8; 32]; + let result = Value::Identifier(bytes).try_into_validating_json().unwrap(); + let arr: Vec = bytes.iter().map(|b| json!(*b)).collect(); + assert_eq!(result, JsonValue::Array(arr)); + } + + #[test] + fn validating_json_array_nested() { + let val = Value::Array(vec![Value::U64(1), Value::Text("two".into())]); + let result = val.try_into_validating_json().unwrap(); + assert_eq!(result, json!([1, "two"])); + } + + #[test] + fn validating_json_map() { + let map = vec![ + (Value::Text("a".into()), Value::U64(1)), + (Value::Text("b".into()), Value::Bool(true)), + ]; + let val = Value::Map(map); + let result = val.try_into_validating_json().unwrap(); + assert_eq!(result, json!({"a": 1, "b": true})); + } + + #[test] + fn validating_json_enum_u8_unsupported() { + let err = Value::EnumU8(vec![1, 2]) + .try_into_validating_json() + .unwrap_err(); + assert!(matches!(err, Error::Unsupported(_))); + } + + #[test] + fn validating_json_enum_string_unsupported() { + let err = Value::EnumString(vec!["a".into()]) + .try_into_validating_json() + .unwrap_err(); + assert!(matches!(err, Error::Unsupported(_))); + } + + // ----------------------------------------------------------------------- + // From for Value — all JSON variants + // ----------------------------------------------------------------------- + + #[test] + fn from_json_null() { + let val: Value = JsonValue::Null.into(); + assert_eq!(val, Value::Null); + } + + #[test] + fn from_json_bool_true() { + let val: Value = json!(true).into(); + assert_eq!(val, Value::Bool(true)); + } + + #[test] + fn from_json_bool_false() { + let val: Value = json!(false).into(); + assert_eq!(val, Value::Bool(false)); + } + + #[test] + fn from_json_positive_integer() { + let val: Value = json!(42).into(); + assert_eq!(val, Value::U64(42)); + } + + #[test] + fn from_json_negative_integer() { + let val: Value = json!(-7).into(); + assert_eq!(val, Value::I64(-7)); + } + + #[test] + fn from_json_float() { + let val: Value = json!(2.5).into(); + assert_eq!(val, Value::Float(2.5)); + } + + #[test] + fn from_json_string() { + let val: Value = json!("hello").into(); + assert_eq!(val, Value::Text("hello".into())); + } + + #[test] + fn from_json_object() { + let val: Value = json!({"key": "value"}).into(); + assert!(val.is_map()); + } + + // --- byte-array heuristic tests --- + + #[test] + fn from_json_array_10_u8_range_becomes_bytes() { + // Exactly 10 elements, all in u8 range -> Bytes + let arr: Vec = (0u64..10).map(|i| json!(i)).collect(); + let val: Value = JsonValue::Array(arr).into(); + assert_eq!(val, Value::Bytes(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); + } + + #[test] + fn from_json_array_9_u8_range_stays_array() { + // Only 9 elements -> stays as Array even though all are u8-range + let arr: Vec = (0u64..9).map(|i| json!(i)).collect(); + let val: Value = JsonValue::Array(arr).into(); + assert!(matches!(val, Value::Array(_))); + } + + #[test] + fn from_json_array_mixed_types_stays_array() { + // 10+ elements but mixed types -> stays as Array + let mut arr: Vec = (0u64..10).map(|i| json!(i)).collect(); + arr.push(json!("not_a_number")); + let val: Value = JsonValue::Array(arr).into(); + assert!(matches!(val, Value::Array(_))); + } + + #[test] + fn from_json_array_large_values_stays_array() { + // 10+ elements but values exceed u8 range -> stays as Array + let arr: Vec = (0u64..12).map(|i| json!(i * 100)).collect(); + let val: Value = JsonValue::Array(arr).into(); + // Some values like 1100 exceed u8::MAX (255), so not all u8-range + assert!(matches!(val, Value::Array(_))); + } + + #[test] + fn from_json_array_all_255_becomes_bytes() { + // 10 elements all at u8::MAX + let arr: Vec = vec![json!(255); 10]; + let val: Value = JsonValue::Array(arr).into(); + assert_eq!(val, Value::Bytes(vec![255; 10])); + } + + #[test] + fn from_json_array_with_negative_stays_array() { + // Negative numbers are not in u8 range + let mut arr: Vec = (0u64..9).map(|i| json!(i)).collect(); + arr.push(json!(-1)); + let val: Value = JsonValue::Array(arr).into(); + assert!(matches!(val, Value::Array(_))); + } + + // ----------------------------------------------------------------------- + // From<&JsonValue> for Value — reference variant + // ----------------------------------------------------------------------- + + #[test] + fn from_json_ref_null() { + let jv = JsonValue::Null; + let val: Value = (&jv).into(); + assert_eq!(val, Value::Null); + } + + #[test] + fn from_json_ref_array_becomes_bytes() { + let arr: Vec = (0u64..15).map(|i| json!(i)).collect(); + let jv = JsonValue::Array(arr); + let val: Value = (&jv).into(); + assert!(matches!(val, Value::Bytes(_))); + } + + #[test] + fn from_json_ref_array_short_stays_array() { + let arr: Vec = (0u64..5).map(|i| json!(i)).collect(); + let jv = JsonValue::Array(arr); + let val: Value = (&jv).into(); + assert!(matches!(val, Value::Array(_))); + } + + // ----------------------------------------------------------------------- + // TryInto for Value — bytes become base64, identifiers become bs58 + // ----------------------------------------------------------------------- + + #[test] + fn try_into_json_bytes_become_base64() { + let bytes = vec![0xDE, 0xAD, 0xBE, 0xEF]; + let expected = BASE64_STANDARD.encode(&bytes); + let result: JsonValue = Value::Bytes(bytes).try_into().unwrap(); + assert_eq!(result, JsonValue::String(expected)); + } + + #[test] + fn try_into_json_bytes20_become_base64() { + let bytes = [0xAAu8; 20]; + let expected = BASE64_STANDARD.encode(bytes); + let result: JsonValue = Value::Bytes20(bytes).try_into().unwrap(); + assert_eq!(result, JsonValue::String(expected)); + } + + #[test] + fn try_into_json_bytes32_become_base64() { + let bytes = [0xBBu8; 32]; + let expected = BASE64_STANDARD.encode(bytes); + let result: JsonValue = Value::Bytes32(bytes).try_into().unwrap(); + assert_eq!(result, JsonValue::String(expected)); + } + + #[test] + fn try_into_json_bytes36_become_base64() { + let bytes = [0xCCu8; 36]; + let expected = BASE64_STANDARD.encode(bytes); + let result: JsonValue = Value::Bytes36(bytes).try_into().unwrap(); + assert_eq!(result, JsonValue::String(expected)); + } + + #[test] + fn try_into_json_identifier_becomes_bs58() { + let bytes = [0x01u8; 32]; + let expected = bs58::encode(&bytes).into_string(); + let result: JsonValue = Value::Identifier(bytes).try_into().unwrap(); + assert_eq!(result, JsonValue::String(expected)); + } + + #[test] + fn try_into_json_u128_becomes_string() { + let result: JsonValue = Value::U128(u128::MAX).try_into().unwrap(); + assert_eq!(result, JsonValue::String(u128::MAX.to_string())); + } + + #[test] + fn try_into_json_i128_becomes_string() { + let result: JsonValue = Value::I128(i128::MIN).try_into().unwrap(); + assert_eq!(result, JsonValue::String(i128::MIN.to_string())); + } + + #[test] + fn try_into_json_null() { + let result: JsonValue = Value::Null.try_into().unwrap(); + assert_eq!(result, JsonValue::Null); + } + + #[test] + fn try_into_json_bool() { + let result: JsonValue = Value::Bool(true).try_into().unwrap(); + assert_eq!(result, JsonValue::Bool(true)); + } + + #[test] + fn try_into_json_text() { + let result: JsonValue = Value::Text("abc".into()).try_into().unwrap(); + assert_eq!(result, json!("abc")); + } + + #[test] + fn try_into_json_integer_types() { + let r: JsonValue = Value::U8(1).try_into().unwrap(); + assert_eq!(r, json!(1)); + let r: JsonValue = Value::I8(-1).try_into().unwrap(); + assert_eq!(r, json!(-1)); + let r: JsonValue = Value::U16(500).try_into().unwrap(); + assert_eq!(r, json!(500)); + let r: JsonValue = Value::I16(-500).try_into().unwrap(); + assert_eq!(r, json!(-500)); + let r: JsonValue = Value::U32(70000).try_into().unwrap(); + assert_eq!(r, json!(70000)); + let r: JsonValue = Value::I32(-70000).try_into().unwrap(); + assert_eq!(r, json!(-70000)); + let r: JsonValue = Value::U64(123456789).try_into().unwrap(); + assert_eq!(r, json!(123456789)); + let r: JsonValue = Value::I64(-123456789).try_into().unwrap(); + assert_eq!(r, json!(-123456789)); + } + + #[test] + fn try_into_json_array() { + let val = Value::Array(vec![Value::U64(1), Value::Bool(false)]); + let result: JsonValue = val.try_into().unwrap(); + assert_eq!(result, json!([1, false])); + } + + #[test] + fn try_into_json_map() { + let map = vec![(Value::Text("x".into()), Value::U64(99))]; + let val = Value::Map(map); + let result: JsonValue = val.try_into().unwrap(); + assert_eq!(result, json!({"x": 99})); + } + + #[test] + fn try_into_json_enum_u8_error() { + let result: Result = Value::EnumU8(vec![1]).try_into(); + assert!(matches!(result, Err(Error::Unsupported(_)))); + } + + #[test] + fn try_into_json_enum_string_error() { + let result: Result = Value::EnumString(vec!["a".into()]).try_into(); + assert!(matches!(result, Err(Error::Unsupported(_)))); + } + + // ----------------------------------------------------------------------- + // Round-trip: Value -> JsonValue -> Value for basic types + // ----------------------------------------------------------------------- + + #[test] + fn round_trip_null() { + let original = Value::Null; + let json: JsonValue = original.clone().try_into_validating_json().unwrap(); + let back: Value = json.into(); + assert_eq!(back, original); + } + + #[test] + fn round_trip_bool() { + let original = Value::Bool(true); + let json: JsonValue = original.clone().try_into_validating_json().unwrap(); + let back: Value = json.into(); + assert_eq!(back, Value::Bool(true)); + } + + #[test] + fn round_trip_u64() { + let original = Value::U64(42); + let json: JsonValue = original.clone().try_into_validating_json().unwrap(); + let back: Value = json.into(); + // JSON numbers parse back as U64 + assert_eq!(back, Value::U64(42)); + } + + #[test] + fn round_trip_i64() { + let original = Value::I64(-42); + let json: JsonValue = original.clone().try_into_validating_json().unwrap(); + let back: Value = json.into(); + assert_eq!(back, Value::I64(-42)); + } + + #[test] + fn round_trip_text() { + let original = Value::Text("hello world".into()); + let json: JsonValue = original.clone().try_into_validating_json().unwrap(); + let back: Value = json.into(); + assert_eq!(back, original); + } + + // ----------------------------------------------------------------------- + // BTreeValueJsonConverter methods + // ----------------------------------------------------------------------- + + #[test] + fn btree_into_json_value() { + let mut map = BTreeMap::new(); + map.insert("x".to_string(), Value::U64(10)); + map.insert("y".to_string(), Value::Text("test".into())); + let json = map.into_json_value().unwrap(); + assert!(json.is_object()); + assert_eq!(json["x"], json!(10)); + assert_eq!(json["y"], json!("test")); + } + + #[test] + fn btree_into_validating_json_value() { + let mut map = BTreeMap::new(); + map.insert("n".to_string(), Value::U64(5)); + let json = map.into_validating_json_value().unwrap(); + assert_eq!(json["n"], json!(5)); + } + + #[test] + fn btree_to_json_value() { + let mut map = BTreeMap::new(); + map.insert("k".to_string(), Value::Bool(true)); + let json = map.to_json_value().unwrap(); + assert_eq!(json["k"], json!(true)); + // Original map is still available (borrow, not move) + assert!(map.contains_key("k")); + } + + #[test] + fn btree_to_validating_json_value() { + let mut map = BTreeMap::new(); + map.insert("v".to_string(), Value::I64(-1)); + let json = map.to_validating_json_value().unwrap(); + assert_eq!(json["v"], json!(-1)); + } + + #[test] + fn btree_from_json_value() { + let json = json!({"a": 1, "b": "two"}); + let map = BTreeMap::::from_json_value(json).unwrap(); + assert_eq!(map.get("a"), Some(&Value::U64(1))); + assert_eq!(map.get("b"), Some(&Value::Text("two".into()))); + } + + #[test] + fn btree_from_json_value_non_object_error() { + let json = json!([1, 2, 3]); + let result = BTreeMap::::from_json_value(json); + assert!(result.is_err()); + } + + // ----------------------------------------------------------------------- + // From> for Value + // ----------------------------------------------------------------------- + + #[test] + fn from_btree_json_map() { + let mut btree = BTreeMap::new(); + btree.insert("key".to_string(), json!(42)); + let val: Value = btree.into(); + assert!(val.is_map()); + } + + #[test] + fn from_btree_json_map_ref() { + let mut btree = BTreeMap::new(); + btree.insert("key".to_string(), json!(42)); + let val: Value = (&btree).into(); + assert!(val.is_map()); + } + + // ----------------------------------------------------------------------- + // try_to_validating_json (borrow variant) mirrors try_into_validating_json + // ----------------------------------------------------------------------- + + #[test] + fn try_to_validating_json_basic() { + let val = Value::U64(99); + let json = val.try_to_validating_json().unwrap(); + assert_eq!(json, json!(99)); + } + + #[test] + fn try_to_validating_json_u128_too_large() { + let val = Value::U128(u128::MAX); + let err = val.try_to_validating_json().unwrap_err(); + assert_eq!(err, Error::IntegerSizeError); + } + + #[test] + fn try_to_validating_json_i128_too_large() { + let val = Value::I128(i128::MAX); + let err = val.try_to_validating_json().unwrap_err(); + assert_eq!(err, Error::IntegerSizeError); + } + + #[test] + fn try_to_validating_json_i128_too_small() { + let val = Value::I128(i128::MIN); + let err = val.try_to_validating_json().unwrap_err(); + assert_eq!(err, Error::IntegerSizeError); + } + + #[test] + fn try_to_validating_json_enum_u8_error() { + let val = Value::EnumU8(vec![1]); + let err = val.try_to_validating_json().unwrap_err(); + assert!(matches!(err, Error::Unsupported(_))); + } + + #[test] + fn try_to_validating_json_enum_string_error() { + let val = Value::EnumString(vec!["a".into()]); + let err = val.try_to_validating_json().unwrap_err(); + assert!(matches!(err, Error::Unsupported(_))); + } + + // ----------------------------------------------------------------------- + // try_into_validating_btree_map_json + // ----------------------------------------------------------------------- + + #[test] + fn try_into_validating_btree_map_json_success() { + let map = vec![(Value::Text("k".into()), Value::U64(7))]; + let val = Value::Map(map); + let result = val.try_into_validating_btree_map_json().unwrap(); + assert_eq!(result.get("k"), Some(&json!(7))); + } + + // ----------------------------------------------------------------------- + // convert_from_serde_json_map + // ----------------------------------------------------------------------- + + #[test] + fn convert_from_serde_json_map_basic() { + let pairs = vec![ + ("a".to_string(), json!(1)), + ("b".to_string(), json!("hello")), + ]; + let result: BTreeMap = Value::convert_from_serde_json_map(pairs); + assert_eq!(result.get("a"), Some(&Value::U64(1))); + assert_eq!(result.get("b"), Some(&Value::Text("hello".into()))); + } + + #[test] + fn validating_json_float_nan_becomes_zero() { + // NaN cannot be represented in JSON Number, falls back to 0 + let result = Value::Float(f64::NAN).try_into_validating_json().unwrap(); + assert_eq!(result, json!(0)); + } } diff --git a/packages/rs-platform-value/src/patch/mod.rs b/packages/rs-platform-value/src/patch/mod.rs index c73eb1b834f..f3dd7c7c5cf 100644 --- a/packages/rs-platform-value/src/patch/mod.rs +++ b/packages/rs-platform-value/src/patch/mod.rs @@ -469,3 +469,569 @@ pub fn merge(doc: &mut Value, patch: &Value) { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{from_value, platform_value}; + + // --------------------------------------------------------------- + // add operation + // --------------------------------------------------------------- + + #[test] + fn add_to_map_key() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/b", "value": 2 } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc.pointer("/b"), Some(&platform_value!(2))); + } + + #[test] + fn add_to_array_push_with_dash() { + let mut doc = platform_value!({"arr": [1, 2]}); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/arr/-", "value": 3 } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc, platform_value!({"arr": [1, 2, 3]})); + } + + #[test] + fn add_to_array_insert_at_index() { + let mut doc = platform_value!({"arr": [1, 3]}); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/arr/1", "value": 2 } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc, platform_value!({"arr": [1, 2, 3]})); + } + + #[test] + fn add_empty_path_replaces_whole_document() { + let mut doc = platform_value!({"old": "value"}); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "", "value": "replaced" } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc, platform_value!("replaced")); + } + + #[test] + fn add_to_nested_map() { + let mut doc = platform_value!({"a": {"b": 1}}); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/a/c", "value": 2 } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc.pointer("/a/c"), Some(&platform_value!(2))); + } + + #[test] + fn add_at_array_beginning() { + let mut doc = platform_value!([2, 3]); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/0", "value": 1 } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc, platform_value!([1, 2, 3])); + } + + // --------------------------------------------------------------- + // remove operation + // --------------------------------------------------------------- + + #[test] + fn remove_from_map() { + let mut doc = platform_value!({"a": 1, "b": 2}); + let p: Patch = from_value(platform_value!([ + { "op": "remove", "path": "/a" } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc.pointer("/a"), None); + assert_eq!(doc.pointer("/b"), Some(&platform_value!(2))); + } + + #[test] + fn remove_from_array_by_index() { + let mut doc = platform_value!({"arr": [1, 2, 3]}); + let p: Patch = from_value(platform_value!([ + { "op": "remove", "path": "/arr/1" } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc, platform_value!({"arr": [1, 3]})); + } + + #[test] + fn remove_missing_key_errors() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "remove", "path": "/nonexistent" } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert!(matches!(err.kind, PatchErrorKind::InvalidPointer)); + } + + #[test] + fn remove_invalid_array_index_errors() { + let mut doc = platform_value!({"arr": [1]}); + let p: Patch = from_value(platform_value!([ + { "op": "remove", "path": "/arr/5" } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert!(matches!(err.kind, PatchErrorKind::InvalidPointer)); + } + + // --------------------------------------------------------------- + // replace operation + // --------------------------------------------------------------- + + #[test] + fn replace_existing_key() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "replace", "path": "/a", "value": 99 } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc, platform_value!({"a": 99})); + } + + #[test] + fn replace_missing_key_errors() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "replace", "path": "/b", "value": 2 } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert!(matches!(err.kind, PatchErrorKind::InvalidPointer)); + } + + #[test] + fn replace_root_document() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "replace", "path": "", "value": [1, 2, 3] } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc, platform_value!([1, 2, 3])); + } + + // --------------------------------------------------------------- + // move operation + // --------------------------------------------------------------- + + #[test] + fn move_between_map_keys() { + let mut doc = platform_value!({"a": 1, "b": 2}); + let p: Patch = from_value(platform_value!([ + { "op": "move", "from": "/a", "path": "/c" } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc.pointer("/a"), None); + assert_eq!(doc.pointer("/c"), Some(&platform_value!(1))); + assert_eq!(doc.pointer("/b"), Some(&platform_value!(2))); + } + + #[test] + fn move_inside_self_errors() { + let mut doc = platform_value!({"a": {"b": 1}}); + let p: Patch = from_value(platform_value!([ + { "op": "move", "from": "/a", "path": "/a/b/c" } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert!(matches!(err.kind, PatchErrorKind::CannotMoveInsideItself)); + } + + #[test] + fn move_from_invalid_path_errors() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "move", "from": "/nonexistent", "path": "/b" } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert!(matches!(err.kind, PatchErrorKind::InvalidFromPointer)); + } + + // --------------------------------------------------------------- + // copy operation + // --------------------------------------------------------------- + + #[test] + fn copy_between_map_keys() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "copy", "from": "/a", "path": "/b" } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc.pointer("/a"), Some(&platform_value!(1))); + assert_eq!(doc.pointer("/b"), Some(&platform_value!(1))); + } + + #[test] + fn copy_from_invalid_path_errors() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "copy", "from": "/missing", "path": "/b" } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert!(matches!(err.kind, PatchErrorKind::InvalidFromPointer)); + } + + #[test] + fn copy_nested_value() { + let mut doc = platform_value!({"a": {"x": 10}}); + let p: Patch = from_value(platform_value!([ + { "op": "copy", "from": "/a", "path": "/b" } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc.pointer("/b/x"), Some(&platform_value!(10))); + } + + // --------------------------------------------------------------- + // test operation + // --------------------------------------------------------------- + + #[test] + fn test_matching_value_succeeds() { + let mut doc = platform_value!({"a": "hello"}); + let p: Patch = from_value(platform_value!([ + { "op": "test", "path": "/a", "value": "hello" } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + } + + #[test] + fn test_mismatched_value_fails() { + let mut doc = platform_value!({"a": "hello"}); + let p: Patch = from_value(platform_value!([ + { "op": "test", "path": "/a", "value": "world" } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert!(matches!(err.kind, PatchErrorKind::TestFailed)); + } + + #[test] + fn test_missing_path_errors() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "test", "path": "/nope", "value": 1 } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert!(matches!(err.kind, PatchErrorKind::InvalidPointer)); + } + + // --------------------------------------------------------------- + // apply_patches: multi-operation and rollback + // --------------------------------------------------------------- + + #[test] + fn apply_patches_multi_operation() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/b", "value": 2 }, + { "op": "replace", "path": "/a", "value": 10 }, + { "op": "remove", "path": "/b" } + ])) + .unwrap(); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc, platform_value!({"a": 10})); + } + + #[test] + fn apply_patches_rollback_add_new_map_key_on_failure() { + // Known limitation: map rollback for add-new-key does not fully + // restore the original because remove() on a ValueMap uses + // position-based lookup that may not find the appended entry. + // This test documents the current (broken) behavior. + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/b", "value": 2 }, + { "op": "test", "path": "/a", "value": 999 } + ])) + .unwrap(); + // Patch fails (test op doesn't match), rollback is attempted + assert!(patch(&mut doc, &p).is_err()); + // The key "b" should have been removed by rollback but may remain + // due to the ValueMap append-only behavior. + } + + #[test] + fn apply_patches_rollback_add_array_on_failure() { + // Array rollback works correctly. + let mut doc = platform_value!([1, 2, 3]); + let original = doc.clone(); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/1", "value": 99 }, + { "op": "test", "path": "/0", "value": 999 } + ])) + .unwrap(); + assert!(patch(&mut doc, &p).is_err()); + assert_eq!(doc, original); + } + + #[test] + fn apply_patches_rollback_replace_on_failure() { + let mut doc = platform_value!({"a": 1, "b": 2}); + let original = doc.clone(); + let p: Patch = from_value(platform_value!([ + { "op": "replace", "path": "/a", "value": 100 }, + { "op": "test", "path": "/b", "value": 999 } + ])) + .unwrap(); + assert!(patch(&mut doc, &p).is_err()); + assert_eq!(doc, original); + } + + #[test] + fn apply_patches_rollback_remove_array_on_failure() { + let mut doc = platform_value!([1, 2, 3]); + let original = doc.clone(); + let p: Patch = from_value(platform_value!([ + { "op": "remove", "path": "/1" }, + { "op": "test", "path": "/0", "value": 999 } + ])) + .unwrap(); + assert!(patch(&mut doc, &p).is_err()); + assert_eq!(doc, original); + } + + #[test] + fn apply_patches_rollback_copy_array_on_failure() { + let mut doc = platform_value!({"items": [10, 20]}); + let original = doc.clone(); + let p: Patch = from_value(platform_value!([ + { "op": "copy", "from": "/items/0", "path": "/items/-" }, + { "op": "test", "path": "/items/0", "value": 999 } + ])) + .unwrap(); + assert!(patch(&mut doc, &p).is_err()); + assert_eq!(doc, original); + } + + #[test] + fn apply_patches_empty_patch_list() { + let mut doc = platform_value!({"a": 1}); + let p = Patch(vec![]); + patch(&mut doc, &p).unwrap(); + assert_eq!(doc, platform_value!({"a": 1})); + } + + // --------------------------------------------------------------- + // merge + // --------------------------------------------------------------- + + #[test] + fn merge_recursive_map() { + let mut doc = platform_value!({ + "a": { "b": 1, "c": 2 } + }); + let p = platform_value!({ + "a": { "b": 10, "d": 3 } + }); + merge(&mut doc, &p); + assert_eq!(doc.pointer("/a/b"), Some(&platform_value!(10))); + assert_eq!(doc.pointer("/a/c"), Some(&platform_value!(2))); + assert_eq!(doc.pointer("/a/d"), Some(&platform_value!(3))); + } + + #[test] + fn merge_null_removes_key() { + let mut doc = platform_value!({"a": 1, "b": 2}); + let p = platform_value!({"a": null}); + merge(&mut doc, &p); + assert_eq!(doc.pointer("/a"), None); + assert_eq!(doc.pointer("/b"), Some(&platform_value!(2))); + } + + #[test] + fn merge_non_map_patch_replaces_entire_document() { + let mut doc = platform_value!({"a": 1}); + let p = platform_value!("replaced"); + merge(&mut doc, &p); + assert_eq!(doc, platform_value!("replaced")); + } + + #[test] + fn merge_into_non_map_doc_creates_map() { + let mut doc = platform_value!("not a map"); + let p = platform_value!({"x": 1}); + merge(&mut doc, &p); + assert_eq!(doc.pointer("/x"), Some(&platform_value!(1))); + } + + #[test] + fn merge_adds_new_keys() { + let mut doc = platform_value!({"a": 1}); + let p = platform_value!({"b": 2}); + merge(&mut doc, &p); + assert_eq!(doc.pointer("/a"), Some(&platform_value!(1))); + assert_eq!(doc.pointer("/b"), Some(&platform_value!(2))); + } + + #[test] + fn merge_replaces_array_entirely() { + let mut doc = platform_value!({"tags": [1, 2, 3]}); + let p = platform_value!({"tags": [4]}); + merge(&mut doc, &p); + assert_eq!(doc.pointer("/tags"), Some(&platform_value!([4]))); + } + + // --------------------------------------------------------------- + // parse_index + // --------------------------------------------------------------- + + #[test] + fn parse_index_valid() { + assert_eq!(parse_index("0", 5).unwrap(), 0); + assert_eq!(parse_index("3", 5).unwrap(), 3); + assert_eq!(parse_index("4", 5).unwrap(), 4); + } + + #[test] + fn parse_index_leading_zero_errors() { + assert!(matches!( + parse_index("01", 5), + Err(PatchErrorKind::InvalidPointer) + )); + } + + #[test] + fn parse_index_leading_plus_errors() { + assert!(matches!( + parse_index("+1", 5), + Err(PatchErrorKind::InvalidPointer) + )); + } + + #[test] + fn parse_index_out_of_bounds_errors() { + assert!(matches!( + parse_index("5", 5), + Err(PatchErrorKind::InvalidPointer) + )); + } + + #[test] + fn parse_index_non_numeric_errors() { + assert!(matches!( + parse_index("abc", 5), + Err(PatchErrorKind::InvalidPointer) + )); + } + + #[test] + fn parse_index_single_zero_valid() { + assert_eq!(parse_index("0", 1).unwrap(), 0); + } + + // --------------------------------------------------------------- + // unescape + // --------------------------------------------------------------- + + #[test] + fn unescape_tilde_zero_becomes_tilde() { + assert_eq!(unescape("a~0b"), "a~b"); + } + + #[test] + fn unescape_tilde_one_becomes_slash() { + assert_eq!(unescape("a~1b"), "a/b"); + } + + #[test] + fn unescape_both_sequences() { + assert_eq!(unescape("~0~1"), "~/"); + } + + #[test] + fn unescape_no_tilde_borrows() { + let result = unescape("plain"); + assert!(matches!(result, Cow::Borrowed(_))); + assert_eq!(result, "plain"); + } + + #[test] + fn unescape_with_tilde_returns_owned() { + let result = unescape("a~0b"); + assert!(matches!(result, Cow::Owned(_))); + } + + // --------------------------------------------------------------- + // patch error reporting + // --------------------------------------------------------------- + + #[test] + fn patch_error_reports_correct_operation_index() { + let mut doc = platform_value!({"a": 1}); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/b", "value": 2 }, + { "op": "remove", "path": "/nonexistent" } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert_eq!(err.operation, 1); + assert_eq!(err.path, "/nonexistent"); + } + + // --------------------------------------------------------------- + // split_pointer + // --------------------------------------------------------------- + + #[test] + fn split_pointer_valid() { + let (parent, last) = split_pointer("/a/b").unwrap(); + assert_eq!(parent, "/a"); + assert_eq!(last, "b"); + } + + #[test] + fn split_pointer_root_child() { + let (parent, last) = split_pointer("/x").unwrap(); + assert_eq!(parent, ""); + assert_eq!(last, "x"); + } + + #[test] + fn split_pointer_no_slash_errors() { + assert!(split_pointer("noslash").is_err()); + } + + // --------------------------------------------------------------- + // add: error on invalid parent + // --------------------------------------------------------------- + + #[test] + fn add_to_scalar_parent_errors() { + let mut doc = platform_value!({"a": 42}); + let p: Patch = from_value(platform_value!([ + { "op": "add", "path": "/a/b", "value": 1 } + ])) + .unwrap(); + let err = patch(&mut doc, &p).unwrap_err(); + assert!(matches!(err.kind, PatchErrorKind::InvalidPointer)); + } +} diff --git a/packages/rs-platform-value/src/value_map.rs b/packages/rs-platform-value/src/value_map.rs index a067b87efba..4e09fa9cbd8 100644 --- a/packages/rs-platform-value/src/value_map.rs +++ b/packages/rs-platform-value/src/value_map.rs @@ -223,6 +223,411 @@ impl ValueMapHelper for ValueMap { } } +#[cfg(test)] +mod tests { + use super::*; + + fn text(s: &str) -> Value { + Value::Text(s.to_string()) + } + + fn make_map(pairs: &[(&str, Value)]) -> ValueMap { + pairs.iter().map(|(k, v)| (text(k), v.clone())).collect() + } + + // --------------------------------------------------------------- + // sort_by_keys + // --------------------------------------------------------------- + + #[test] + fn sort_by_keys_mixed_text_keys() { + let mut map = make_map(&[ + ("c", Value::U32(3)), + ("a", Value::U32(1)), + ("b", Value::U32(2)), + ]); + map.sort_by_keys(); + let keys: Vec<_> = map.iter().map(|(k, _)| k.clone()).collect(); + assert_eq!(keys, vec![text("a"), text("b"), text("c")]); + } + + #[test] + fn sort_by_keys_mixed_types() { + // Integer keys should sort before text keys via PartialOrd on Value + let mut map: ValueMap = vec![ + (text("z"), Value::U32(1)), + (Value::U32(5), Value::U32(2)), + (text("a"), Value::U32(3)), + ]; + map.sort_by_keys(); + // U32(5) < Text("a") < Text("z") by Value's PartialOrd (enum variant order) + assert_eq!(map[0].0, Value::U32(5)); + assert_eq!(map[1].0, text("a")); + assert_eq!(map[2].0, text("z")); + } + + // --------------------------------------------------------------- + // sort_by_lexicographical_byte_ordering_keys + // --------------------------------------------------------------- + + #[test] + fn sort_by_lexicographical_byte_ordering_shorter_first() { + // "ab" (len 2) should come before "abc" (len 3) + let mut map = make_map(&[ + ("abc", Value::U32(1)), + ("ab", Value::U32(2)), + ("a", Value::U32(3)), + ]); + map.sort_by_lexicographical_byte_ordering_keys(); + let keys: Vec<_> = map.iter().map(|(k, _)| k.to_text().unwrap()).collect(); + assert_eq!(keys, vec!["a", "ab", "abc"]); + } + + #[test] + fn sort_by_lexicographical_byte_ordering_same_length_alphabetical() { + let mut map = make_map(&[ + ("cb", Value::U32(1)), + ("ab", Value::U32(2)), + ("bb", Value::U32(3)), + ]); + map.sort_by_lexicographical_byte_ordering_keys(); + let keys: Vec<_> = map.iter().map(|(k, _)| k.to_text().unwrap()).collect(); + assert_eq!(keys, vec!["ab", "bb", "cb"]); + } + + #[test] + fn sort_by_lexicographical_byte_ordering_non_text_keys_uses_partial_cmp() { + let mut map: ValueMap = vec![(Value::U32(10), Value::Null), (Value::U32(2), Value::Null)]; + map.sort_by_lexicographical_byte_ordering_keys(); + assert_eq!(map[0].0, Value::U32(2)); + assert_eq!(map[1].0, Value::U32(10)); + } + + // --------------------------------------------------------------- + // get_key_mut_or_insert + // --------------------------------------------------------------- + + #[test] + fn get_key_mut_or_insert_inserts_new() { + let mut map = make_map(&[("a", Value::U32(1))]); + let val = map.get_key_mut_or_insert("b", Value::U32(99)); + assert_eq!(*val, Value::U32(99)); + // Mutate the returned reference + *val = Value::U32(100); + assert_eq!(map.get_optional_key("b"), Some(&Value::U32(100))); + } + + #[test] + fn get_key_mut_or_insert_returns_existing() { + let mut map = make_map(&[("a", Value::U32(1))]); + let val = map.get_key_mut_or_insert("a", Value::U32(99)); + // Should return existing value, not the default + assert_eq!(*val, Value::U32(1)); + } + + #[test] + fn get_key_mut_or_insert_existing_is_mutable() { + let mut map = make_map(&[("a", Value::U32(1))]); + let val = map.get_key_mut_or_insert("a", Value::U32(99)); + *val = Value::U32(42); + assert_eq!(map.get_optional_key("a"), Some(&Value::U32(42))); + } + + // --------------------------------------------------------------- + // remove_optional_key_if_null + // --------------------------------------------------------------- + + #[test] + fn remove_optional_key_if_null_removes_null() { + let mut map = make_map(&[("a", Value::Null), ("b", Value::U32(2))]); + map.remove_optional_key_if_null("a"); + assert_eq!(map.get_optional_key("a"), None); + assert_eq!(map.get_optional_key("b"), Some(&Value::U32(2))); + } + + #[test] + fn remove_optional_key_if_null_keeps_non_null() { + let mut map = make_map(&[("a", Value::U32(1))]); + map.remove_optional_key_if_null("a"); + assert_eq!(map.get_optional_key("a"), Some(&Value::U32(1))); + } + + #[test] + fn remove_optional_key_if_null_missing_key_is_noop() { + let mut map = make_map(&[("a", Value::U32(1))]); + map.remove_optional_key_if_null("missing"); + assert_eq!(map.len(), 1); + } + + // --------------------------------------------------------------- + // remove_optional_key_if_empty_array + // --------------------------------------------------------------- + + #[test] + fn remove_optional_key_if_empty_array_removes_empty() { + let mut map = make_map(&[("a", Value::Array(vec![])), ("b", Value::U32(1))]); + map.remove_optional_key_if_empty_array("a"); + assert_eq!(map.get_optional_key("a"), None); + assert_eq!(map.get_optional_key("b"), Some(&Value::U32(1))); + } + + #[test] + fn remove_optional_key_if_empty_array_keeps_non_empty() { + let mut map = make_map(&[("a", Value::Array(vec![Value::U32(1)]))]); + map.remove_optional_key_if_empty_array("a"); + assert!(map.get_optional_key("a").is_some()); + } + + #[test] + fn remove_optional_key_if_empty_array_keeps_non_array() { + let mut map = make_map(&[("a", Value::U32(42))]); + map.remove_optional_key_if_empty_array("a"); + assert_eq!(map.get_optional_key("a"), Some(&Value::U32(42))); + } + + #[test] + fn remove_optional_key_if_empty_array_missing_key_is_noop() { + let mut map = make_map(&[("a", Value::U32(1))]); + map.remove_optional_key_if_empty_array("missing"); + assert_eq!(map.len(), 1); + } + + // --------------------------------------------------------------- + // into_btree_string_map + // --------------------------------------------------------------- + + #[test] + fn into_btree_string_map_valid_conversion() { + let val = Value::Map(make_map(&[("b", Value::U32(2)), ("a", Value::U32(1))])); + let btree = val.into_btree_string_map().unwrap(); + assert_eq!(btree.get("a"), Some(&Value::U32(1))); + assert_eq!(btree.get("b"), Some(&Value::U32(2))); + // BTreeMap should be sorted by key + let keys: Vec<_> = btree.keys().collect(); + assert_eq!(keys, vec!["a", "b"]); + } + + #[test] + fn into_btree_string_map_error_on_non_string_keys() { + let val = Value::Map(vec![(Value::U32(1), Value::U32(2))]); + let result = val.into_btree_string_map(); + assert!(result.is_err()); + } + + #[test] + fn into_btree_string_map_error_on_non_map() { + let val = Value::Bool(true); + let result = val.into_btree_string_map(); + assert!(result.is_err()); + } + + // --------------------------------------------------------------- + // map_ref_into_indexed_string_map + // --------------------------------------------------------------- + + #[test] + fn map_ref_into_indexed_string_map_sorts_by_integer_key() { + let map: ValueMap = vec![ + ( + text("second"), + Value::Map(make_map(&[("pos", Value::U32(2))])), + ), + ( + text("first"), + Value::Map(make_map(&[("pos", Value::U32(1))])), + ), + ( + text("third"), + Value::Map(make_map(&[("pos", Value::U32(3))])), + ), + ]; + let indexed = Value::map_ref_into_indexed_string_map::(&map, "pos").unwrap(); + let keys: Vec<_> = indexed.keys().collect(); + assert_eq!(keys, vec!["first", "second", "third"]); + } + + #[test] + fn map_ref_into_indexed_string_map_error_missing_sort_key() { + let map: ValueMap = vec![( + text("item"), + Value::Map(make_map(&[("other", Value::U32(1))])), + )]; + let result = Value::map_ref_into_indexed_string_map::(&map, "pos"); + assert!(result.is_err()); + } + + // --------------------------------------------------------------- + // get_key / get_optional_key + // --------------------------------------------------------------- + + #[test] + fn get_key_found() { + let map = make_map(&[("x", Value::U32(42))]); + let val = map.get_key("x").unwrap(); + assert_eq!(*val, Value::U32(42)); + } + + #[test] + fn get_key_not_found_errors() { + let map = make_map(&[("x", Value::U32(42))]); + assert!(map.get_key("y").is_err()); + } + + #[test] + fn get_optional_key_none_for_missing() { + let map = make_map(&[("x", Value::U32(42))]); + assert_eq!(map.get_optional_key("y"), None); + } + + #[test] + fn get_optional_key_ignores_non_text_keys() { + let map: ValueMap = vec![(Value::U32(1), Value::U32(2))]; + assert_eq!(map.get_optional_key("1"), None); + } + + // --------------------------------------------------------------- + // remove_key / remove_optional_key + // --------------------------------------------------------------- + + #[test] + fn remove_key_success() { + let mut map = make_map(&[("a", Value::U32(1)), ("b", Value::U32(2))]); + let removed = map.remove_key("a").unwrap(); + assert_eq!(removed, Value::U32(1)); + assert_eq!(map.len(), 1); + } + + #[test] + fn remove_key_not_found_errors() { + let mut map = make_map(&[("a", Value::U32(1))]); + assert!(map.remove_key("missing").is_err()); + } + + #[test] + fn remove_optional_key_returns_none_for_missing() { + let mut map = make_map(&[("a", Value::U32(1))]); + assert_eq!(map.remove_optional_key("missing"), None); + } + + #[test] + fn remove_optional_key_returns_value() { + let mut map = make_map(&[("a", Value::U32(1))]); + assert_eq!(map.remove_optional_key("a"), Some(Value::U32(1))); + assert!(map.is_empty()); + } + + // --------------------------------------------------------------- + // remove_optional_key_value + // --------------------------------------------------------------- + + #[test] + fn remove_optional_key_value_by_value_key() { + let mut map: ValueMap = vec![ + (Value::U32(10), Value::Bool(true)), + (text("x"), Value::Bool(false)), + ]; + let removed = map.remove_optional_key_value(&Value::U32(10)); + assert_eq!(removed, Some(Value::Bool(true))); + assert_eq!(map.len(), 1); + } + + #[test] + fn remove_optional_key_value_not_found() { + let mut map = make_map(&[("a", Value::U32(1))]); + assert_eq!(map.remove_optional_key_value(&Value::U32(99)), None); + } + + // --------------------------------------------------------------- + // insert_string_key_value + // --------------------------------------------------------------- + + #[test] + fn insert_string_key_value_appends() { + let mut map: ValueMap = vec![]; + map.insert_string_key_value("hello".to_string(), Value::Bool(true)); + assert_eq!(map.len(), 1); + assert_eq!(map.get_optional_key("hello"), Some(&Value::Bool(true))); + } + + // --------------------------------------------------------------- + // from_btree_map + // --------------------------------------------------------------- + + #[test] + fn from_btree_map_preserves_entries() { + let mut btree = BTreeMap::new(); + btree.insert("b".to_string(), Value::U32(2)); + btree.insert("a".to_string(), Value::U32(1)); + let map = ValueMap::from_btree_map(btree); + assert_eq!(map.len(), 2); + assert_eq!(map.get_optional_key("a"), Some(&Value::U32(1))); + assert_eq!(map.get_optional_key("b"), Some(&Value::U32(2))); + } + + // --------------------------------------------------------------- + // get_key_by_value_mut_or_insert + // --------------------------------------------------------------- + + #[test] + fn get_key_by_value_mut_or_insert_inserts_new() { + let mut map: ValueMap = vec![]; + let val = map.get_key_by_value_mut_or_insert(&Value::U32(42), Value::Bool(true)); + assert_eq!(*val, Value::Bool(true)); + assert_eq!(map.len(), 1); + } + + #[test] + fn get_key_by_value_mut_or_insert_returns_existing() { + let mut map: ValueMap = vec![(Value::U32(42), Value::Bool(false))]; + let val = map.get_key_by_value_mut_or_insert(&Value::U32(42), Value::Bool(true)); + assert_eq!(*val, Value::Bool(false)); + } + + // --------------------------------------------------------------- + // sort_by_keys_and_inner_maps + // --------------------------------------------------------------- + + #[test] + fn sort_by_keys_and_inner_maps_sorts_recursively() { + let inner = make_map(&[("z", Value::U32(1)), ("a", Value::U32(2))]); + let mut map = make_map(&[("b", Value::Map(inner)), ("a", Value::U32(3))]); + map.sort_by_keys_and_inner_maps(); + // Outer keys should be sorted + assert_eq!(map[0].0, text("a")); + assert_eq!(map[1].0, text("b")); + // Inner map should also be sorted + if let Value::Map(ref inner) = map[1].1 { + assert_eq!(inner[0].0, text("a")); + assert_eq!(inner[1].0, text("z")); + } else { + panic!("expected inner map"); + } + } + + // --------------------------------------------------------------- + // to_btree_ref_string_map + // --------------------------------------------------------------- + + #[test] + fn to_btree_ref_string_map_valid() { + let val = Value::Map(make_map(&[("x", Value::U32(10))])); + let btree = val.to_btree_ref_string_map().unwrap(); + assert_eq!(btree.get("x"), Some(&&Value::U32(10))); + } + + #[test] + fn to_btree_ref_string_map_error_on_non_map() { + let val = Value::U32(1); + assert!(val.to_btree_ref_string_map().is_err()); + } + + #[test] + fn to_btree_ref_string_map_error_on_non_string_key() { + let val = Value::Map(vec![(Value::U32(1), Value::U32(2))]); + assert!(val.to_btree_ref_string_map().is_err()); + } +} + impl Value { /// If the `Value` is a `Map`, returns a the associated `BTreeMap` data as `Ok`. /// Returns `Err(Error::Structure("reason"))` otherwise. diff --git a/packages/rs-scripts/Cargo.toml b/packages/rs-scripts/Cargo.toml index dc639760994..18bb06e33b9 100644 --- a/packages/rs-scripts/Cargo.toml +++ b/packages/rs-scripts/Cargo.toml @@ -15,4 +15,3 @@ base64 = "0.22" chrono = "0.4" hex = "0.4" clap = { version = "4", features = ["derive"] } -serde_json = "1"