mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
10 Commits
regorus-v0
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cf77bb5ea | ||
|
|
6dc3a3e2dc | ||
|
|
e8f126d479 | ||
|
|
bc2fcc1cee | ||
|
|
e865f13102 | ||
|
|
ed6ae465b0 | ||
|
|
bd90453dd3 | ||
|
|
11940ddb04 | ||
|
|
5b7010ba16 | ||
|
|
ba7d29b134 |
78
Cargo.lock
generated
78
Cargo.lock
generated
@@ -119,9 +119,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
@@ -162,9 +162,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
@@ -402,9 +402,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
version = "6.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||
checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
@@ -422,9 +422,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.106",
|
||||
"quote 1.0.45",
|
||||
@@ -433,9 +433,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||
|
||||
[[package]]
|
||||
name = "email_address"
|
||||
@@ -869,9 +869,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.98"
|
||||
version = "0.3.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -944,9 +944,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
@@ -956,9 +956,9 @@ checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
@@ -1431,7 +1431,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"spin 0.10.0",
|
||||
"spin 0.12.0",
|
||||
"test-generator",
|
||||
"thiserror",
|
||||
"url",
|
||||
@@ -1518,9 +1518,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -1580,9 +1580,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.10.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
@@ -1693,9 +1693,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.25.11+spec-1.1.0"
|
||||
version = "0.25.12+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
|
||||
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
@@ -1841,9 +1841,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1854,9 +1854,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||
dependencies = [
|
||||
"quote 1.0.45",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1864,9 +1864,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2 1.0.106",
|
||||
@@ -1877,9 +1877,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1920,9 +1920,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.98"
|
||||
version = "0.3.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
|
||||
checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -2029,9 +2029,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
|
||||
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -2173,18 +2173,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.106",
|
||||
"quote 1.0.45",
|
||||
|
||||
@@ -98,7 +98,7 @@ rand = ["dep:rand"]
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.102", default-features = false }
|
||||
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
|
||||
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
|
||||
serde_json = { version = "1.0.150", default-features = false, features = ["alloc"] }
|
||||
hashbrown = { version = "0.17", default-features = false, features = ["default-hasher"], optional = true }
|
||||
lazy_static = { version = "1.4.0", default-features = false }
|
||||
thiserror = { version = "2.0", default-features = false }
|
||||
@@ -107,7 +107,7 @@ data-encoding = { version = "2.8.0", optional = true, default-features=false, fe
|
||||
num-bigint = { version = "0.4", default-features = false }
|
||||
num-traits = { version = "0.2", default-features = false }
|
||||
parking_lot = { version = "0.12", optional = true }
|
||||
spin = { version = "0.10.0", default-features = false, features = ["mutex", "spin_mutex"] }
|
||||
spin = { version = "0.12.0", default-features = false, features = ["mutex", "spin_mutex"] }
|
||||
|
||||
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
|
||||
regex = {version = "1.12.3", optional = true, default-features = false }
|
||||
|
||||
74
bindings/ffi/Cargo.lock
generated
74
bindings/ffi/Cargo.lock
generated
@@ -98,9 +98,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
@@ -141,9 +141,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
@@ -153,9 +153,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.29.2"
|
||||
version = "0.29.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799"
|
||||
checksum = "c95537b45400390270fae69ac098d057c8f5399001cde9d04f700c105ddfff2d"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"heck",
|
||||
@@ -285,9 +285,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
version = "6.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||
checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
@@ -305,9 +305,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -712,9 +712,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.98"
|
||||
version = "0.3.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -790,9 +790,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
@@ -802,9 +802,9 @@ checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
@@ -1255,9 +1255,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -1314,9 +1314,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.10.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
@@ -1426,7 +1426,7 @@ version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow 1.0.2",
|
||||
"winnow 1.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1535,9 +1535,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1548,9 +1548,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1558,9 +1558,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1571,9 +1571,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1688,9 +1688,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
|
||||
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
@@ -1817,18 +1817,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1837,9 +1837,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
@@ -13,7 +13,7 @@ crate-type = ["cdylib", "staticlib"]
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
regorus = { path = "../..", default-features = false }
|
||||
serde_json = "1.0.140"
|
||||
serde_json = "1.0.150"
|
||||
parking_lot = { version = "0.12", optional = true }
|
||||
|
||||
[profile.release]
|
||||
|
||||
56
bindings/java/Cargo.lock
generated
56
bindings/java/Cargo.lock
generated
@@ -48,9 +48,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
@@ -91,9 +91,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
@@ -199,9 +199,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -598,9 +598,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.98"
|
||||
version = "0.3.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -670,9 +670,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
@@ -682,9 +682,9 @@ checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
@@ -1128,9 +1128,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -1194,9 +1194,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.10.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
@@ -1360,9 +1360,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1373,9 +1373,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1383,9 +1383,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1396,9 +1396,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1639,18 +1639,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -21,6 +21,6 @@ cache = ["regorus/cache"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
serde_json = "1.0.112"
|
||||
serde_json = "1.0.150"
|
||||
jni = "0.22.4"
|
||||
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||
|
||||
56
bindings/python/Cargo.lock
generated
56
bindings/python/Cargo.lock
generated
@@ -48,9 +48,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
@@ -91,9 +91,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
@@ -183,9 +183,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -533,9 +533,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.98"
|
||||
version = "0.3.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -605,9 +605,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
@@ -617,9 +617,9 @@ checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
@@ -1120,9 +1120,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -1170,9 +1170,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.10.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
@@ -1332,9 +1332,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1345,9 +1345,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1355,9 +1355,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1368,9 +1368,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1593,18 +1593,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -25,5 +25,5 @@ anyhow = "1.0"
|
||||
ordered-float = "5.3.0"
|
||||
pyo3 = { version = "0.28.3", features = ["abi3-py310", "anyhow", "extension-module"] }
|
||||
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||
serde_json = "1.0.140"
|
||||
serde_json = "1.0.150"
|
||||
|
||||
|
||||
60
bindings/ruby/Cargo.lock
generated
60
bindings/ruby/Cargo.lock
generated
@@ -48,9 +48,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
@@ -109,9 +109,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
@@ -212,9 +212,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -223,9 +223,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||
|
||||
[[package]]
|
||||
name = "email_address"
|
||||
@@ -571,9 +571,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.98"
|
||||
version = "0.3.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -653,9 +653,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
@@ -688,9 +688,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
@@ -1162,9 +1162,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -1229,9 +1229,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.10.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
@@ -1391,9 +1391,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1404,9 +1404,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1414,9 +1414,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1427,9 +1427,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1652,18 +1652,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
72
bindings/wasm/Cargo.lock
generated
72
bindings/wasm/Cargo.lock
generated
@@ -59,9 +59,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
@@ -102,9 +102,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
@@ -200,9 +200,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -565,9 +565,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.98"
|
||||
version = "0.3.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -643,9 +643,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
@@ -655,9 +655,9 @@ checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
@@ -1120,9 +1120,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -1170,9 +1170,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.10.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
@@ -1344,9 +1344,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1357,9 +1357,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.71"
|
||||
version = "0.4.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
|
||||
checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -1367,9 +1367,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1377,9 +1377,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1390,18 +1390,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.71"
|
||||
version = "0.3.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af5ec93229ad9ccd0a545a516dec76dc276613f278f6a91aa6b463d5b33d42d0"
|
||||
checksum = "74fde991ccdc895cb7fbaa14b137d62af74d9011be67b71c694bfc40edd3119c"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cast",
|
||||
@@ -1421,9 +1421,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.71"
|
||||
version = "0.3.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c81b9fef827e575e0e54431736d1baa0d700315d8c62cfef1f61fa3aad0cbeb"
|
||||
checksum = "e925354648d2a4d1bf205412e36d520a800280622eef4719678d268e5d40e978"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1432,9 +1432,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-shared"
|
||||
version = "0.2.121"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f4d8ae7ad5440360e9799dfd42857d126454a88441ddf72d288ef83fa47f527"
|
||||
checksum = "684365b586a9a6256c1cc3544eee8680de48d6041142f581776ec7b139622ae9"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
@@ -1672,18 +1672,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -42,7 +42,7 @@ coverage = ["regorus/coverage"]
|
||||
[dependencies]
|
||||
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
serde_json = "1.0.150"
|
||||
wasm-bindgen = "0.2.100"
|
||||
serde-wasm-bindgen = "0.6"
|
||||
# Specify uuid as a mandatory dependency so as to enable `js` feature which is now required
|
||||
@@ -55,7 +55,7 @@ getrandom03 = { package = "getrandom", version = "0.3.1", features = ["std", "wa
|
||||
getrandom = { version = "0.4.2", features = ["wasm_js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.71"
|
||||
wasm-bindgen-test = "0.3.72"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }
|
||||
|
||||
84
docs/value/object.md
Normal file
84
docs/value/object.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Object
|
||||
|
||||
Opaque container for `Value::Object`'s key→value storage, enabling
|
||||
alternative backends without call-site changes.
|
||||
|
||||
## Design
|
||||
|
||||
`Object` wraps the storage for a key→value collection of `Value`s and
|
||||
provides a curated set of methods (`get`, `insert`, `remove`, `iter`,
|
||||
`iter_sorted`, `cursor`, serde). The backing store is private; callers
|
||||
never see or pattern-match on it, so the representation can change
|
||||
without rippling through call sites.
|
||||
|
||||
Multiple backends can coexist at runtime. Because the backing store is
|
||||
private, different `Object` instances in the same process can use
|
||||
different implementations — e.g., a lazy DB-backed object for `input`,
|
||||
inline small-map objects for SARIF location records, and a regular
|
||||
sorted map elsewhere — all interoperating through the same opaque
|
||||
type. This is stronger than the typical Cargo-feature-selected backend
|
||||
seen in precedent crates.
|
||||
|
||||
Iteration is split intentionally. `iter()` makes no ordering promise,
|
||||
which lets backends that don't keep entries sorted skip any sort work.
|
||||
`iter_sorted()` returns entries in `Value` order and is what
|
||||
serialization and `Ord` rely on for deterministic output. Cursor types
|
||||
add resumable, incremental traversal for the RVM iteration state
|
||||
without leaking iterator internals.
|
||||
|
||||
`Ord` and `PartialOrd` are defined against `iter_sorted()` rather than
|
||||
derived from the storage. Two `Object`s built on different backends —
|
||||
or with different insertion histories — compare equal whenever their
|
||||
sorted entries match, so changing the backend never changes observable
|
||||
comparison results.
|
||||
|
||||
## Precedents
|
||||
|
||||
Other crates that hide storage behind a stable API so the implementation
|
||||
can change without breaking callers:
|
||||
|
||||
- **`serde_json::Map`** — opaque newtype allowing cargo-feature based
|
||||
swap between `BTreeMap` (canonical order) and `IndexMap` (insertion
|
||||
order).
|
||||
- **`toml::Table`** — opaque newtype allowing cargo-feature based swap
|
||||
between `BTreeMap` and `IndexMap`.
|
||||
- **`simdjson` DOM** — opaque tree that lazily materializes nodes on
|
||||
access instead of parsing the whole document up front.
|
||||
|
||||
## Use cases
|
||||
|
||||
- **SARIF small-object pressure** — SARIF reports contain millions of
|
||||
small objects (location records, rule references, message arguments),
|
||||
most with 2-5 keys. A small-map-optimized backend (inline storage
|
||||
for ≤N entries, heap above) eliminates per-object BTreeMap allocation
|
||||
for the common case.
|
||||
|
||||
- **Kubernetes admission policies** — large, deeply-nested resource
|
||||
objects (Pod specs, CRDs) where policies typically touch a handful
|
||||
of paths. A lazy-materializing backend (`LazyObjectProvider` over
|
||||
the incoming JSON) parses only the accessed subtrees.
|
||||
|
||||
- **Azure Policy aliases** — ARM exposes the same logical property
|
||||
under multiple aliases (e.g. paths like
|
||||
`Microsoft.Compute/virtualMachines/storageProfile.osDisk.managedDisk.id`).
|
||||
An alias-aware backend resolves lookups across canonical and alias
|
||||
forms without rewriting every policy.
|
||||
|
||||
- **Azure Policy case-insensitive compare** — ARM property names are
|
||||
case-preserving but case-insensitive on lookup (`tags.Environment`
|
||||
and `tags.environment` resolve identically). A case-insensitive
|
||||
backend centralizes this once at the storage layer instead of at
|
||||
every comparison site.
|
||||
|
||||
- **External data sources** — `input` or `data` backed by a database
|
||||
query, CBOR slice, REST endpoint, or other streaming source via a
|
||||
`LazyObjectProvider`. Entries materialize on demand; the policy
|
||||
only pays for what it touches.
|
||||
|
||||
- **Eval-time temporaries** — objects constructed during evaluation
|
||||
(comprehensions, intermediate rule results) on a bumpalo arena.
|
||||
The whole arena drops at query end with zero per-entry free cost.
|
||||
|
||||
- **Host-language interop** — Python dicts or JS objects accessed via
|
||||
FFI callbacks from the embedding application, without copying into
|
||||
Rust on every binding boundary.
|
||||
@@ -319,7 +319,7 @@ pub fn resolve_path(root: &Value, path: &str) -> Value {
|
||||
match ¤t {
|
||||
Value::Object(map) => {
|
||||
let mut next = None;
|
||||
for (key, value) in map.iter() {
|
||||
for (key, value) in map.iter_sorted() {
|
||||
if let Value::String(ref key_str) = *key {
|
||||
if strings::keys::eq(key_str, &segment) {
|
||||
next = Some(value.clone());
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Object;
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -72,7 +72,7 @@ fn fn_intersection(
|
||||
// Intersection of objects: keep key-value pairs from the first
|
||||
// object only when the key exists in every other object AND
|
||||
// the value is equal across all of them.
|
||||
let mut result: BTreeMap<Value, Value> = first.as_ref().clone();
|
||||
let mut result: Object = first.as_ref().clone();
|
||||
for arg in rest {
|
||||
let Value::Object(ref other) = *arg else {
|
||||
return Ok(Value::Undefined);
|
||||
@@ -114,7 +114,7 @@ fn fn_union(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool)
|
||||
Value::Object(_) => {
|
||||
// Union of objects: recursive merge. Nested objects are merged
|
||||
// recursively; all other types (including arrays) use last-writer-wins.
|
||||
let mut result = BTreeMap::<Value, Value>::new();
|
||||
let mut result = Object::new();
|
||||
for arg in args {
|
||||
let Value::Object(ref obj) = *arg else {
|
||||
return Ok(Value::Undefined);
|
||||
@@ -264,7 +264,7 @@ fn fn_create_object(
|
||||
);
|
||||
}
|
||||
|
||||
let mut map = BTreeMap::<Value, Value>::new();
|
||||
let mut map = Object::new();
|
||||
|
||||
for pair in args.chunks(2) {
|
||||
#[allow(clippy::pattern_type_mismatch)]
|
||||
@@ -280,9 +280,9 @@ fn fn_create_object(
|
||||
|
||||
/// Recursively merge two objects. Nested objects are merged; everything
|
||||
/// else (including arrays) uses the value from `incoming`.
|
||||
fn merge_objects(base: &BTreeMap<Value, Value>, overlay: &BTreeMap<Value, Value>) -> Value {
|
||||
fn merge_objects(base: &Object, overlay: &Object) -> Value {
|
||||
let mut result = base.clone();
|
||||
for (k, v) in overlay {
|
||||
for (k, v) in overlay.iter() {
|
||||
#[allow(clippy::needless_borrowed_reference)]
|
||||
let merged = match (result.get(k), v) {
|
||||
(Some(&Value::Object(ref prev)), &Value::Object(ref next)) => merge_objects(prev, next),
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Object;
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::{String, ToString as _};
|
||||
use alloc::vec::Vec;
|
||||
use anyhow::Result;
|
||||
@@ -84,8 +84,8 @@ fn fn_items(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool)
|
||||
return Ok(Value::Undefined);
|
||||
};
|
||||
let mut result = Vec::with_capacity(obj.len());
|
||||
for (k, v) in obj.as_ref() {
|
||||
let mut entry = BTreeMap::<Value, Value>::new();
|
||||
for (k, v) in obj.iter_sorted() {
|
||||
let mut entry = Object::new();
|
||||
entry.insert(Value::from("key"), k.clone());
|
||||
entry.insert(Value::from("value"), v.clone());
|
||||
result.push(Value::Object(Rc::new(entry)));
|
||||
|
||||
@@ -308,7 +308,7 @@ fn urlquery_encode_object(
|
||||
|
||||
{
|
||||
let mut pairs = url.query_pairs_mut();
|
||||
for (key, value) in obj.iter() {
|
||||
for (key, value) in obj.iter_sorted() {
|
||||
let key = ensure_string(name, ¶ms[0], key)?;
|
||||
match value {
|
||||
Value::String(v) => {
|
||||
|
||||
@@ -7,10 +7,11 @@ use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
use crate::builtins::utils::{enforce_limit, ensure_args_count, ensure_object};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Object;
|
||||
use crate::value::Value;
|
||||
use crate::*;
|
||||
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::collections::BTreeSet;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
@@ -80,7 +81,7 @@ fn reachable(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) ->
|
||||
}
|
||||
|
||||
fn visit(
|
||||
graph: &BTreeMap<Value, Value>,
|
||||
graph: &Object,
|
||||
visited: &mut BTreeSet<Value>,
|
||||
node: &Value,
|
||||
path: &mut Vec<Value>,
|
||||
@@ -211,7 +212,7 @@ fn walk_visit(path: &mut Vec<Value>, value: &Value, paths: &mut Vec<Value>) -> R
|
||||
}
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
for (key, value) in obj.iter() {
|
||||
for (key, value) in obj.iter_sorted() {
|
||||
path.push(key.clone());
|
||||
// Guard path stack growth while traversing object entries.
|
||||
enforce_limit()?;
|
||||
|
||||
@@ -205,7 +205,7 @@ fn merge_filters(
|
||||
let vref = match f {
|
||||
Value::Object(obj) => {
|
||||
let obj = Rc::make_mut(obj);
|
||||
let entry = obj.entry(p.clone()).or_insert_with(Value::new_object);
|
||||
let entry = obj.get_or_insert_with(p.clone(), Value::new_object);
|
||||
// Guard filter map growth when creating nested objects.
|
||||
enforce_limit()?;
|
||||
entry
|
||||
|
||||
@@ -207,7 +207,7 @@ fn to_string(v: &Value, unescape: bool) -> String {
|
||||
}
|
||||
Value::Object(o) => {
|
||||
"{".to_owned()
|
||||
+ &o.iter()
|
||||
+ &o.iter_sorted()
|
||||
.map(|(k, v)| to_string(k, true) + ": " + &to_string(v, true))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
@@ -568,7 +568,7 @@ fn replace_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -
|
||||
let mut s = ensure_string(name, ¶ms[1], &args[1])?;
|
||||
|
||||
let span = params[0].span();
|
||||
for item in obj.as_ref().iter() {
|
||||
for item in obj.as_ref().iter_sorted() {
|
||||
match item {
|
||||
(Value::String(k), Value::String(v)) => {
|
||||
s = s.replace(k.as_ref(), v.as_ref()).into();
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::lexer::Span;
|
||||
use crate::number::Number;
|
||||
use crate::value::Object;
|
||||
use crate::Rc;
|
||||
use crate::Value;
|
||||
use crate::*;
|
||||
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::collections::BTreeSet;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
@@ -168,7 +169,7 @@ pub fn ensure_set(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<BTreeSet<Value>>
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ensure_object(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<BTreeMap<Value, Value>>> {
|
||||
pub fn ensure_object(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<Object>> {
|
||||
Ok(match v {
|
||||
Value::Object(o) => o,
|
||||
_ => {
|
||||
|
||||
@@ -28,7 +28,6 @@ use crate::{Expression, Extension, Location, QueryResult, QueryResults};
|
||||
use crate::query::traversal::traverse;
|
||||
|
||||
use crate::Rc;
|
||||
use alloc::collections::btree_map::Entry as BTreeMapEntry;
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use core::ops::Bound::*;
|
||||
@@ -587,7 +586,7 @@ impl Interpreter {
|
||||
} else {
|
||||
format!("{ref_path}.{index}.{}", path.join("."))
|
||||
};
|
||||
self.ensure_rule_evaluated(ref_path)?;
|
||||
self.ensure_matching_rules_for_dynamic_data_index(&ref_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1312,10 +1311,10 @@ impl Interpreter {
|
||||
*obj = Value::new_object();
|
||||
}
|
||||
|
||||
obj = obj
|
||||
.as_object_mut()?
|
||||
.entry(Value::String(p.to_string().into()))
|
||||
.or_insert(Value::new_object());
|
||||
obj = obj.as_object_mut()?.get_or_insert_with(
|
||||
Value::String(p.to_string().into()),
|
||||
Value::new_object,
|
||||
);
|
||||
}
|
||||
*obj = value;
|
||||
// Mark modified rules as processed.
|
||||
@@ -1682,8 +1681,7 @@ impl Interpreter {
|
||||
let set = obj
|
||||
.as_object_mut()
|
||||
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
|
||||
.entry(p)
|
||||
.or_insert(Value::new_set())
|
||||
.get_or_insert_with(p, Value::new_set)
|
||||
.as_set_mut()
|
||||
.map_err(|_| anyhow!(span.error("previous value is not a set")))?;
|
||||
set.append(value.as_set_mut()?);
|
||||
@@ -1691,20 +1689,13 @@ impl Interpreter {
|
||||
let obj = obj
|
||||
.as_object_mut()
|
||||
.map_err(|_| anyhow!(span.error("previous value is not an object")))?;
|
||||
match obj.entry(p) {
|
||||
BTreeMapEntry::Vacant(v) => {
|
||||
if value != Value::Undefined {
|
||||
v.insert(value);
|
||||
} else {
|
||||
// TODO: clean this assumption between Undefined vs Object.
|
||||
v.insert(Value::new_object());
|
||||
}
|
||||
}
|
||||
BTreeMapEntry::Occupied(o) => {
|
||||
if o.get() != &value && value != Value::Undefined {
|
||||
bail!(span
|
||||
.error("complete rules should not produce multiple outputs"))
|
||||
}
|
||||
if value == Value::Undefined {
|
||||
// TODO: clean this assumption between Undefined vs Object.
|
||||
obj.get_or_insert_with(p, Value::new_object);
|
||||
} else {
|
||||
let existing = obj.get_or_insert_with(p, || value.clone());
|
||||
if *existing != value {
|
||||
bail!(span.error("complete rules should not produce multiple outputs"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1713,8 +1704,7 @@ impl Interpreter {
|
||||
obj = obj
|
||||
.as_object_mut()
|
||||
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
|
||||
.entry(p)
|
||||
.or_insert(Value::new_object());
|
||||
.get_or_insert_with(p, Value::new_object);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -1822,8 +1812,7 @@ impl Interpreter {
|
||||
let set = ctx_mut
|
||||
.rule_value
|
||||
.as_object_mut()?
|
||||
.entry(Value::from_array(comps))
|
||||
.or_insert(Value::new_set());
|
||||
.get_or_insert_with(Value::from_array(comps), Value::new_set);
|
||||
if output != Value::Undefined {
|
||||
set.as_set_mut()?.insert(output);
|
||||
return Ok(true);
|
||||
@@ -1832,20 +1821,13 @@ impl Interpreter {
|
||||
}
|
||||
|
||||
// Non-set rule.
|
||||
match ctx_mut
|
||||
.rule_value
|
||||
.as_object_mut()?
|
||||
.entry(Value::from_array(comps))
|
||||
{
|
||||
BTreeMapEntry::Vacant(v) => {
|
||||
v.insert(output);
|
||||
}
|
||||
BTreeMapEntry::Occupied(o) if o.get() != &output => bail!(rule_ref
|
||||
let key = Value::from_array(comps);
|
||||
let obj_mut = ctx_mut.rule_value.as_object_mut()?;
|
||||
let existing = obj_mut.get_or_insert_with(key, || output.clone());
|
||||
if *existing != output {
|
||||
bail!(rule_ref
|
||||
.span()
|
||||
.error("rules must not produce multiple outputs")),
|
||||
_ => {
|
||||
// Rule produced same value.
|
||||
}
|
||||
.error("rules must not produce multiple outputs"));
|
||||
}
|
||||
|
||||
return Ok(true);
|
||||
@@ -2471,7 +2453,7 @@ impl Interpreter {
|
||||
}
|
||||
Value::Object(map) => {
|
||||
s.push('{');
|
||||
for (idx, (k, entry_value)) in map.iter().enumerate() {
|
||||
for (idx, (k, entry_value)) in map.iter_sorted().enumerate() {
|
||||
if idx > 0 {
|
||||
s.push_str(", ");
|
||||
}
|
||||
@@ -2978,6 +2960,136 @@ impl Interpreter {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensures all rule/default-rule paths matching a dynamic data lookup are evaluated.
|
||||
/// Matches both exact path (`data.a.b`) and descendants with the `data.a.b.` prefix.
|
||||
fn ensure_matching_rules_for_dynamic_data_index(&mut self, path: &str) -> Result<()> {
|
||||
self.check_execution_time()?;
|
||||
let path_prefix = format!("{path}.");
|
||||
let mut matching_paths: Vec<String> = self
|
||||
.compiled_policy
|
||||
.default_rules
|
||||
.keys()
|
||||
.chain(self.compiled_policy.rules.keys())
|
||||
.filter(|rule_path| *rule_path == path || rule_path.starts_with(&path_prefix))
|
||||
.cloned()
|
||||
.collect();
|
||||
matching_paths.sort();
|
||||
matching_paths.dedup();
|
||||
|
||||
for rule_path in matching_paths {
|
||||
self.ensure_rule_evaluated(rule_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds a canonical `data` path from field components.
|
||||
/// For an empty field list, returns `"data"`.
|
||||
fn build_data_path(fields: &[&str]) -> String {
|
||||
if fields.is_empty() {
|
||||
"data".to_string()
|
||||
} else {
|
||||
format!("data.{}", fields.join("."))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` when `prefix` matches `path` on segment boundaries.
|
||||
///
|
||||
/// Examples:
|
||||
/// - `path_is_prefix("data.auth", "data.auth") == true`
|
||||
/// - `path_is_prefix("data.auth", "data.auth.allow") == true`
|
||||
/// - `path_is_prefix("data.auth", "data.authorization") == false`
|
||||
fn path_is_prefix(prefix: &str, path: &str) -> bool {
|
||||
if path == prefix {
|
||||
return true;
|
||||
}
|
||||
path.get(prefix.len()..)
|
||||
.is_some_and(|suffix| suffix.starts_with('.'))
|
||||
}
|
||||
|
||||
/// Checks whether a `requested_path` can contain values produced by an active rule.
|
||||
///
|
||||
/// The active rule path is logically `module_path.rule_path`, but this check avoids
|
||||
/// allocating that joined string in tight evaluation loops.
|
||||
///
|
||||
/// Examples:
|
||||
/// - request `data` matches module `data.authz` (module expansion needed)
|
||||
/// - request `data.authz` matches rule `allow`
|
||||
/// - request `data.authz.allow` matches rule `allow`
|
||||
/// - request `data.auth` does not match module `data.authz`
|
||||
fn request_matches_active_rule_path(
|
||||
requested_path: &str,
|
||||
module_path: &str,
|
||||
rule_path: &str,
|
||||
) -> bool {
|
||||
if Self::path_is_prefix(requested_path, module_path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
requested_path
|
||||
.strip_prefix(module_path)
|
||||
.and_then(|suffix| suffix.strip_prefix('.'))
|
||||
.is_some_and(|requested_rule_prefix| {
|
||||
Self::path_is_prefix(requested_rule_prefix, rule_path)
|
||||
})
|
||||
}
|
||||
|
||||
fn should_defer_module_eval_for_path(&self, requested_path: &str) -> Result<bool> {
|
||||
for active_rule in &self.active_rules {
|
||||
let module = self.get_rule_module(active_rule)?;
|
||||
let module_path = get_path_string(&module.package.refr, Some("data"))?;
|
||||
let rule_path = get_path_string(Self::get_rule_refr(active_rule), None)?;
|
||||
if Self::request_matches_active_rule_path(requested_path, &module_path, &rule_path) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Resolves `data.<fields...>` while preserving correct rule semantics.
|
||||
/// When a rule is already active, this avoids eager module-wide evaluation so legitimate
|
||||
/// cross-package references are not misidentified as cyclic recursion.
|
||||
fn lookup_data_path(&mut self, fields: &[&str]) -> Result<Value> {
|
||||
if self.is_processed(fields)? {
|
||||
return Ok(Self::get_value_chained(self.data.clone(), fields));
|
||||
}
|
||||
|
||||
// If "data" is used in a query without any fields, then evaluate all modules.
|
||||
if fields.is_empty() && self.active_rules.is_empty() {
|
||||
for module in self.compiled_policy.modules.clone().iter() {
|
||||
for rule in &module.policy {
|
||||
self.eval_rule(module, rule)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// While a rule is active, avoid eagerly evaluating all matching modules.
|
||||
// This prevents re-entry through sibling rules or other modules from being
|
||||
// misclassified as cyclic recursion.
|
||||
let requested_path = Self::build_data_path(fields);
|
||||
if self.active_rules.is_empty()
|
||||
|| !self.should_defer_module_eval_for_path(&requested_path)?
|
||||
{
|
||||
self.ensure_module_evaluated(requested_path.clone())?;
|
||||
}
|
||||
|
||||
for i in (1..=fields.len()).rev() {
|
||||
let prefix = fields.iter().take(i).copied().collect::<Vec<_>>();
|
||||
let prefix_path = Self::build_data_path(&prefix);
|
||||
if self.compiled_policy.rules.contains_key(&prefix_path)
|
||||
|| self
|
||||
.compiled_policy
|
||||
.default_rules
|
||||
.contains_key(&prefix_path)
|
||||
{
|
||||
self.ensure_rule_evaluated(prefix_path)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self::get_value_chained(self.data.clone(), fields))
|
||||
}
|
||||
|
||||
fn is_processed(&self, path: &[&str]) -> Result<bool> {
|
||||
let mut obj = &self.processed_paths;
|
||||
for p in path {
|
||||
@@ -3028,39 +3140,7 @@ impl Interpreter {
|
||||
|
||||
// Ensure that rules are evaluated
|
||||
if name.text() == "data" {
|
||||
if self.is_processed(fields)? {
|
||||
return Ok(Self::get_value_chained(self.data.clone(), fields));
|
||||
}
|
||||
|
||||
// If "data" is used in a query, without any fields, then evaluate all the modules.
|
||||
if fields.is_empty() && self.active_rules.is_empty() {
|
||||
for module in self.compiled_policy.modules.clone().iter() {
|
||||
for rule in &module.policy {
|
||||
self.eval_rule(module, rule)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With modifiers may be used to specify part of a module that that not yet been
|
||||
// evaluated. Therefore ensure that module is evaluated first.
|
||||
let requested_path = format!("data.{}", fields.join("."));
|
||||
self.ensure_module_evaluated(requested_path.clone())?;
|
||||
|
||||
for i in (1..=fields.len()).rev() {
|
||||
let prefix = fields.iter().take(i).copied().collect::<Vec<_>>();
|
||||
let prefix_path = format!("data.{}", prefix.join("."));
|
||||
if self.compiled_policy.rules.contains_key(&prefix_path)
|
||||
|| self
|
||||
.compiled_policy
|
||||
.default_rules
|
||||
.contains_key(&prefix_path)
|
||||
{
|
||||
self.ensure_rule_evaluated(prefix_path)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self::get_value_chained(self.data.clone(), fields))
|
||||
self.lookup_data_path(fields)
|
||||
} else if !self.compiled_policy.modules.is_empty() {
|
||||
let module = self.current_module()?;
|
||||
let parsed_path = Parser::get_path_ref_components(&module.package.refr)?;
|
||||
@@ -3110,6 +3190,17 @@ impl Interpreter {
|
||||
|
||||
if !found {
|
||||
if let Some(imported_var) = self.compiled_policy.imports.get(&rule_path).cloned() {
|
||||
if let Ok(import_path) = get_path_string(&imported_var, None) {
|
||||
if import_path == "data" || import_path.starts_with("data.") {
|
||||
let combined_path = if fields.is_empty() {
|
||||
import_path
|
||||
} else {
|
||||
format!("{}.{}", import_path, fields.join("."))
|
||||
};
|
||||
let data_fields: Vec<&str> = combined_path.split('.').skip(1).collect();
|
||||
return self.lookup_data_path(&data_fields);
|
||||
}
|
||||
}
|
||||
return Ok(Self::get_value_chained(
|
||||
self.eval_expr(&imported_var)?,
|
||||
fields,
|
||||
|
||||
@@ -213,10 +213,10 @@ pub fn denormalize_with_aliases(
|
||||
// Phase 4: Attach properties to result.
|
||||
if !properties.is_empty() {
|
||||
if let Some(Value::Object(existing_rc)) = result.get_mut("properties") {
|
||||
// Merge directly into the BTreeMap, avoiding full ObjMap round-trip.
|
||||
// Merge directly into the Object, avoiding full ObjMap round-trip.
|
||||
let existing = Rc::make_mut(existing_rc);
|
||||
for (k, v) in properties {
|
||||
existing.entry(Value::String(k)).or_insert(v);
|
||||
existing.get_or_insert_with(Value::String(k), || v);
|
||||
}
|
||||
} else {
|
||||
obj_insert(&mut result, "properties", make_value(properties));
|
||||
|
||||
@@ -7,6 +7,7 @@ use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::value::Object;
|
||||
use crate::Value;
|
||||
|
||||
use super::super::obj_map::{make_value, new_map, obj_insert, val_str, ObjMap};
|
||||
@@ -141,7 +142,7 @@ fn rewrap_nested_array(
|
||||
/// BTreeMap-native recursion for nested sub-resource array re-wrapping,
|
||||
/// avoiding ObjMap round-trips on each array element.
|
||||
fn rewrap_nested_array_in_btree(
|
||||
btree: &mut alloc::collections::BTreeMap<Value, Value>,
|
||||
btree: &mut Object,
|
||||
parent_parts: &[&str],
|
||||
array_name: &str,
|
||||
envelope_fields: &BTreeSet<String>,
|
||||
@@ -187,10 +188,7 @@ fn rewrap_nested_array_in_btree(
|
||||
}
|
||||
|
||||
/// Find a key in a BTreeMap using case-insensitive comparison.
|
||||
fn find_key_ci_btree(
|
||||
btree: &alloc::collections::BTreeMap<Value, Value>,
|
||||
key: &str,
|
||||
) -> Option<Value> {
|
||||
fn find_key_ci_btree(btree: &Object, key: &str) -> Option<Value> {
|
||||
btree
|
||||
.keys()
|
||||
.find(|k| val_str(k).is_some_and(|s| s.eq_ignore_ascii_case(key)))
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::value::Object;
|
||||
use crate::Value;
|
||||
|
||||
use super::super::obj_map::{
|
||||
obj_get, obj_get_mut, obj_insert, set_nested_in_btree, set_nested_lowercased,
|
||||
set_nested_verbatim, ObjMap,
|
||||
obj_get, obj_get_mut, obj_insert, set_nested, set_nested_lowercased, set_nested_verbatim,
|
||||
ObjMap,
|
||||
};
|
||||
use super::super::types::PrecomputedRemap;
|
||||
|
||||
@@ -118,7 +119,7 @@ fn apply_remap_at_depth(
|
||||
/// BTreeMap-native recursion for element-level remap, avoiding ObjMap
|
||||
/// round-trips on each array element.
|
||||
fn remap_at_depth_in_btree(
|
||||
btree: &mut alloc::collections::BTreeMap<Value, Value>,
|
||||
btree: &mut Object,
|
||||
array_chain: &[Vec<String>],
|
||||
depth: usize,
|
||||
source_field: &str,
|
||||
@@ -177,12 +178,7 @@ fn remap_at_depth_in_btree(
|
||||
}
|
||||
|
||||
/// Remap a value between dotted paths directly in a BTreeMap.
|
||||
fn remap_deep_field_in_btree(
|
||||
btree: &mut alloc::collections::BTreeMap<Value, Value>,
|
||||
source: &str,
|
||||
target: &str,
|
||||
lowercase: bool,
|
||||
) {
|
||||
fn remap_deep_field_in_btree(btree: &mut Object, source: &str, target: &str, lowercase: bool) {
|
||||
let val = match read_dotted_path_btree(btree, source) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
@@ -198,14 +194,11 @@ fn remap_deep_field_in_btree(
|
||||
}
|
||||
return;
|
||||
}
|
||||
set_nested_in_btree(btree, &segments, val, lowercase);
|
||||
set_nested(btree, &segments, val, lowercase);
|
||||
}
|
||||
|
||||
/// Read a value at a dotted path from a BTreeMap.
|
||||
fn read_dotted_path_btree(
|
||||
btree: &alloc::collections::BTreeMap<Value, Value>,
|
||||
path: &str,
|
||||
) -> Option<Value> {
|
||||
fn read_dotted_path_btree(btree: &Object, path: &str) -> Option<Value> {
|
||||
let segments: Vec<&str> = path.split('.').collect();
|
||||
let first = segments.first()?;
|
||||
let mut cur: &Value = btree.get(&Value::from(*first))?;
|
||||
|
||||
@@ -13,6 +13,7 @@ mod flatten;
|
||||
// Re-export items used by the denormalizer.
|
||||
pub(crate) use element_remap::{apply_element_remap, ElementRemap};
|
||||
|
||||
use crate::value::Object;
|
||||
use crate::Value;
|
||||
|
||||
use super::obj_map::{
|
||||
@@ -109,7 +110,7 @@ pub fn normalize_with_aliases(
|
||||
/// Merge `properties` fields into the result map, skipping keys that already
|
||||
/// exist.
|
||||
fn merge_properties(
|
||||
obj: &alloc::collections::BTreeMap<Value, Value>,
|
||||
obj: &Object,
|
||||
result: &mut ObjMap,
|
||||
sub_arrays: Option<&alloc::collections::BTreeSet<alloc::string::String>>,
|
||||
) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! Lightweight string-keyed map used during normalization/denormalization.
|
||||
//!
|
||||
//! Internally uses `hashbrown::HashMap<Rc<str>, Value>` for O(1) lookups,
|
||||
//! then converts to `Value::Object` (a `BTreeMap<Value, Value>`) only at
|
||||
//! then converts to `Value::Object` (an `Object`) only at
|
||||
//! the output boundary via [`make_value`].
|
||||
|
||||
use alloc::string::String;
|
||||
@@ -12,6 +12,7 @@ use alloc::vec::Vec;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::value::Object;
|
||||
use crate::Rc;
|
||||
use crate::Value;
|
||||
|
||||
@@ -81,14 +82,13 @@ pub fn obj_remove(map: &mut ObjMap, key: &str) -> Option<Value> {
|
||||
/// Convert an [`ObjMap`] into a [`Value::Object`].
|
||||
///
|
||||
/// Keys are converted from `Rc<str>` to `Value::String` and inserted into
|
||||
/// a `BTreeMap` to match the `Value::Object` representation.
|
||||
/// an `Object` to match the `Value::Object` representation.
|
||||
pub fn make_value(map: ObjMap) -> Value {
|
||||
use alloc::collections::BTreeMap;
|
||||
let mut btree = BTreeMap::new();
|
||||
for (k, v) in map {
|
||||
btree.insert(Value::String(k), v);
|
||||
}
|
||||
Value::Object(Rc::new(btree))
|
||||
let obj: Object = map
|
||||
.into_iter()
|
||||
.map(|(k, v)| (Value::String(k), v))
|
||||
.collect();
|
||||
Value::Object(Rc::new(obj))
|
||||
}
|
||||
|
||||
/// Convert a `Vec<Value>` into a `Value::Array`.
|
||||
@@ -115,14 +115,14 @@ pub fn extract_type_field(resource: &Value) -> Option<&str> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a `Value::Object` (BTreeMap<Value, Value>) into an [`ObjMap`].
|
||||
/// Convert a `Value::Object` (Object) into an [`ObjMap`].
|
||||
///
|
||||
/// Non-string keys are silently skipped.
|
||||
#[allow(dead_code)]
|
||||
pub fn value_to_obj_map(value: &Value) -> Option<ObjMap> {
|
||||
let btree = value.as_object().ok()?;
|
||||
let mut map = ObjMap::with_capacity(btree.len());
|
||||
for (k, v) in btree.iter() {
|
||||
let obj = value.as_object().ok()?;
|
||||
let mut map = ObjMap::with_capacity(obj.len());
|
||||
for (k, v) in obj.iter() {
|
||||
if let Value::String(s) = k {
|
||||
map.insert(Rc::clone(s), v.clone());
|
||||
}
|
||||
@@ -194,7 +194,7 @@ fn set_nested_inner(obj: &mut ObjMap, segments: &[&str], value: Value, lowercase
|
||||
// Descend directly into the BTreeMap, avoiding ObjMap round-trip.
|
||||
if let Some(Value::Object(inner_rc)) = obj.get_mut(&*seg) {
|
||||
let inner_btree = Rc::make_mut(inner_rc);
|
||||
set_nested_in_btree(
|
||||
set_nested(
|
||||
inner_btree,
|
||||
segments.get(1..).unwrap_or_default(),
|
||||
value,
|
||||
@@ -203,17 +203,12 @@ fn set_nested_inner(obj: &mut ObjMap, segments: &[&str], value: Value, lowercase
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a value at a path directly in a `BTreeMap<Value, Value>`, creating
|
||||
/// Set a value at a path directly in an `Object`, creating
|
||||
/// intermediate `Value::Object` nodes as needed.
|
||||
///
|
||||
/// This avoids the `btree_to_obj_map` / `obj_map_to_btree` round-trip that
|
||||
/// would clone every sibling entry at each nesting level.
|
||||
pub fn set_nested_in_btree(
|
||||
btree: &mut alloc::collections::BTreeMap<Value, Value>,
|
||||
segments: &[&str],
|
||||
value: Value,
|
||||
lowercase: bool,
|
||||
) {
|
||||
pub fn set_nested(obj: &mut Object, segments: &[&str], value: Value, lowercase: bool) {
|
||||
let Some(&first) = segments.first() else {
|
||||
return;
|
||||
};
|
||||
@@ -226,18 +221,18 @@ pub fn set_nested_in_btree(
|
||||
let key_val = Value::String(Rc::clone(&key_rc));
|
||||
|
||||
if segments.len() == 1 {
|
||||
btree.insert(key_val, value);
|
||||
obj.insert(key_val, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure an intermediate object exists.
|
||||
if !btree.contains_key(&key_val) {
|
||||
btree.insert(key_val.clone(), make_value(new_map()));
|
||||
if !obj.contains_key(&key_val) {
|
||||
obj.insert(key_val.clone(), make_value(new_map()));
|
||||
}
|
||||
|
||||
if let Some(Value::Object(inner_rc)) = btree.get_mut(&key_val) {
|
||||
if let Some(Value::Object(inner_rc)) = obj.get_mut(&key_val) {
|
||||
let inner = Rc::make_mut(inner_rc);
|
||||
set_nested_in_btree(
|
||||
set_nested(
|
||||
inner,
|
||||
segments.get(1..).unwrap_or_default(),
|
||||
value,
|
||||
@@ -352,20 +347,15 @@ fn remove_field_at_depth(obj: &mut ObjMap, array_chain: &[Vec<String>], depth: u
|
||||
for elem in inner.iter_mut() {
|
||||
if let Value::Object(obj_rc) = elem {
|
||||
let inner_btree = Rc::make_mut(obj_rc);
|
||||
remove_field_at_depth_in_btree(
|
||||
inner_btree,
|
||||
array_chain,
|
||||
depth.saturating_add(1),
|
||||
field,
|
||||
);
|
||||
remove_field_at_depth_obj(inner_btree, array_chain, depth.saturating_add(1), field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// BTreeMap-native recursion for element-level field removal.
|
||||
fn remove_field_at_depth_in_btree(
|
||||
btree: &mut alloc::collections::BTreeMap<Value, Value>,
|
||||
/// Object-native recursion for element-level field removal.
|
||||
fn remove_field_at_depth_obj(
|
||||
obj: &mut Object,
|
||||
array_chain: &[Vec<String>],
|
||||
depth: usize,
|
||||
field: &str,
|
||||
@@ -374,10 +364,10 @@ fn remove_field_at_depth_in_btree(
|
||||
let segments: Vec<&str> = field.split('.').collect();
|
||||
if segments.len() == 1 {
|
||||
if let Some(&seg) = segments.first() {
|
||||
btree.remove(&Value::from(seg));
|
||||
obj.remove(&Value::from(seg));
|
||||
}
|
||||
} else if segments.len() > 1 {
|
||||
remove_at_dotted_path_in_btree(btree, &segments);
|
||||
remove_at_dotted_path_obj(obj, &segments);
|
||||
}
|
||||
return;
|
||||
};
|
||||
@@ -389,12 +379,12 @@ fn remove_field_at_depth_in_btree(
|
||||
|
||||
let key_val = Value::from(first);
|
||||
let arr_val = if nav.len() == 1 {
|
||||
match btree.get_mut(&key_val) {
|
||||
match obj.get_mut(&key_val) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
}
|
||||
} else {
|
||||
let mut cur: &mut Value = match btree.get_mut(&key_val) {
|
||||
let mut cur: &mut Value = match obj.get_mut(&key_val) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
@@ -415,27 +405,19 @@ fn remove_field_at_depth_in_btree(
|
||||
for elem in inner.iter_mut() {
|
||||
if let Value::Object(obj_rc) = elem {
|
||||
let inner_btree = Rc::make_mut(obj_rc);
|
||||
remove_field_at_depth_in_btree(
|
||||
inner_btree,
|
||||
array_chain,
|
||||
depth.saturating_add(1),
|
||||
field,
|
||||
);
|
||||
remove_field_at_depth_obj(inner_btree, array_chain, depth.saturating_add(1), field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the leaf segment at a dotted path directly in a BTreeMap.
|
||||
fn remove_at_dotted_path_in_btree(
|
||||
btree: &mut alloc::collections::BTreeMap<Value, Value>,
|
||||
segments: &[&str],
|
||||
) {
|
||||
/// Remove the leaf segment at a dotted path directly in an Object.
|
||||
fn remove_at_dotted_path_obj(obj: &mut Object, segments: &[&str]) {
|
||||
let Some((&leaf, parent_segs)) = segments.split_last() else {
|
||||
return;
|
||||
};
|
||||
if parent_segs.is_empty() {
|
||||
btree.remove(&Value::from(leaf));
|
||||
obj.remove(&Value::from(leaf));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -443,7 +425,7 @@ fn remove_at_dotted_path_in_btree(
|
||||
return;
|
||||
};
|
||||
let first_key = Value::from(first);
|
||||
let parent_val = match btree.get_mut(&first_key) {
|
||||
let parent_val = match obj.get_mut(&first_key) {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! to fetch a related resource and an optional `existenceCondition` evaluated
|
||||
//! inline.
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use crate::value::Object;
|
||||
use alloc::format;
|
||||
use alloc::string::ToString as _;
|
||||
use alloc::vec::Vec;
|
||||
@@ -814,7 +814,7 @@ pub(super) fn build_object_from_keys(
|
||||
span: &crate::lexer::Span,
|
||||
) -> Result<u8> {
|
||||
// Build template: object with all keys set to Undefined.
|
||||
let mut template = BTreeMap::new();
|
||||
let mut template = Object::new();
|
||||
for &(key_idx, _) in &keys {
|
||||
// key_idx was returned by `add_literal_u16` in the calling code,
|
||||
// so it is always in bounds. We use `.get()` + `?` instead of
|
||||
|
||||
@@ -272,7 +272,7 @@ impl Compiler {
|
||||
fn insert_string_set_annotation(
|
||||
annot: &mut alloc::collections::BTreeMap<String, Value>,
|
||||
key: &str,
|
||||
observed: &BTreeSet<String>,
|
||||
observed: &alloc::collections::BTreeSet<String>,
|
||||
) {
|
||||
if !observed.is_empty() {
|
||||
let set: BTreeSet<Value> = observed
|
||||
|
||||
@@ -11,8 +11,9 @@ use crate::ast::{Expr, ExprRef};
|
||||
use crate::lexer::Span;
|
||||
use crate::rvm::instructions::{ArrayCreateParams, ObjectCreateParams, SetCreateParams};
|
||||
use crate::rvm::Instruction;
|
||||
use crate::value::Object;
|
||||
use crate::{Rc, Value};
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::collections::BTreeSet;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Try to evaluate an expression as a compile-time constant.
|
||||
@@ -43,7 +44,7 @@ pub(in crate::languages::rego::compiler) fn try_eval_const(expr: &Expr) -> Optio
|
||||
Expr::Object { fields, .. } => fields
|
||||
.iter()
|
||||
.map(|(_, k, v)| Some((try_eval_const(k.as_ref())?, try_eval_const(v.as_ref())?)))
|
||||
.collect::<Option<BTreeMap<_, _>>>()
|
||||
.collect::<Option<Object>>()
|
||||
.map(|m| Value::Object(Rc::new(m))),
|
||||
_ => None,
|
||||
}
|
||||
@@ -117,7 +118,7 @@ impl<'a> Compiler<'a> {
|
||||
fields: &[(crate::lexer::Span, ExprRef, ExprRef)],
|
||||
span: &Span,
|
||||
) -> Result<Register> {
|
||||
let all_const: Option<BTreeMap<_, _>> = fields
|
||||
let all_const: Option<Object> = fields
|
||||
.iter()
|
||||
.map(|(_, k, v)| Some((try_eval_const(k.as_ref())?, try_eval_const(v.as_ref())?)))
|
||||
.collect();
|
||||
@@ -166,7 +167,7 @@ impl<'a> Compiler<'a> {
|
||||
let mut template_keys = literal_keys.clone();
|
||||
template_keys.sort();
|
||||
|
||||
let mut template_obj = BTreeMap::new();
|
||||
let mut template_obj = Object::new();
|
||||
for key in &template_keys {
|
||||
template_obj.insert(key.clone(), Value::Undefined);
|
||||
}
|
||||
|
||||
@@ -338,6 +338,15 @@ impl<'a> Compiler<'a> {
|
||||
// No rule found; fall back to module-level imports.
|
||||
let import_key = format!("{}.{}", &self.current_package, root);
|
||||
if let Some(import_expr) = self.policy.inner.imports.get(&import_key) {
|
||||
if let Ok(mut import_chain) = parse_reference_chain(import_expr) {
|
||||
if let ReferenceRoot::Variable(import_root) = &import_chain.root {
|
||||
if import_root == "data" {
|
||||
import_chain.components.extend(chain.components.clone());
|
||||
return self.compile_data_chain(&import_chain, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let import_reg =
|
||||
self.compile_rego_expr_with_span(import_expr, import_expr.span(), false)?;
|
||||
if chain.components.is_empty() {
|
||||
|
||||
@@ -155,7 +155,7 @@ pub mod target;
|
||||
#[cfg(any(test, all(feature = "yaml", feature = "std")))]
|
||||
pub mod test_utils;
|
||||
pub mod utils;
|
||||
mod value;
|
||||
pub mod value;
|
||||
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub use {
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
//! values are converted through [`MetadataValue`] — a postcard/bincode-safe
|
||||
//! enum that avoids `deserialize_any`.
|
||||
|
||||
use crate::value::Object;
|
||||
use crate::Rc;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::collections::BTreeSet;
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -52,7 +52,7 @@ impl ProgramMetadata {
|
||||
pub fn to_value(&self) -> crate::value::Value {
|
||||
use crate::value::Value;
|
||||
|
||||
let mut obj = BTreeMap::new();
|
||||
let mut obj = Object::new();
|
||||
obj.insert(
|
||||
Value::String("compiler_version".into()),
|
||||
Value::String(self.compiler_version.as_str().into()),
|
||||
@@ -75,7 +75,7 @@ impl ProgramMetadata {
|
||||
);
|
||||
|
||||
if !self.annotations.is_empty() {
|
||||
let mut annotations_obj = BTreeMap::new();
|
||||
let mut annotations_obj = Object::new();
|
||||
for (k, v) in &self.annotations {
|
||||
annotations_obj.insert(Value::String(k.as_str().into()), v.clone());
|
||||
}
|
||||
@@ -198,7 +198,7 @@ impl MetadataValue {
|
||||
match *self {
|
||||
MetadataValue::String(ref s) => Value::String(s.as_str().into()),
|
||||
MetadataValue::StringSet(ref set) => {
|
||||
let mut bset = alloc::collections::BTreeSet::new();
|
||||
let mut bset = BTreeSet::new();
|
||||
for s in set {
|
||||
bset.insert(Value::String(s.as_str().into()));
|
||||
}
|
||||
@@ -211,7 +211,7 @@ impl MetadataValue {
|
||||
Value::Array(Rc::new(values))
|
||||
}
|
||||
MetadataValue::Map(ref map) => {
|
||||
let mut obj = BTreeMap::new();
|
||||
let mut obj = Object::new();
|
||||
for (k, v) in map {
|
||||
obj.insert(Value::String(k.as_str().into()), v.to_value());
|
||||
}
|
||||
@@ -257,7 +257,6 @@ mod metadata_serde {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::value::Value;
|
||||
use alloc::collections::BTreeSet;
|
||||
|
||||
/// Round-trip: Value → MetadataValue → Value must be equivalent for
|
||||
/// all lossless variants (strings, bools, integers, arrays, objects).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::collections::BTreeSet;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
@@ -11,6 +11,7 @@ use serde::ser::{SerializeSeq as _, SerializeTuple as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::number::Number;
|
||||
use crate::value::Object;
|
||||
use crate::value::Value;
|
||||
|
||||
const VARIANT_NULL: u32 = 0;
|
||||
@@ -132,7 +133,7 @@ impl<'a> Serialize for BinarySetRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
struct BinaryObjectRef<'a>(&'a BTreeMap<Value, Value>);
|
||||
struct BinaryObjectRef<'a>(&'a Object);
|
||||
|
||||
impl<'a> Serialize for BinaryObjectRef<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
@@ -140,7 +141,7 @@ impl<'a> Serialize for BinaryObjectRef<'a> {
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
|
||||
for (key, value) in self.0.iter() {
|
||||
for (key, value) in self.0.iter_sorted() {
|
||||
seq.serialize_element(&BinaryEntryRef(key, value))?;
|
||||
}
|
||||
seq.end()
|
||||
@@ -261,11 +262,11 @@ impl<'de> Visitor<'de> for BinaryValueVisitor {
|
||||
}
|
||||
(BinaryVariant::Object, variant) => {
|
||||
let entries: Vec<(BinaryValue, BinaryValue)> = variant.newtype_variant()?;
|
||||
let mut map = BTreeMap::new();
|
||||
let mut map = Object::new();
|
||||
for (key, value) in entries {
|
||||
map.insert(key.into_value(), value.into_value());
|
||||
}
|
||||
Ok(BinaryValue(Value::from(map)))
|
||||
Ok(BinaryValue(Value::Object(crate::Rc::new(map))))
|
||||
}
|
||||
(BinaryVariant::Undefined, variant) => {
|
||||
variant.unit_variant()?;
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
// Disable both to keep patterns consistent within this file.
|
||||
#![allow(clippy::pattern_type_mismatch, clippy::needless_borrowed_reference)]
|
||||
|
||||
use alloc::collections::BTreeSet;
|
||||
|
||||
use crate::number::Number;
|
||||
use crate::value::Value;
|
||||
|
||||
@@ -32,8 +30,9 @@ impl RegoVM {
|
||||
match (a, b) {
|
||||
(&Value::Number(ref x), &Value::Number(ref y)) => Ok(Value::from(x.sub(y)?)),
|
||||
(&Value::Set(ref left), &Value::Set(ref right)) => {
|
||||
let diff: BTreeSet<Value> = left.difference(right).cloned().collect();
|
||||
Ok(Value::from_set(diff))
|
||||
let diff: alloc::collections::BTreeSet<Value> =
|
||||
left.difference(right).cloned().collect();
|
||||
Ok(Value::from(diff))
|
||||
}
|
||||
_ => Err(VmError::InvalidSubtraction {
|
||||
left: a.clone(),
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::rvm::instructions::{ComprehensionBeginParams, ComprehensionMode};
|
||||
use crate::value::Object;
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
@@ -34,12 +34,12 @@ impl RegoVM {
|
||||
let initial_result = match params.mode {
|
||||
ComprehensionMode::Set => Value::new_set(),
|
||||
ComprehensionMode::Array => Value::new_array(),
|
||||
ComprehensionMode::Object => Value::Object(Rc::new(BTreeMap::new())),
|
||||
ComprehensionMode::Object => Value::Object(Rc::new(Object::new())),
|
||||
};
|
||||
self.set_register(params.result_reg, initial_result.clone())?;
|
||||
|
||||
let auto_iterate = params.collection_reg != params.result_reg;
|
||||
let iteration_state = if auto_iterate {
|
||||
let mut iteration_state = if auto_iterate {
|
||||
let source_value = self.get_register(params.collection_reg)?.clone();
|
||||
match source_value {
|
||||
Value::Array(items) => {
|
||||
@@ -53,11 +53,9 @@ impl RegoVM {
|
||||
if obj.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(IterationState::Object {
|
||||
obj,
|
||||
current_key: None,
|
||||
first_iteration: true,
|
||||
})
|
||||
// O(1) cursor over shared Rc<Object>.
|
||||
let cursor = obj.cursor();
|
||||
Some(IterationState::Object { obj, cursor })
|
||||
}
|
||||
}
|
||||
Value::Set(set) => {
|
||||
@@ -79,7 +77,7 @@ impl RegoVM {
|
||||
None
|
||||
};
|
||||
|
||||
let has_iteration = if let Some(state) = iteration_state.as_ref() {
|
||||
let has_iteration = if let Some(state) = iteration_state.as_mut() {
|
||||
self.setup_next_iteration(state, params.key_reg, params.value_reg)?
|
||||
} else {
|
||||
false
|
||||
@@ -123,12 +121,12 @@ impl RegoVM {
|
||||
let initial_result = match params.mode {
|
||||
ComprehensionMode::Set => Value::new_set(),
|
||||
ComprehensionMode::Array => Value::new_array(),
|
||||
ComprehensionMode::Object => Value::Object(Rc::new(BTreeMap::new())),
|
||||
ComprehensionMode::Object => Value::Object(Rc::new(Object::new())),
|
||||
};
|
||||
self.set_register(params.result_reg, initial_result.clone())?;
|
||||
|
||||
let auto_iterate = params.collection_reg != params.result_reg;
|
||||
let iteration_state = if auto_iterate {
|
||||
let mut iteration_state = if auto_iterate {
|
||||
let source_value = self.get_register(params.collection_reg)?.clone();
|
||||
match source_value {
|
||||
Value::Array(items) => {
|
||||
@@ -142,11 +140,8 @@ impl RegoVM {
|
||||
if obj.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(IterationState::Object {
|
||||
obj,
|
||||
current_key: None,
|
||||
first_iteration: true,
|
||||
})
|
||||
let cursor = obj.cursor();
|
||||
Some(IterationState::Object { obj, cursor })
|
||||
}
|
||||
}
|
||||
Value::Set(set) => {
|
||||
@@ -168,7 +163,7 @@ impl RegoVM {
|
||||
None
|
||||
};
|
||||
|
||||
let has_iteration = if let Some(state) = iteration_state.as_ref() {
|
||||
let has_iteration = if let Some(state) = iteration_state.as_mut() {
|
||||
self.setup_next_iteration(state, params.key_reg, params.value_reg)?
|
||||
} else {
|
||||
false
|
||||
@@ -255,6 +250,21 @@ impl RegoVM {
|
||||
};
|
||||
|
||||
let result_reg = comprehension_context.result_reg;
|
||||
// Snapshot the iteration value register BEFORE taking the result
|
||||
// register: if the comprehension compiler ever allocates
|
||||
// `result_reg == context.value_reg`, the writeback at the bottom
|
||||
// of this function would clobber the value register, and a
|
||||
// post-writeback read here would feed the wrong value into
|
||||
// `IterationState::Set::current_item`. Only Set needs the snapshot
|
||||
// (Object uses a self-advancing cursor; Array advances by index).
|
||||
let set_resume_snapshot = if matches!(
|
||||
comprehension_context.iteration_state,
|
||||
Some(IterationState::Set { .. })
|
||||
) {
|
||||
Some(self.get_register(comprehension_context.value_reg)?.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Take ownership of the result register so Rc refcount stays at 1,
|
||||
// allowing Rc::make_mut to mutate in-place instead of deep-cloning.
|
||||
let mut current_result = self.take_register(result_reg)?;
|
||||
@@ -292,29 +302,16 @@ impl RegoVM {
|
||||
self.set_register(result_reg, current_result)?;
|
||||
|
||||
if let Some(iter_state) = comprehension_context.iteration_state.as_mut() {
|
||||
match *iter_state {
|
||||
IterationState::Object {
|
||||
ref mut current_key,
|
||||
..
|
||||
} => {
|
||||
let tracked_key =
|
||||
if comprehension_context.key_reg != comprehension_context.value_reg {
|
||||
self.get_register(comprehension_context.key_reg)?.clone()
|
||||
} else {
|
||||
self.get_register(comprehension_context.value_reg)?.clone()
|
||||
};
|
||||
*current_key = Some(tracked_key);
|
||||
}
|
||||
IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} => {
|
||||
*current_item =
|
||||
Some(self.get_register(comprehension_context.value_reg)?.clone());
|
||||
}
|
||||
IterationState::Array { .. } | IterationState::Single { .. } => {}
|
||||
// Set's `Bound::Excluded(current_item)` resume scheme needs the
|
||||
// pre-mutation snapshot taken at the top of this function.
|
||||
// Object uses a self-advancing cursor and needs no snapshot.
|
||||
if let IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} = *iter_state
|
||||
{
|
||||
*current_item = set_resume_snapshot;
|
||||
}
|
||||
|
||||
iter_state.advance();
|
||||
let has_next = self.setup_next_iteration(
|
||||
iter_state,
|
||||
@@ -359,8 +356,7 @@ impl RegoVM {
|
||||
result_reg_idx,
|
||||
key_reg_idx,
|
||||
value_reg_idx,
|
||||
iteration_key,
|
||||
iteration_value,
|
||||
iter_is_set,
|
||||
) = {
|
||||
let frame =
|
||||
self.execution_stack
|
||||
@@ -382,8 +378,8 @@ impl RegoVM {
|
||||
|
||||
let result_reg_idx = context.result_reg;
|
||||
let mode = context.mode.clone();
|
||||
let iteration_key = self.get_register(context.key_reg)?.clone();
|
||||
let iteration_value = self.get_register(context.value_reg)?.clone();
|
||||
let iter_is_set =
|
||||
matches!(context.iteration_state, Some(IterationState::Set { .. }));
|
||||
|
||||
(
|
||||
value_to_add,
|
||||
@@ -392,8 +388,7 @@ impl RegoVM {
|
||||
result_reg_idx,
|
||||
context.key_reg,
|
||||
context.value_reg,
|
||||
iteration_key,
|
||||
iteration_value,
|
||||
iter_is_set,
|
||||
)
|
||||
} else {
|
||||
return Err(VmError::InvalidIteration {
|
||||
@@ -403,6 +398,18 @@ impl RegoVM {
|
||||
}
|
||||
};
|
||||
|
||||
// Snapshot the iteration value register BEFORE the result writeback:
|
||||
// if the compiler ever allocates `result_reg == value_reg_idx`, a
|
||||
// post-writeback read would feed the result accumulator into
|
||||
// `IterationState::Set::current_item`, breaking the next iteration.
|
||||
// Only Set needs this (Object cursor self-advances; Array advances
|
||||
// by index).
|
||||
let set_resume_snapshot = if iter_is_set {
|
||||
Some(self.get_register(value_reg_idx)?.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Take ownership of the result register so Rc refcount stays at 1,
|
||||
// allowing Rc::make_mut to mutate in-place instead of deep-cloning.
|
||||
let mut current_result = self.take_register(result_reg_idx)?;
|
||||
@@ -450,27 +457,13 @@ impl RegoVM {
|
||||
} = &mut frame.kind
|
||||
{
|
||||
if let Some(iter_state) = context.iteration_state.as_mut() {
|
||||
match *iter_state {
|
||||
IterationState::Object {
|
||||
ref mut current_key,
|
||||
..
|
||||
} => {
|
||||
let tracked_key = if context.key_reg != context.value_reg {
|
||||
iteration_key.clone()
|
||||
} else {
|
||||
iteration_value.clone()
|
||||
};
|
||||
*current_key = Some(tracked_key);
|
||||
}
|
||||
IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} => {
|
||||
*current_item = Some(iteration_value.clone());
|
||||
}
|
||||
IterationState::Array { .. } | IterationState::Single { .. } => {}
|
||||
if let IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} = *iter_state
|
||||
{
|
||||
*current_item = set_resume_snapshot;
|
||||
}
|
||||
|
||||
iter_state.advance();
|
||||
}
|
||||
|
||||
@@ -487,8 +480,21 @@ impl RegoVM {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(state) = iteration_state_snapshot.as_ref() {
|
||||
let has_next = self.setup_next_iteration(state, key_reg_idx, value_reg_idx)?;
|
||||
if let Some(mut state) = iteration_state_snapshot {
|
||||
let has_next = self.setup_next_iteration(&mut state, key_reg_idx, value_reg_idx)?;
|
||||
|
||||
// `setup_next_iteration` advances Object's internal cursor; the
|
||||
// owning frame holds the iteration_state, so we must write the
|
||||
// updated state back. (The Array/Set variants are also unchanged
|
||||
// by copy, so the writeback is uniform.)
|
||||
if let Some(frame) = self.execution_stack.get_mut(comprehension_index) {
|
||||
if let FrameKind::Comprehension {
|
||||
ref mut context, ..
|
||||
} = frame.kind
|
||||
{
|
||||
context.iteration_state = Some(state);
|
||||
}
|
||||
}
|
||||
|
||||
if has_next {
|
||||
if let Some(frame) = self.execution_stack.get_mut(comprehension_index) {
|
||||
@@ -528,7 +534,6 @@ impl RegoVM {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn handle_comprehension_condition_failure_suspendable(&mut self) -> Result<bool> {
|
||||
if let Some(mut frame) = self.execution_stack.pop() {
|
||||
let handled = if let &mut FrameKind::Comprehension {
|
||||
@@ -554,11 +559,16 @@ impl RegoVM {
|
||||
context: &mut ComprehensionContext,
|
||||
) -> Result<()> {
|
||||
if let Some(iter_state) = context.iteration_state.as_mut() {
|
||||
self.capture_comprehension_iteration_position(
|
||||
iter_state,
|
||||
context.key_reg,
|
||||
context.value_reg,
|
||||
)?;
|
||||
// Snapshot the current value into Set's `current_item` so the
|
||||
// next iteration can resume from `Bound::Excluded(current)`.
|
||||
// Object uses a self-advancing cursor and needs no snapshot here.
|
||||
if let IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} = *iter_state
|
||||
{
|
||||
*current_item = Some(self.get_register(context.value_reg)?.clone());
|
||||
}
|
||||
iter_state.advance();
|
||||
let has_next =
|
||||
self.setup_next_iteration(iter_state, context.key_reg, context.value_reg)?;
|
||||
@@ -575,37 +585,10 @@ impl RegoVM {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn capture_comprehension_iteration_position(
|
||||
&mut self,
|
||||
iter_state: &mut IterationState,
|
||||
key_reg: u8,
|
||||
value_reg: u8,
|
||||
) -> Result<()> {
|
||||
match *iter_state {
|
||||
IterationState::Object {
|
||||
ref mut current_key,
|
||||
..
|
||||
} => {
|
||||
let tracked_key = if key_reg != value_reg {
|
||||
self.get_register(key_reg)?.clone()
|
||||
} else {
|
||||
self.get_register(value_reg)?.clone()
|
||||
};
|
||||
*current_key = Some(tracked_key);
|
||||
}
|
||||
IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} => {
|
||||
*current_item = Some(self.get_register(value_reg)?.clone());
|
||||
}
|
||||
IterationState::Array { .. } | IterationState::Single { .. } => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_comprehension_end_run_to_completion(&mut self) -> Result<()> {
|
||||
// `ComprehensionEnd` is reached from a loaded program; an empty stack
|
||||
// here means malformed user-supplied bytecode, which must still surface
|
||||
// as a typed error rather than a panic — including in debug builds.
|
||||
self.comprehension_stack.pop().map_or_else(
|
||||
|| {
|
||||
Err(VmError::InvalidIteration {
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
use crate::rvm::instructions::{ComprehensionMode, LoopMode};
|
||||
use crate::value::Value;
|
||||
use crate::value::{Object, ObjectCursor};
|
||||
use crate::Rc;
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
use alloc::collections::BTreeSet;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Loop execution context for managing iteration state
|
||||
@@ -24,7 +25,18 @@ pub struct LoopContext {
|
||||
pub current_iteration_failed: bool, // Track if current iteration had condition failures
|
||||
}
|
||||
|
||||
/// Iterator state for different collection types
|
||||
/// Iterator state for different collection types.
|
||||
///
|
||||
/// Snapshot independence for `Object` is provided by the shared
|
||||
/// `Rc<Object>` — `Rc::make_mut` on an aliased Rc allocates a new
|
||||
/// collection, leaving the iterator's Rc pointing at the original
|
||||
/// pre-mutation state. The `ObjectCursor` is opaque and resumes in
|
||||
/// O(log n) for the BTree backend.
|
||||
///
|
||||
/// `Set` continues to use the pre-existing snapshot-by-cloned-key
|
||||
/// approach (`current_item` + `first_iteration`); migration of `Set`
|
||||
/// to a cursor-based iterator ships with the `Set` storage abstraction
|
||||
/// in a follow-up PR.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IterationState {
|
||||
Array {
|
||||
@@ -32,9 +44,8 @@ pub enum IterationState {
|
||||
index: usize,
|
||||
},
|
||||
Object {
|
||||
obj: Rc<BTreeMap<Value, Value>>,
|
||||
current_key: Option<Value>,
|
||||
first_iteration: bool,
|
||||
obj: Rc<Object>,
|
||||
cursor: ObjectCursor,
|
||||
},
|
||||
Set {
|
||||
items: Rc<BTreeSet<Value>>,
|
||||
@@ -54,13 +65,21 @@ impl IterationState {
|
||||
pub(super) const fn advance(&mut self) {
|
||||
match *self {
|
||||
Self::Array { ref mut index, .. } => {
|
||||
// Array iteration uses `usize` as the cursor and advances via
|
||||
// `saturating_add(1)`. A cursor already at `usize::MAX` here
|
||||
// means a stuck (non-progressing) iteration was emitted by
|
||||
// malformed bytecode; assert in debug to surface it loudly.
|
||||
debug_assert!(
|
||||
*index < usize::MAX,
|
||||
"IterationState::Array index already at usize::MAX on advance"
|
||||
);
|
||||
*index = index.saturating_add(1);
|
||||
}
|
||||
Self::Object {
|
||||
ref mut first_iteration,
|
||||
..
|
||||
}
|
||||
| Self::Set {
|
||||
// For Object the cursor advances inside `setup_next_iteration`
|
||||
// when it pulls the next item via `Object::next`, so `advance`
|
||||
// is a no-op for the cursor-backed Object variant.
|
||||
Self::Object { .. } => {}
|
||||
Self::Set {
|
||||
ref mut first_iteration,
|
||||
..
|
||||
} => {
|
||||
@@ -69,6 +88,12 @@ impl IterationState {
|
||||
Self::Single {
|
||||
ref mut consumed, ..
|
||||
} => {
|
||||
// `Single` yields exactly once; advancing a consumed Single
|
||||
// means the compiler emitted a redundant LoopNext.
|
||||
debug_assert!(
|
||||
!*consumed,
|
||||
"IterationState::Single advanced after consumption"
|
||||
);
|
||||
*consumed = true;
|
||||
}
|
||||
}
|
||||
@@ -107,3 +132,71 @@ pub(super) struct ComprehensionContext {
|
||||
/// Resume location for the parent frame once this comprehension completes
|
||||
pub(super) resume_pc: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(
|
||||
clippy::expect_used,
|
||||
clippy::unwrap_used,
|
||||
clippy::unreachable,
|
||||
clippy::pattern_type_mismatch,
|
||||
clippy::shadow_unrelated,
|
||||
clippy::panic
|
||||
)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::value::Object;
|
||||
|
||||
/// IterationState::Object holds an `Rc<Object>` plus an opaque cursor.
|
||||
/// Mutating an aliased Rc via `Rc::make_mut` allocates a new collection
|
||||
/// (CoW) so the in-flight iterator's source is unaffected.
|
||||
#[test]
|
||||
fn iteration_state_object_is_snapshot_independent_of_source() {
|
||||
let mut obj = Object::new();
|
||||
obj.insert(Value::from("a"), Value::from(1));
|
||||
obj.insert(Value::from("b"), Value::from(2));
|
||||
obj.insert(Value::from("c"), Value::from(3));
|
||||
|
||||
let source = Value::Object(Rc::new(obj));
|
||||
|
||||
let snapshot_obj = match &source {
|
||||
Value::Object(o) => Rc::clone(o),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let state = IterationState::Object {
|
||||
obj: Rc::clone(&snapshot_obj),
|
||||
cursor: snapshot_obj.cursor(),
|
||||
};
|
||||
|
||||
// Mutate a clone of the source mid-iteration.
|
||||
let mut alias = source.clone();
|
||||
let inner = alias.as_object_mut().expect("object");
|
||||
inner.insert(Value::from("a"), Value::from(999));
|
||||
inner.insert(Value::from("d"), Value::from(4));
|
||||
inner.remove(&Value::from("b"));
|
||||
|
||||
// Drain the snapshot via the cursor — must still report the original
|
||||
// 3 entries with original values.
|
||||
let mut collected: Vec<(Value, Value)> = Vec::new();
|
||||
if let IterationState::Object {
|
||||
ref obj,
|
||||
mut cursor,
|
||||
} = state
|
||||
{
|
||||
while let Some((k, v)) = obj.next(&mut cursor) {
|
||||
collected.push((k.clone(), v.clone()));
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
assert_eq!(collected.len(), 3);
|
||||
assert!(collected.contains(&(Value::from("a"), Value::from(1))));
|
||||
assert!(collected.contains(&(Value::from("b"), Value::from(2))));
|
||||
assert!(collected.contains(&(Value::from("c"), Value::from(3))));
|
||||
assert!(!collected.iter().any(|kv| kv.0 == Value::from("d")));
|
||||
|
||||
// The original source Value (untouched) is also unchanged.
|
||||
let src_obj = source.as_object().expect("object");
|
||||
assert_eq!(src_obj.len(), 3);
|
||||
assert_eq!(src_obj.get(&Value::from("a")), Some(&Value::from(1)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
use crate::rvm::instructions::{GuardMode, Instruction, LiteralOrRegister};
|
||||
use crate::rvm::program::Program;
|
||||
use crate::value::Value;
|
||||
use alloc::collections::BTreeSet;
|
||||
use alloc::vec::Vec;
|
||||
use core::mem;
|
||||
|
||||
@@ -670,7 +669,7 @@ impl RegoVM {
|
||||
}
|
||||
}
|
||||
SetNew { dest } => {
|
||||
let empty_set = Value::Set(crate::Rc::new(BTreeSet::new()));
|
||||
let empty_set = Value::new_set();
|
||||
self.set_register(dest, empty_set)?;
|
||||
Ok(InstructionOutcome::Continue)
|
||||
}
|
||||
@@ -707,7 +706,7 @@ impl RegoVM {
|
||||
if any_undefined {
|
||||
self.set_register(params.dest, Value::Undefined)?;
|
||||
} else {
|
||||
let mut set = BTreeSet::new();
|
||||
let mut set = alloc::collections::BTreeSet::new();
|
||||
for ® in params.element_registers() {
|
||||
set.insert(self.get_register(reg)?.clone());
|
||||
}
|
||||
|
||||
@@ -295,6 +295,13 @@ pub enum VmError {
|
||||
#[error("Call rule stack underflow during rule finalization (pc={pc})")]
|
||||
CallRuleStackUnderflow { pc: usize },
|
||||
|
||||
#[error("Call rule stack mismatch during rule finalization: expected rule_index {expected}, popped {actual} (pc={pc})")]
|
||||
CallRuleStackMismatch {
|
||||
expected: u16,
|
||||
actual: u16,
|
||||
pc: usize,
|
||||
},
|
||||
|
||||
#[error("Internal VM error: {message} (pc={pc})")]
|
||||
Internal { message: String, pc: usize },
|
||||
}
|
||||
|
||||
@@ -117,6 +117,10 @@ impl RegoVM {
|
||||
let target = self.convert_pc(target, "jump target")?;
|
||||
self.pc = target;
|
||||
while self.pc < program.instructions.len() {
|
||||
// Per-instruction sanity check: every iteration of the dispatch
|
||||
// loop must re-enter with the VM in a Running/Ready state and the
|
||||
// working data structures coherent.
|
||||
self.assert_vm_invariants();
|
||||
self.memory_check()?;
|
||||
if self.executed_instructions >= self.max_instructions {
|
||||
return Err(VmError::InstructionLimitExceeded {
|
||||
@@ -189,6 +193,9 @@ impl RegoVM {
|
||||
}
|
||||
|
||||
fn execute_suspendable_entry(&mut self, entry_point_pc: usize) -> Result<Value> {
|
||||
// Precondition: callers (execute_entry_point_by_{index,name}) reset the
|
||||
// VM before invoking this method, so the VM must be in a clean state.
|
||||
self.debug_assert_state_is_clean();
|
||||
self.execution_state = ExecutionState::Running;
|
||||
self.reset_execution_timer_state();
|
||||
match self.run_stackless_from(entry_point_pc) {
|
||||
@@ -201,6 +208,10 @@ impl RegoVM {
|
||||
}
|
||||
|
||||
pub fn resume(&mut self, resume_value: Option<Value>) -> Result<Value> {
|
||||
// Precondition is enforced below by returning `VmError::InvalidResumeState`
|
||||
// for any non-`Suspended` state. A `debug_assert!` here would diverge
|
||||
// debug vs release behavior and, when invoked via FFI, would trip the
|
||||
// unwind guard and poison the engine on a recoverable misuse.
|
||||
let (reason, mut last_result) = match self.execution_state.clone() {
|
||||
ExecutionState::Suspended {
|
||||
reason,
|
||||
@@ -289,6 +300,9 @@ impl RegoVM {
|
||||
|
||||
fn run_stackless_loop(&mut self, program: &Program, last_result: &mut Value) -> Result<()> {
|
||||
while !self.execution_stack.is_empty() {
|
||||
// Per-instruction sanity check: see `assert_vm_invariants` for the
|
||||
// exact contract. Compiled out in release.
|
||||
self.assert_vm_invariants();
|
||||
self.memory_check()?;
|
||||
self.frame_pc_overridden = false;
|
||||
let should_finalize_rule = self.execution_stack.last().is_some_and(|frame| {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::rvm::instructions::LoopMode;
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
|
||||
use super::context::{IterationState, LoopContext};
|
||||
use super::errors::{Result, VmError};
|
||||
@@ -89,13 +90,13 @@ impl RegoVM {
|
||||
) -> Result<()> {
|
||||
self.set_register(params.result_reg, Value::Bool(false))?;
|
||||
|
||||
let iteration_state = match self.resolve_iteration_state(mode, ¶ms)? {
|
||||
let mut iteration_state = match self.resolve_iteration_state(mode, ¶ms)? {
|
||||
Some(state) => state,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let has_next =
|
||||
self.setup_next_iteration(&iteration_state, params.key_reg, params.value_reg)?;
|
||||
self.setup_next_iteration(&mut iteration_state, params.key_reg, params.value_reg)?;
|
||||
if !has_next {
|
||||
self.pc = usize::from(params.loop_end);
|
||||
return Ok(());
|
||||
@@ -155,15 +156,10 @@ impl RegoVM {
|
||||
LoopAction::Continue => {}
|
||||
}
|
||||
|
||||
if let &mut IterationState::Object {
|
||||
ref mut current_key,
|
||||
..
|
||||
} = &mut loop_ctx.iteration_state
|
||||
{
|
||||
if loop_ctx.key_reg != loop_ctx.value_reg {
|
||||
*current_key = Some(self.get_register(loop_ctx.key_reg)?.clone());
|
||||
}
|
||||
} else if let &mut IterationState::Set {
|
||||
// Snapshot the current value for Set so its next iteration can resume
|
||||
// from `Bound::Excluded(current)`. Object uses a cursor and advances
|
||||
// inside `setup_next_iteration` itself.
|
||||
if let &mut IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} = &mut loop_ctx.iteration_state
|
||||
@@ -173,7 +169,7 @@ impl RegoVM {
|
||||
|
||||
loop_ctx.iteration_state.advance();
|
||||
let has_next = self.setup_next_iteration(
|
||||
&loop_ctx.iteration_state,
|
||||
&mut loop_ctx.iteration_state,
|
||||
loop_ctx.key_reg,
|
||||
loop_ctx.value_reg,
|
||||
)?;
|
||||
@@ -211,13 +207,13 @@ impl RegoVM {
|
||||
) -> Result<()> {
|
||||
self.set_register(params.result_reg, Value::Bool(false))?;
|
||||
|
||||
let iteration_state = match self.resolve_iteration_state(mode, ¶ms)? {
|
||||
let mut iteration_state = match self.resolve_iteration_state(mode, ¶ms)? {
|
||||
Some(state) => state,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let has_next =
|
||||
self.setup_next_iteration(&iteration_state, params.key_reg, params.value_reg)?;
|
||||
self.setup_next_iteration(&mut iteration_state, params.key_reg, params.value_reg)?;
|
||||
if !has_next {
|
||||
self.pc = usize::from(params.loop_end);
|
||||
return Ok(());
|
||||
@@ -316,7 +312,14 @@ impl RegoVM {
|
||||
Ok(())
|
||||
}
|
||||
LoopAction::Continue => {
|
||||
let (mode, success_count, total_iterations, key_reg, value_reg, iteration_state) = {
|
||||
let (
|
||||
mode,
|
||||
success_count,
|
||||
total_iterations,
|
||||
key_reg,
|
||||
value_reg,
|
||||
mut iteration_state,
|
||||
) = {
|
||||
let (mode, success_count, total_iterations, key_reg, value_reg) = {
|
||||
let frame = self
|
||||
.execution_stack
|
||||
@@ -334,11 +337,6 @@ impl RegoVM {
|
||||
}
|
||||
};
|
||||
|
||||
let key_value = if key_reg != value_reg {
|
||||
Some(self.get_register(key_reg)?.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let value_value = self.get_register(value_reg)?.clone();
|
||||
|
||||
let frame = self
|
||||
@@ -349,20 +347,16 @@ impl RegoVM {
|
||||
&mut FrameKind::Loop {
|
||||
ref mut context, ..
|
||||
} => {
|
||||
if let &mut IterationState::Object {
|
||||
ref mut current_key,
|
||||
..
|
||||
} = &mut context.iteration_state
|
||||
{
|
||||
if context.key_reg != context.value_reg {
|
||||
*current_key = key_value;
|
||||
}
|
||||
} else if let &mut IterationState::Set {
|
||||
// Snapshot the current value for Set so its next
|
||||
// iteration can resume from `Bound::Excluded(current)`.
|
||||
// Object uses a cursor and advances inside
|
||||
// `setup_next_iteration` itself.
|
||||
if let &mut IterationState::Set {
|
||||
ref mut current_item,
|
||||
..
|
||||
} = &mut context.iteration_state
|
||||
{
|
||||
*current_item = Some(value_value.clone());
|
||||
*current_item = Some(value_value);
|
||||
}
|
||||
|
||||
context.iteration_state.advance();
|
||||
@@ -381,7 +375,21 @@ impl RegoVM {
|
||||
}
|
||||
};
|
||||
|
||||
let has_next = self.setup_next_iteration(&iteration_state, key_reg, value_reg)?;
|
||||
let has_next =
|
||||
self.setup_next_iteration(&mut iteration_state, key_reg, value_reg)?;
|
||||
|
||||
// `setup_next_iteration` advances Object's internal cursor;
|
||||
// the owning frame holds the iteration_state, so we must
|
||||
// write the updated state back. (Array/Set are unchanged by
|
||||
// the call, so the writeback is uniform.)
|
||||
if let Some(frame) = self.execution_stack.last_mut() {
|
||||
if let FrameKind::Loop {
|
||||
ref mut context, ..
|
||||
} = frame.kind
|
||||
{
|
||||
context.iteration_state = iteration_state;
|
||||
}
|
||||
}
|
||||
|
||||
if has_next {
|
||||
if let Some(frame) = self.execution_stack.last_mut() {
|
||||
@@ -459,10 +467,14 @@ impl RegoVM {
|
||||
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
|
||||
return Ok(None);
|
||||
}
|
||||
// O(1) resumable cursor over the shared Rc<Object>.
|
||||
// No eager pair snapshot: avoids O(N) setup, O(N) memory
|
||||
// floor, and O(N) memory-limit checks. Snapshot
|
||||
// independence is via the shared Rc (CoW).
|
||||
let cursor = obj.cursor();
|
||||
Ok(Some(IterationState::Object {
|
||||
obj: obj.clone(),
|
||||
current_key: None,
|
||||
first_iteration: true,
|
||||
obj: Rc::clone(obj),
|
||||
cursor,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -512,7 +524,7 @@ impl RegoVM {
|
||||
|
||||
pub(super) fn setup_next_iteration(
|
||||
&mut self,
|
||||
state: &IterationState,
|
||||
state: &mut IterationState,
|
||||
key_reg: u8,
|
||||
value_reg: u8,
|
||||
) -> Result<bool> {
|
||||
@@ -538,33 +550,19 @@ impl RegoVM {
|
||||
}
|
||||
IterationState::Object {
|
||||
ref obj,
|
||||
ref current_key,
|
||||
ref first_iteration,
|
||||
ref mut cursor,
|
||||
} => {
|
||||
if *first_iteration {
|
||||
if let Some((key, value)) = obj.iter().next() {
|
||||
if key_reg != value_reg {
|
||||
self.set_register(key_reg, key.clone())?;
|
||||
}
|
||||
self.set_register(value_reg, value.clone())?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
} else if let Some(ref current) = *current_key {
|
||||
let mut range_iter = obj.range((
|
||||
core::ops::Bound::Excluded(current),
|
||||
core::ops::Bound::Unbounded,
|
||||
));
|
||||
if let Some((key, value)) = range_iter.next() {
|
||||
if key_reg != value_reg {
|
||||
self.set_register(key_reg, key.clone())?;
|
||||
}
|
||||
self.set_register(value_reg, value.clone())?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
// Object iterates via a resumable cursor on the shared
|
||||
// `Rc<Object>`; `next` both yields the current entry and
|
||||
// advances the cursor. No explicit `current_key` snapshot is
|
||||
// needed — see the doc on `IterationState`.
|
||||
if let Some((key, value)) = obj.next(cursor) {
|
||||
let value = value.clone();
|
||||
if key_reg != value_reg {
|
||||
self.set_register(key_reg, key.clone())?;
|
||||
}
|
||||
self.set_register(value_reg, value)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -277,6 +277,19 @@ impl RegoVM {
|
||||
.call_rule_stack
|
||||
.pop()
|
||||
.ok_or(VmError::CallRuleStackUnderflow { pc: self.pc })?;
|
||||
// Stack discipline: the context we just popped must belong to the
|
||||
// rule we are finalizing. A mismatch indicates a missing push or an
|
||||
// extra pop somewhere in this rule's execution and would otherwise
|
||||
// silently restore the wrong return_pc / rule_type. Surface as a
|
||||
// typed VmError so the contract holds the same in debug and release
|
||||
// builds (avoiding FFI poisoning via a debug-only panic).
|
||||
if rule_index != call_context.rule_index {
|
||||
return Err(VmError::CallRuleStackMismatch {
|
||||
expected: rule_index,
|
||||
actual: call_context.rule_index,
|
||||
pc: self.pc,
|
||||
});
|
||||
}
|
||||
self.pc = call_context.return_pc;
|
||||
|
||||
let result_from_rule = if !rule_failed_due_to_inconsistency {
|
||||
@@ -831,6 +844,9 @@ impl RegoVM {
|
||||
|
||||
self.registers = parent_registers;
|
||||
|
||||
// Underflow here means malformed/poisoned program state; surface as a
|
||||
// typed error rather than a debug-only panic so the public load_program
|
||||
// contract holds the same in debug and release.
|
||||
if self.call_rule_stack.pop().is_none() {
|
||||
return Err(VmError::CallRuleStackUnderflow { pc: self.pc });
|
||||
}
|
||||
|
||||
@@ -34,6 +34,139 @@ impl RegoVM {
|
||||
|
||||
// Builtin cache entries only live for a single execution
|
||||
self.builtins_cache.clear();
|
||||
|
||||
// Postcondition: every stack/cache that `reset_execution_state` touches
|
||||
// must be in its documented "clean" shape. This catches accidental
|
||||
// omissions in future edits to this function.
|
||||
self.debug_assert_state_is_clean();
|
||||
}
|
||||
|
||||
/// Debug-only postcondition for `reset_execution_state`.
|
||||
///
|
||||
/// Asserts the invariants every caller of `reset_execution_state` relies on
|
||||
/// before starting a fresh execution. The body is fully gated by
|
||||
/// `#[cfg(debug_assertions)]` so this is a zero-cost no-op in release.
|
||||
#[inline]
|
||||
pub(super) fn debug_assert_state_is_clean(&self) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// --- Stacks: every per-execution stack must be drained. ---
|
||||
debug_assert!(
|
||||
self.execution_stack.is_empty(),
|
||||
"reset_execution_state postcondition: execution_stack must be empty"
|
||||
);
|
||||
debug_assert!(
|
||||
self.loop_stack.is_empty(),
|
||||
"reset_execution_state postcondition: loop_stack must be empty"
|
||||
);
|
||||
debug_assert!(
|
||||
self.comprehension_stack.is_empty(),
|
||||
"reset_execution_state postcondition: comprehension_stack must be empty"
|
||||
);
|
||||
debug_assert!(
|
||||
self.call_rule_stack.is_empty(),
|
||||
"reset_execution_state postcondition: call_rule_stack must be empty"
|
||||
);
|
||||
debug_assert!(
|
||||
self.register_stack.is_empty(),
|
||||
"reset_execution_state postcondition: register_stack must be empty"
|
||||
);
|
||||
|
||||
// --- Caches: cleared so a new program/input cannot read stale entries. ---
|
||||
debug_assert!(
|
||||
self.builtins_cache.is_empty(),
|
||||
"reset_execution_state postcondition: builtins_cache must be empty"
|
||||
);
|
||||
|
||||
// --- Registers: window resized to the program's base count and zeroed. ---
|
||||
debug_assert_eq!(
|
||||
self.registers.len(),
|
||||
self.base_register_count,
|
||||
"reset_execution_state postcondition: registers must be sized to base_register_count"
|
||||
);
|
||||
debug_assert!(
|
||||
self.registers.iter().all(|v| matches!(v, Value::Undefined)),
|
||||
"reset_execution_state postcondition: all registers must be Undefined"
|
||||
);
|
||||
|
||||
// --- Rule cache: sized to the current program and marked uncomputed. ---
|
||||
debug_assert_eq!(
|
||||
self.rule_cache.len(),
|
||||
self.program.rule_infos.len(),
|
||||
"reset_execution_state postcondition: rule_cache size must match program rule_infos"
|
||||
);
|
||||
debug_assert!(
|
||||
self.rule_cache.iter().all(|entry| !entry.0),
|
||||
"reset_execution_state postcondition: rule_cache entries must be uncomputed"
|
||||
);
|
||||
|
||||
// --- Counters and execution-state machine: zeroed and back to Ready. ---
|
||||
debug_assert_eq!(
|
||||
self.pc, 0,
|
||||
"reset_execution_state postcondition: pc must be 0"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
self.executed_instructions, 0,
|
||||
"reset_execution_state postcondition: executed_instructions must be 0"
|
||||
);
|
||||
debug_assert!(
|
||||
matches!(self.execution_state, ExecutionState::Ready),
|
||||
"reset_execution_state postcondition: execution_state must be Ready"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-opcode VM invariants checked from the inner dispatch loop.
|
||||
///
|
||||
/// These hold every time control re-enters the dispatch loop with another
|
||||
/// instruction to execute. Only conditions that are *purely VM-internal*
|
||||
/// (i.e. cannot be made false by any host-supplied program or out-of-order
|
||||
/// API call) are asserted here — anything reachable from `load_program`
|
||||
/// input must surface as a typed `VmError` instead, to avoid panicking in
|
||||
/// debug builds and poisoning the engine across FFI.
|
||||
///
|
||||
/// Fully `#[cfg(debug_assertions)]`-gated so the method body compiles out
|
||||
/// in release.
|
||||
#[inline]
|
||||
pub(super) fn assert_vm_invariants(&self) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// The dispatch loop only runs while execution is live. Once the VM
|
||||
// has transitioned to a terminal state (Suspended/Completed/Error)
|
||||
// the loop must have exited. Note `Ready` is also valid here because
|
||||
// some entry points (e.g. `execute_entry_point_by_index` in
|
||||
// RunToCompletion mode) drive `jump_to` without flipping the state.
|
||||
// `execution_state` is mutated only inside the VM and is not
|
||||
// host-controllable.
|
||||
debug_assert!(
|
||||
matches!(
|
||||
self.execution_state,
|
||||
ExecutionState::Ready | ExecutionState::Running
|
||||
),
|
||||
"vm invariant: execution_state must be Ready or Running inside the dispatch loop, was {:?}",
|
||||
self.execution_state
|
||||
);
|
||||
|
||||
// Rule cache is sized once at reset (against the currently loaded
|
||||
// program) and the VM does not resize it mid-execution. Any
|
||||
// mismatch here would indicate an internal accounting bug rather
|
||||
// than malformed input.
|
||||
debug_assert_eq!(
|
||||
self.rule_cache.len(),
|
||||
self.program.rule_infos.len(),
|
||||
"vm invariant: rule_cache size must equal program.rule_infos size"
|
||||
);
|
||||
|
||||
// NOTE: `!registers.is_empty()` and an `execution_stack` depth
|
||||
// ceiling were intentionally *not* asserted here: both can be
|
||||
// triggered by a host-loaded program (registers via
|
||||
// `RuleInfo::num_registers == 0`; stack depth via deeply nested
|
||||
// rules/loops/comprehensions) and would therefore panic in debug
|
||||
// and poison the engine across FFI. Register access is already
|
||||
// guarded by `VmError::RegisterIndexOutOfBounds`; runaway recursion
|
||||
// is bounded in production by `set_max_instructions` and
|
||||
// `memory_check`.
|
||||
}
|
||||
}
|
||||
|
||||
/// Return all active objects to their respective pools for reuse
|
||||
|
||||
@@ -569,7 +569,7 @@ impl Analyzer {
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
Array { .. } | Object { .. } => Ok(true),
|
||||
Expr::Array { .. } | Expr::Object { .. } => Ok(true),
|
||||
_ => Ok(false),
|
||||
})?;
|
||||
Ok(true)
|
||||
@@ -666,7 +666,7 @@ impl Analyzer {
|
||||
Ok(false)
|
||||
}
|
||||
// TODO: key vs value for object binding
|
||||
Array { .. } | Object { .. } => Ok(true),
|
||||
Expr::Array { .. } | Expr::Object { .. } => Ok(true),
|
||||
_ => Ok(false),
|
||||
})?;
|
||||
Ok(vars)
|
||||
@@ -853,7 +853,7 @@ impl Analyzer {
|
||||
Ok(false)
|
||||
}
|
||||
// TODO: Object key/value
|
||||
Array { .. } | Object { .. } => Ok(true),
|
||||
Expr::Array { .. } | Expr::Object { .. } => Ok(true),
|
||||
_ => {
|
||||
non_vars.push(e.clone());
|
||||
Ok(false)
|
||||
|
||||
@@ -1498,7 +1498,7 @@ fn test_deserialize_object_default_empty_object() {
|
||||
let s = Schema::from_serde_json_value(schema).unwrap();
|
||||
match s.as_type() {
|
||||
Type::Object { default, .. } => {
|
||||
assert_eq!(default, &Some(Value::Object(Rc::new(BTreeMap::new()))));
|
||||
assert_eq!(default, &Some(Value::new_object()));
|
||||
}
|
||||
_ => panic!("Expected Type::Object"),
|
||||
}
|
||||
@@ -1778,8 +1778,11 @@ fn test_deserialize_enum_values_with_object_non_string_keys() {
|
||||
match s.as_type() {
|
||||
Type::Enum { values, .. } => match &values[0] {
|
||||
Value::Object(obj) => {
|
||||
assert_eq!(obj[&Value::from("1")], Value::from("one"));
|
||||
assert_eq!(obj[&Value::from("true")], Value::from("bool"));
|
||||
assert_eq!(*obj.get(&Value::from("1")).expect("1"), Value::from("one"));
|
||||
assert_eq!(
|
||||
*obj.get(&Value::from("true")).expect("true"),
|
||||
Value::from("bool")
|
||||
);
|
||||
}
|
||||
_ => panic!("Expected object in enum values"),
|
||||
},
|
||||
@@ -1802,18 +1805,21 @@ fn test_deserialize_enum_values_with_deeply_nested_structures() {
|
||||
match s.as_type() {
|
||||
Type::Enum { values, .. } => match &values[0] {
|
||||
Value::Object(obj) => {
|
||||
let a = &obj[&Value::from("a")];
|
||||
let a = obj.get(&Value::from("a")).expect("a");
|
||||
match a {
|
||||
Value::Array(arr) => match &arr[0] {
|
||||
Value::Object(inner) => {
|
||||
let b = &inner[&Value::from("b")];
|
||||
let b = inner.get(&Value::from("b")).expect("b");
|
||||
match b {
|
||||
Value::Array(barr) => {
|
||||
assert_eq!(barr[0], Value::from(1));
|
||||
assert_eq!(barr[1], Value::from(2));
|
||||
match &barr[2] {
|
||||
Value::Object(cobj) => {
|
||||
assert_eq!(cobj[&Value::from("c")], Value::Null);
|
||||
assert_eq!(
|
||||
*cobj.get(&Value::from("c")).expect("c"),
|
||||
Value::Null
|
||||
);
|
||||
}
|
||||
_ => panic!("Expected object for 'c'"),
|
||||
}
|
||||
@@ -1886,8 +1892,11 @@ fn test_deserialize_const_value_object() {
|
||||
match s.as_type() {
|
||||
Type::Const { value, .. } => match value {
|
||||
Value::Object(ref obj) => {
|
||||
assert_eq!(obj[&Value::from("foo")], Value::from("bar"));
|
||||
assert_eq!(obj[&Value::from("baz")], Value::from(1));
|
||||
assert_eq!(
|
||||
*obj.get(&Value::from("foo")).expect("foo"),
|
||||
Value::from("bar")
|
||||
);
|
||||
assert_eq!(*obj.get(&Value::from("baz")).expect("baz"), Value::from(1));
|
||||
}
|
||||
_ => panic!("Expected object for const value"),
|
||||
},
|
||||
@@ -1940,13 +1949,13 @@ fn test_deserialize_const_value_deeply_nested() {
|
||||
match s.as_type() {
|
||||
Type::Const { value, .. } => match value {
|
||||
Value::Object(ref obj) => {
|
||||
let a = &obj[&Value::from("a")];
|
||||
let a = obj.get(&Value::from("a")).expect("a");
|
||||
match a {
|
||||
Value::Array(arr) => {
|
||||
assert_eq!(arr[0], Value::from(1));
|
||||
match &arr[1] {
|
||||
Value::Object(inner) => {
|
||||
let b = &inner[&Value::from("b")];
|
||||
let b = inner.get(&Value::from("b")).expect("b");
|
||||
match b {
|
||||
Value::Array(barr) => {
|
||||
assert_eq!(barr[0], Value::Null);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
use crate::{
|
||||
schema::{error::ValidationError, Schema, Type},
|
||||
value::Object,
|
||||
*,
|
||||
};
|
||||
use alloc::collections::BTreeMap;
|
||||
@@ -537,7 +538,7 @@ impl SchemaValidator {
|
||||
}
|
||||
|
||||
fn validate_discriminated_subobject_with_base(
|
||||
object_value: &BTreeMap<Value, Value>,
|
||||
object_value: &Object,
|
||||
discriminated_subobject: &crate::schema::DiscriminatedSubobject,
|
||||
base_properties: &BTreeMap<String, Schema>,
|
||||
base_additional_properties: Option<&Schema>,
|
||||
@@ -653,7 +654,7 @@ impl SchemaValidator {
|
||||
}
|
||||
|
||||
fn validate_subobject(
|
||||
object_value: &BTreeMap<Value, Value>,
|
||||
object_value: &Object,
|
||||
subobject: &crate::schema::Subobject,
|
||||
path: &str,
|
||||
) -> Result<(), ValidationError> {
|
||||
|
||||
@@ -11,6 +11,18 @@
|
||||
clippy::as_conversions
|
||||
)] // value helpers index paths directly for performance
|
||||
|
||||
mod object;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[allow(unused_imports)] // surface for downstream PRs
|
||||
pub use object::{IntoIter, Iter, IterMut, Object};
|
||||
|
||||
#[cfg(feature = "rvm")]
|
||||
#[allow(unused_imports)] // surface for downstream PRs
|
||||
pub use object::ObjectCursor;
|
||||
|
||||
use crate::number::Number;
|
||||
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
@@ -23,7 +35,7 @@ use core::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use serde::de::{self, Deserializer, Error as DeError, MapAccess, SeqAccess, Visitor};
|
||||
use serde::ser::{SerializeMap, Serializer};
|
||||
use serde::ser::Serializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::*;
|
||||
@@ -63,7 +75,7 @@ pub enum Value {
|
||||
|
||||
/// An object.
|
||||
/// Unlike JSON, keys can be any value, not just string.
|
||||
Object(Rc<BTreeMap<Value, Value>>),
|
||||
Object(Rc<Object>),
|
||||
|
||||
/// Undefined value.
|
||||
/// Used to indicate the absence of a value.
|
||||
@@ -86,26 +98,15 @@ impl Serialize for Value {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
use serde::ser::Error;
|
||||
match self {
|
||||
Value::Null => serializer.serialize_unit(),
|
||||
Value::Bool(b) => serializer.serialize_bool(*b),
|
||||
Value::String(s) => serializer.serialize_str(s.as_ref()),
|
||||
Value::Number(n) => n.serialize(serializer),
|
||||
Value::Array(a) => a.serialize(serializer),
|
||||
Value::Object(fields) => {
|
||||
let mut map = serializer.serialize_map(Some(fields.len()))?;
|
||||
for (k, v) in fields.iter() {
|
||||
match k {
|
||||
Value::String(_) => map.serialize_entry(k, v)?,
|
||||
_ => {
|
||||
let key_str = serde_json::to_string(k).map_err(Error::custom)?;
|
||||
map.serialize_entry(&key_str, v)?
|
||||
}
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
// Delegate to the Object/Set serializers — single canonical path,
|
||||
// handles non-string-key stringification internally.
|
||||
Value::Object(fields) => fields.serialize(serializer),
|
||||
|
||||
// display set as an array
|
||||
Value::Set(s) => s.serialize(serializer),
|
||||
@@ -345,7 +346,7 @@ impl Value {
|
||||
/// assert_eq!(array[4], Value::from(12345u64));
|
||||
/// let obj = array[5].as_object().expect("not an object");
|
||||
/// assert_eq!(obj.len(), 1);
|
||||
/// assert_eq!(obj[&Value::from("name")], Value::from("regorus"));
|
||||
/// assert_eq!(obj.get(&Value::from("name")).expect("missing name"), &Value::from("regorus"));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@@ -800,7 +801,7 @@ impl From<BTreeMap<Value, Value>> for Value {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
fn from(s: BTreeMap<Value, Value>) -> Self {
|
||||
Value::Object(Rc::new(s))
|
||||
Value::Object(Rc::new(Object::from(s)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1279,16 +1280,16 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast value to [`& BTreeMap<Value, Value>`] if [`Value::Object`].
|
||||
/// Cast value to [`&Object`] if [`Value::Object`].
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # use std::collections::BTreeMap;
|
||||
/// # use regorus::value::Object;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let v = Value::from(
|
||||
/// [(Value::from("Hello"), Value::from("World"))]
|
||||
/// .iter()
|
||||
/// .cloned()
|
||||
/// .collect::<BTreeMap<Value, Value>>(),
|
||||
/// .collect::<Object>(),
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// v.as_object()?.iter().next(),
|
||||
@@ -1296,28 +1297,28 @@ impl Value {
|
||||
/// );
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn as_object(&self) -> Result<&BTreeMap<Value, Value>> {
|
||||
pub fn as_object(&self) -> Result<&Object> {
|
||||
match self {
|
||||
Value::Object(m) => Ok(m),
|
||||
_ => Err(anyhow!("not an object")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast value to [`&mut BTreeMap<Value, Value>`] if [`Value::Object`].
|
||||
/// Cast value to [`&mut Object`] if [`Value::Object`].
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # use std::collections::BTreeMap;
|
||||
/// # use regorus::value::Object;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut v = Value::from(
|
||||
/// [(Value::from("Hello"), Value::from("World"))]
|
||||
/// .iter()
|
||||
/// .cloned()
|
||||
/// .collect::<BTreeMap<Value, Value>>(),
|
||||
/// .collect::<Object>(),
|
||||
/// );
|
||||
/// v.as_object_mut()?.insert(Value::from("Good"), Value::from("Bye"));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn as_object_mut(&mut self) -> Result<&mut BTreeMap<Value, Value>> {
|
||||
pub fn as_object_mut(&mut self) -> Result<&mut Object> {
|
||||
match self {
|
||||
Value::Object(m) => Ok(Rc::make_mut(m)),
|
||||
_ => Err(anyhow!("not an object")),
|
||||
148
src/value/object/iter.rs
Normal file
148
src/value/object/iter.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Opaque iterator types for [`Object`].
|
||||
//!
|
||||
//! These newtypes wrap the storage backend's iterators so the backend can be
|
||||
//! swapped without changing any iterator type signatures observed by callers.
|
||||
|
||||
use alloc::collections::btree_map;
|
||||
use core::iter::FusedIterator;
|
||||
|
||||
use super::Object;
|
||||
use crate::value::Value;
|
||||
|
||||
/// Owned iterator over `(Value, Value)` entries.
|
||||
#[derive(Debug)]
|
||||
pub struct IntoIter {
|
||||
pub(super) inner: btree_map::IntoIter<Value, Value>,
|
||||
}
|
||||
|
||||
impl Iterator for IntoIter {
|
||||
type Item = (Value, Value);
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleEndedIterator for IntoIter {
|
||||
#[inline]
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next_back()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for IntoIter {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for IntoIter {}
|
||||
|
||||
/// Borrowed iterator over `(&Value, &Value)` entries.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Iter<'a> {
|
||||
pub(super) inner: btree_map::Iter<'a, Value, Value>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (&'a Value, &'a Value);
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for Iter<'a> {
|
||||
#[inline]
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next_back()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for Iter<'a> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FusedIterator for Iter<'a> {}
|
||||
|
||||
/// Borrowed iterator over `(&Value, &mut Value)` entries.
|
||||
#[derive(Debug)]
|
||||
pub struct IterMut<'a> {
|
||||
pub(super) inner: btree_map::IterMut<'a, Value, Value>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IterMut<'a> {
|
||||
type Item = (&'a Value, &'a mut Value);
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for IterMut<'a> {
|
||||
#[inline]
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next_back()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for IterMut<'a> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FusedIterator for IterMut<'a> {}
|
||||
|
||||
impl IntoIterator for Object {
|
||||
type Item = (Value, Value);
|
||||
type IntoIter = IntoIter;
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IntoIter {
|
||||
inner: self.inner.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Object {
|
||||
type Item = (&'a Value, &'a Value);
|
||||
type IntoIter = Iter<'a>;
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
Iter {
|
||||
inner: self.inner.iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Object {
|
||||
type Item = (&'a Value, &'a mut Value);
|
||||
type IntoIter = IterMut<'a>;
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IterMut {
|
||||
inner: self.inner.iter_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
265
src/value/object/mod.rs
Normal file
265
src/value/object/mod.rs
Normal file
@@ -0,0 +1,265 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! See [`Object`].
|
||||
|
||||
mod iter;
|
||||
mod serde;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use core::cmp::Ordering;
|
||||
use core::fmt;
|
||||
use core::ops::Bound;
|
||||
|
||||
use crate::value::Value;
|
||||
|
||||
pub use iter::{IntoIter, Iter, IterMut};
|
||||
|
||||
/// Opaque, ordered key-value map keyed by [`Value`].
|
||||
///
|
||||
/// The current backing storage is `BTreeMap<Value, Value>`. The inner field
|
||||
/// is private so the representation can change (two-tier inline+hash, lazy,
|
||||
/// schema-shared) without touching call sites.
|
||||
///
|
||||
/// # Iteration
|
||||
///
|
||||
/// - [`Object::iter`] — implementation-defined order; non-resumable.
|
||||
/// - [`Object::iter_sorted`] — sorted by `Value::Ord`; non-resumable.
|
||||
/// - [`Object::cursor`] / [`Object::next`] — implementation-defined order,
|
||||
/// resumable; cheapest per-step cost. Used by interpreter/RVM when iteration
|
||||
/// must yield mid-flight.
|
||||
#[derive(Default, Clone, Eq, PartialEq)]
|
||||
pub struct Object {
|
||||
inner: BTreeMap<Value, Value>,
|
||||
}
|
||||
|
||||
impl Object {
|
||||
/// Create an empty `Object`.
|
||||
#[inline]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
inner: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, key: &Value) -> Option<&Value> {
|
||||
self.inner.get(key)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_key(&self, key: &Value) -> bool {
|
||||
self.inner.contains_key(key)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, key: &Value) -> Option<&mut Value> {
|
||||
self.inner.get_mut(key)
|
||||
}
|
||||
|
||||
/// Iteration in implementation-defined order. Non-resumable.
|
||||
///
|
||||
/// For the current BTree-backed storage this happens to be sorted, but
|
||||
/// callers MUST NOT depend on that. Use [`Object::iter_sorted`] when
|
||||
/// deterministic order is required, or [`Object::cursor`] when iteration
|
||||
/// must yield and resume.
|
||||
#[inline]
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Value, &Value)> + '_ {
|
||||
self.inner.iter()
|
||||
}
|
||||
|
||||
/// Iteration in sorted key order (by `Value::Ord`). Non-resumable.
|
||||
///
|
||||
/// Use this for serialization, snapshots, hashing, `Debug`, the
|
||||
/// `object.keys` builtin, etc.
|
||||
#[inline]
|
||||
pub fn iter_sorted(&self) -> Iter<'_> {
|
||||
// BTree backend iterates sorted natively.
|
||||
Iter {
|
||||
inner: self.inner.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn keys(&self) -> impl Iterator<Item = &Value> + '_ {
|
||||
self.inner.keys()
|
||||
}
|
||||
|
||||
/// Keys in sorted order (by `Value::Ord`). Symmetric with
|
||||
/// [`Object::iter_sorted`].
|
||||
#[inline]
|
||||
pub fn keys_sorted(&self) -> impl Iterator<Item = &Value> + '_ {
|
||||
self.iter_sorted().map(|(k, _)| k)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn values(&self) -> impl Iterator<Item = &Value> + '_ {
|
||||
self.inner.values()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter_mut(&mut self) -> IterMut<'_> {
|
||||
IterMut {
|
||||
inner: self.inner.iter_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a key-value pair. Returns the previous value if any.
|
||||
#[inline]
|
||||
pub fn insert(&mut self, key: Value, value: Value) -> Option<Value> {
|
||||
self.inner.insert(key, value)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn remove(&mut self, key: &Value) -> Option<Value> {
|
||||
self.inner.remove(key)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn retain<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnMut(&Value, &mut Value) -> bool,
|
||||
{
|
||||
self.inner.retain(f);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear(&mut self) {
|
||||
self.inner.clear();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn append(&mut self, other: &mut Object) {
|
||||
self.inner.append(&mut other.inner);
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the value associated with `key`, inserting
|
||||
/// the result of `default()` if absent. Single O(log n) probe.
|
||||
pub fn get_or_insert_with<F: FnOnce() -> Value>(
|
||||
&mut self,
|
||||
key: Value,
|
||||
default: F,
|
||||
) -> &mut Value {
|
||||
self.inner.entry(key).or_insert_with(default)
|
||||
}
|
||||
|
||||
/// Wrap into a `Value::Object`.
|
||||
#[inline]
|
||||
pub fn into_value(self) -> Value {
|
||||
Value::Object(crate::Rc::new(self))
|
||||
}
|
||||
|
||||
/// Create a resumable cursor over entries in implementation-defined
|
||||
/// order. Stable for the lifetime of `&self`. O(1).
|
||||
///
|
||||
/// The cursor is fully self-owned (it stores a clone of the last-seen
|
||||
/// key, not a reference) so it can be stored as a field of a
|
||||
/// long-lived state struct — e.g. an RVM iteration frame that persists
|
||||
/// across instruction dispatches. As a consequence, mutating the
|
||||
/// `Object` between `next()` calls is not rejected by the borrow
|
||||
/// checker; the resulting iteration order in that case is unspecified.
|
||||
#[inline]
|
||||
pub const fn cursor(&self) -> ObjectCursor {
|
||||
ObjectCursor {
|
||||
inner: ObjectCursorInner::BTree(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance `cursor` and yield the next entry. O(log n) for the BTree
|
||||
/// backend (range probe); future hash/inline variants may be O(1).
|
||||
pub fn next<'a>(&'a self, cursor: &mut ObjectCursor) -> Option<(&'a Value, &'a Value)> {
|
||||
let ObjectCursorInner::BTree(ref mut last) = cursor.inner;
|
||||
let next = last.as_ref().map_or_else(
|
||||
|| self.inner.iter().next(),
|
||||
|prev| {
|
||||
self.inner
|
||||
.range((Bound::Excluded(prev.clone()), Bound::Unbounded))
|
||||
.next()
|
||||
},
|
||||
);
|
||||
let (k, v) = next?;
|
||||
*last = Some(k.clone());
|
||||
Some((k, v))
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque resumable cursor over an [`Object`]'s entries in
|
||||
/// implementation-defined order.
|
||||
///
|
||||
/// Self-owned: holds no borrow on the `Object`, so it can be stored as a
|
||||
/// field of a long-lived state struct (e.g. an RVM iteration frame).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObjectCursor {
|
||||
inner: ObjectCursorInner,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ObjectCursorInner {
|
||||
/// BTree backend cursor: tracks last-seen key. `None` means "before start".
|
||||
BTree(Option<Value>),
|
||||
}
|
||||
|
||||
// ---- Hand-written Ord/PartialOrd ----------------------------------------
|
||||
//
|
||||
// Implemented in terms of `iter_sorted()` so ordering is consistent with the
|
||||
// canonical (sorted) view of the entries and is therefore independent of
|
||||
// the storage variant.
|
||||
|
||||
impl Ord for Object {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.iter_sorted().cmp(other.iter_sorted())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Object {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Object {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Use sorted iteration so Debug output is stable across storage
|
||||
// variants.
|
||||
f.debug_map().entries(self.iter_sorted()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<(Value, Value)> for Object {
|
||||
fn extend<I: IntoIterator<Item = (Value, Value)>>(&mut self, iter: I) {
|
||||
self.inner.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(Value, Value)> for Object {
|
||||
fn from_iter<I: IntoIterator<Item = (Value, Value)>>(iter: I) -> Self {
|
||||
Self {
|
||||
inner: BTreeMap::from_iter(iter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<Value, Value>> for Object {
|
||||
#[inline]
|
||||
fn from(map: BTreeMap<Value, Value>) -> Self {
|
||||
Self { inner: map }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Object> for Value {
|
||||
#[inline]
|
||||
fn from(o: Object) -> Self {
|
||||
o.into_value()
|
||||
}
|
||||
}
|
||||
59
src/value/object/serde.rs
Normal file
59
src/value/object/serde.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Serde `Serialize`/`Deserialize` impls for [`Object`].
|
||||
|
||||
use alloc::string::ToString as _;
|
||||
use core::fmt;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error as _, MapAccess, Visitor};
|
||||
use serde::ser::{Serialize, SerializeMap as _, Serializer};
|
||||
|
||||
use super::Object;
|
||||
use crate::value::Value;
|
||||
|
||||
impl Serialize for Object {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
use serde::ser::Error;
|
||||
let mut map = serializer.serialize_map(Some(self.len()))?;
|
||||
// Sorted iteration: canonical JSON.
|
||||
for (k, v) in self.iter_sorted() {
|
||||
match *k {
|
||||
Value::String(_) => map.serialize_entry(k, v)?,
|
||||
_ => {
|
||||
// Non-string keys are stringified via serde_json::to_string
|
||||
// so the resulting JSON has valid string keys.
|
||||
let key_str = serde_json::to_string(k).map_err(Error::custom)?;
|
||||
map.serialize_entry(&key_str, v)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
struct ObjectVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ObjectVisitor {
|
||||
type Value = Object;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("a map of Value to Value")
|
||||
}
|
||||
|
||||
fn visit_map<A: MapAccess<'de>>(self, mut access: A) -> Result<Self::Value, A::Error> {
|
||||
let mut obj = Object::new();
|
||||
while let Some((k, v)) = access.next_entry::<Value, Value>()? {
|
||||
obj.insert(k, v);
|
||||
crate::utils::limits::check_memory_limit_if_needed()
|
||||
.map_err(|err| A::Error::custom(err.to_string()))?;
|
||||
}
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Object {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_map(ObjectVisitor)
|
||||
}
|
||||
}
|
||||
564
src/value/tests.rs
Normal file
564
src/value/tests.rs
Normal file
@@ -0,0 +1,564 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#![allow(
|
||||
clippy::expect_used,
|
||||
clippy::unwrap_used,
|
||||
clippy::indexing_slicing,
|
||||
clippy::as_conversions,
|
||||
clippy::arithmetic_side_effects,
|
||||
clippy::unseparated_literal_suffix,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::option_if_let_else,
|
||||
clippy::pattern_type_mismatch
|
||||
)]
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::Object;
|
||||
use crate::value::Value;
|
||||
|
||||
fn val(i: u64) -> Value {
|
||||
Value::from(i)
|
||||
}
|
||||
|
||||
fn make_pairs(n: u64) -> Vec<(Value, Value)> {
|
||||
(0..n).map(|i| (val(i), val(i.saturating_mul(2)))).collect()
|
||||
}
|
||||
|
||||
const SIZES: &[u64] = &[0, 1, 2, 4, 8, 64, 256, 1024];
|
||||
|
||||
/// `iter_sorted` must yield entries in the same order as a `BTreeMap` oracle.
|
||||
#[test]
|
||||
fn object_iter_sorted_matches_btreemap_oracle() {
|
||||
for &n in SIZES {
|
||||
let pairs = make_pairs(n);
|
||||
let oracle: BTreeMap<Value, Value> = pairs.iter().cloned().collect();
|
||||
let obj: Object = pairs.into_iter().collect();
|
||||
let actual: Vec<(&Value, &Value)> = obj.iter_sorted().collect();
|
||||
let expected: Vec<(&Value, &Value)> = oracle.iter().collect();
|
||||
assert_eq!(actual, expected, "size {n}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `iter` may be in any order, but as a multiset must equal the oracle's entries.
|
||||
#[test]
|
||||
fn object_iter_multiset_equality_with_oracle() {
|
||||
for &n in SIZES {
|
||||
let pairs = make_pairs(n);
|
||||
let oracle: BTreeMap<Value, Value> = pairs.iter().cloned().collect();
|
||||
let obj: Object = pairs.into_iter().collect();
|
||||
assert_eq!(obj.len(), oracle.len(), "size {n}");
|
||||
let mut a: Vec<(Value, Value)> = obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
|
||||
let mut b: Vec<(Value, Value)> =
|
||||
oracle.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
|
||||
a.sort();
|
||||
b.sort();
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize-then-deserialize must round-trip through JSON without loss.
|
||||
#[test]
|
||||
fn object_serde_roundtrip() {
|
||||
for &n in &[0_u64, 1, 8, 64] {
|
||||
let pairs: Vec<(Value, Value)> = (0..n)
|
||||
.map(|i| (Value::String(format!("k{i}").into()), val(i)))
|
||||
.collect();
|
||||
let obj: Object = pairs.into_iter().collect();
|
||||
let json = serde_json::to_string(&obj).expect("ser");
|
||||
let back: Object = serde_json::from_str(&json).expect("de");
|
||||
assert_eq!(obj, back, "size {n}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Equality depends only on contents, not the order keys were inserted.
|
||||
#[test]
|
||||
fn object_eq_invariant_to_insertion_order() {
|
||||
let mut a = Object::new();
|
||||
let mut b = Object::new();
|
||||
for i in 0..32_u64 {
|
||||
a.insert(val(i), val(i.saturating_add(1)));
|
||||
}
|
||||
for i in (0..32_u64).rev() {
|
||||
b.insert(val(i), val(i.saturating_add(1)));
|
||||
}
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
/// `remove` returns the prior value (or `None`) and `retain` keeps only matching entries.
|
||||
#[test]
|
||||
fn object_remove_and_retain() {
|
||||
let mut obj: Object = make_pairs(16).into_iter().collect();
|
||||
assert_eq!(obj.remove(&val(0)), Some(val(0)));
|
||||
assert!(obj.remove(&val(100)).is_none());
|
||||
obj.retain(|_, v| {
|
||||
if let Value::Number(ref n) = *v {
|
||||
n.as_u64().is_some_and(|x| x % 4 == 0)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
for (_, v) in obj.iter_sorted() {
|
||||
if let Value::Number(ref n) = *v {
|
||||
assert_eq!(n.as_u64().expect("u64") % 4, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `IntoIterator` for `Object` (by value) yields every entry exactly once.
|
||||
#[test]
|
||||
fn object_into_iterator_owned() {
|
||||
let obj: Object = make_pairs(8).into_iter().collect();
|
||||
let collected: Vec<(Value, Value)> = obj.into_iter().collect();
|
||||
assert_eq!(collected.len(), 8);
|
||||
}
|
||||
|
||||
// ---- Duplicate-key semantics --------------------------------------------
|
||||
|
||||
/// `FromIterator` keeps the last value when the same key appears multiple times.
|
||||
#[test]
|
||||
fn object_from_iter_last_wins_on_duplicate_keys() {
|
||||
let obj = Object::from_iter([(val(0), val(1)), (val(0), val(2))]);
|
||||
assert_eq!(obj.get(&val(0)), Some(&val(2)));
|
||||
assert_eq!(obj.len(), 1);
|
||||
}
|
||||
|
||||
/// `From<BTreeMap>` adopts `BTreeMap`'s own last-write-wins semantics for duplicates.
|
||||
#[test]
|
||||
fn object_from_btreemap_last_wins_on_duplicate_keys() {
|
||||
let mut bm: BTreeMap<Value, Value> = BTreeMap::new();
|
||||
bm.insert(val(0), val(1));
|
||||
bm.insert(val(0), val(2));
|
||||
let obj: Object = bm.into();
|
||||
assert_eq!(obj.get(&val(0)), Some(&val(2)));
|
||||
assert_eq!(obj.len(), 1);
|
||||
}
|
||||
|
||||
// ---- get_or_insert_with --------------------------------------------------
|
||||
|
||||
/// `get_or_insert_with` inserts the default when the key is absent and returns a mutable ref to it.
|
||||
#[test]
|
||||
fn object_get_or_insert_with_inserts_when_absent() {
|
||||
let mut obj = Object::new();
|
||||
let v = obj.get_or_insert_with(val(7), || val(42));
|
||||
assert_eq!(*v, val(42));
|
||||
*v = val(43);
|
||||
assert_eq!(obj.get(&val(7)), Some(&val(43)));
|
||||
}
|
||||
|
||||
/// `get_or_insert_with` returns the existing value and never invokes the default closure.
|
||||
#[test]
|
||||
fn object_get_or_insert_with_returns_existing_when_present() {
|
||||
let mut obj = Object::new();
|
||||
obj.insert(val(7), val(1));
|
||||
let mut closure_called = false;
|
||||
let v = obj.get_or_insert_with(val(7), || {
|
||||
closure_called = true;
|
||||
val(999)
|
||||
});
|
||||
assert_eq!(*v, val(1));
|
||||
assert!(!closure_called, "default closure must not run when present");
|
||||
}
|
||||
|
||||
// ---- Accessor coverage ---------------------------------------------------
|
||||
|
||||
/// Smoke-test every accessor: `contains_key`/`get`/`get_mut`/`keys`/`values`/`iter`/`iter_mut`/`append`/`clear`.
|
||||
#[test]
|
||||
fn object_accessor_coverage() {
|
||||
let mut obj: Object = make_pairs(4).into_iter().collect();
|
||||
|
||||
assert!(obj.contains_key(&val(0)));
|
||||
assert!(!obj.contains_key(&val(100)));
|
||||
|
||||
assert_eq!(obj.get(&val(2)), Some(&val(4)));
|
||||
|
||||
if let Some(v) = obj.get_mut(&val(1)) {
|
||||
*v = val(999);
|
||||
}
|
||||
assert_eq!(obj.get(&val(1)), Some(&val(999)));
|
||||
|
||||
let keys: Vec<&Value> = obj.keys().collect();
|
||||
assert_eq!(keys.len(), 4);
|
||||
let values: Vec<&Value> = obj.values().collect();
|
||||
assert_eq!(values.len(), 4);
|
||||
|
||||
for (_, v) in obj.iter_mut() {
|
||||
*v = val(0);
|
||||
}
|
||||
for (_, v) in obj.iter() {
|
||||
assert_eq!(*v, val(0));
|
||||
}
|
||||
|
||||
let mut other = Object::new();
|
||||
other.insert(val(100), val(200));
|
||||
obj.append(&mut other);
|
||||
assert!(other.is_empty());
|
||||
assert!(obj.contains_key(&val(100)));
|
||||
|
||||
obj.clear();
|
||||
assert!(obj.is_empty());
|
||||
}
|
||||
|
||||
// ---- IntoIterator for references -----------------------------------------
|
||||
|
||||
/// `IntoIterator` for `&Object` yields shared refs to every entry.
|
||||
#[test]
|
||||
fn object_into_iterator_ref() {
|
||||
let obj: Object = make_pairs(4).into_iter().collect();
|
||||
let mut count = 0;
|
||||
for (_k, _v) in &obj {
|
||||
count += 1;
|
||||
}
|
||||
assert_eq!(count, 4);
|
||||
}
|
||||
|
||||
/// `IntoIterator` for `&mut Object` exposes mutable refs to values; mutations persist.
|
||||
#[test]
|
||||
fn object_into_iterator_ref_mut() {
|
||||
let mut obj: Object = make_pairs(4).into_iter().collect();
|
||||
for (_k, v) in &mut obj {
|
||||
*v = val(0);
|
||||
}
|
||||
for (_, v) in obj.iter() {
|
||||
assert_eq!(*v, val(0));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Cursor tests --------------------------------------------------------
|
||||
|
||||
/// Driving `cursor`+`next` to completion visits each entry exactly once.
|
||||
#[test]
|
||||
fn object_cursor_yields_every_entry_once() {
|
||||
for &n in SIZES {
|
||||
let pairs = make_pairs(n);
|
||||
let obj: Object = pairs.clone().into_iter().collect();
|
||||
let mut cursor = obj.cursor();
|
||||
let mut collected: Vec<(Value, Value)> = Vec::new();
|
||||
while let Some((k, v)) = obj.next(&mut cursor) {
|
||||
collected.push((k.clone(), v.clone()));
|
||||
}
|
||||
let mut a = collected;
|
||||
a.sort();
|
||||
let mut b = pairs;
|
||||
b.sort();
|
||||
assert_eq!(a, b, "size {n}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A freshly-constructed cursor restarts from the beginning, independent of any prior cursor's state.
|
||||
#[test]
|
||||
fn object_cursor_resumable_fresh_cursor_restarts() {
|
||||
let obj: Object = make_pairs(8).into_iter().collect();
|
||||
let mut c1 = obj.cursor();
|
||||
let _ = obj.next(&mut c1);
|
||||
let _ = obj.next(&mut c1);
|
||||
let mut c2 = obj.cursor();
|
||||
let first_again = obj.next(&mut c2);
|
||||
let first_original = obj.iter().next();
|
||||
assert_eq!(
|
||||
first_again.map(|(k, v)| (k.clone(), v.clone())),
|
||||
first_original.map(|(k, v)| (k.clone(), v.clone()))
|
||||
);
|
||||
}
|
||||
|
||||
/// When `Object` is shared via `Rc`, `Rc::make_mut` clones — leaving an in-flight cursor on the original snapshot unaffected.
|
||||
#[test]
|
||||
fn object_cursor_snapshot_independence_via_rc() {
|
||||
use crate::Rc;
|
||||
let mut obj = Object::new();
|
||||
obj.insert(Value::from("a"), Value::from(1));
|
||||
obj.insert(Value::from("b"), Value::from(2));
|
||||
obj.insert(Value::from("c"), Value::from(3));
|
||||
let rc_obj = Rc::new(obj);
|
||||
|
||||
let alias = Rc::clone(&rc_obj);
|
||||
let mut cursor = rc_obj.cursor();
|
||||
let _ = rc_obj.next(&mut cursor);
|
||||
|
||||
let mut alias_for_mut = alias;
|
||||
Rc::make_mut(&mut alias_for_mut).insert(Value::from("d"), Value::from(4));
|
||||
Rc::make_mut(&mut alias_for_mut).remove(&Value::from("a"));
|
||||
|
||||
assert_eq!(rc_obj.len(), 3);
|
||||
let mut remaining = 0;
|
||||
while rc_obj.next(&mut cursor).is_some() {
|
||||
remaining += 1;
|
||||
}
|
||||
assert_eq!(remaining, 2);
|
||||
}
|
||||
|
||||
/// A cursor over an empty `Object` returns `None` on the first call.
|
||||
#[test]
|
||||
fn object_cursor_empty_returns_none_immediately() {
|
||||
let obj = Object::new();
|
||||
let mut cursor = obj.cursor();
|
||||
assert!(obj.next(&mut cursor).is_none());
|
||||
}
|
||||
|
||||
/// Mutating an `Object` between `next()` calls is well-defined: the cursor
|
||||
/// must not panic and must terminate. The visit order, and whether
|
||||
/// inserted/removed keys appear, is intentionally unspecified — this test
|
||||
/// only pins the safety + termination guarantees that callers (e.g. a
|
||||
/// future RVM iteration frame) may rely on. It must NOT assert any
|
||||
/// particular order or count, or future backend swaps will be forced to
|
||||
/// honor an accidental contract.
|
||||
#[test]
|
||||
fn object_cursor_mutation_between_steps_is_safe_and_terminates() {
|
||||
let mut obj: Object = make_pairs(16).into_iter().collect();
|
||||
let mut cursor = obj.cursor();
|
||||
|
||||
// Yield a few entries before mutating.
|
||||
for _ in 0..3 {
|
||||
let _ = obj.next(&mut cursor);
|
||||
}
|
||||
|
||||
// Interleave mutations and steps. Each yielded entry must, at the
|
||||
// moment of yield, be a real entry in the map.
|
||||
obj.insert(val(100), val(100));
|
||||
if let Some((k, v)) = obj.next(&mut cursor) {
|
||||
assert_eq!(obj.get(k), Some(v));
|
||||
}
|
||||
obj.remove(&val(2));
|
||||
if let Some((k, v)) = obj.next(&mut cursor) {
|
||||
assert_eq!(obj.get(k), Some(v));
|
||||
}
|
||||
obj.clear();
|
||||
// After clear(), draining the cursor must terminate (not panic, not
|
||||
// loop) within a bounded number of calls.
|
||||
let mut terminated = false;
|
||||
for _ in 0..32 {
|
||||
if obj.next(&mut cursor).is_none() {
|
||||
terminated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(terminated, "cursor failed to terminate after clear()");
|
||||
}
|
||||
|
||||
// ---- Hand-written Ord consistency ---------------------------------------
|
||||
|
||||
/// `Ord` (built atop `iter_sorted`) is invariant to insertion order.
|
||||
#[test]
|
||||
fn object_ord_invariant_to_insertion_order() {
|
||||
let mut a = Object::new();
|
||||
let mut b = Object::new();
|
||||
for i in 0..16_u64 {
|
||||
a.insert(val(i), val(i.saturating_add(1)));
|
||||
}
|
||||
for i in (0..16_u64).rev() {
|
||||
b.insert(val(i), val(i.saturating_add(1)));
|
||||
}
|
||||
use core::cmp::Ordering;
|
||||
assert_eq!(a.cmp(&b), Ordering::Equal);
|
||||
}
|
||||
|
||||
/// `Ord` agrees with lexicographic comparison of the sorted-entries view.
|
||||
#[test]
|
||||
fn object_ord_lexicographic_on_sorted_entries() {
|
||||
let a: Object = [(val(0), val(0)), (val(1), val(1))].into_iter().collect();
|
||||
let b: Object = [(val(0), val(0)), (val(2), val(2))].into_iter().collect();
|
||||
assert!(a < b);
|
||||
}
|
||||
|
||||
/// `empty < non_empty` and a shorter prefix compares less than its extension.
|
||||
#[test]
|
||||
fn object_ord_empty_and_prefix() {
|
||||
use core::cmp::Ordering;
|
||||
let empty = Object::new();
|
||||
let one: Object = [(val(0), val(0))].into_iter().collect();
|
||||
let two: Object = [(val(0), val(0)), (val(1), val(1))].into_iter().collect();
|
||||
assert_eq!(empty.cmp(&one), Ordering::Less);
|
||||
assert_eq!(one.cmp(&two), Ordering::Less);
|
||||
assert_eq!(two.cmp(&empty), Ordering::Greater);
|
||||
}
|
||||
|
||||
/// When keys match, `Ord` falls through to comparing values.
|
||||
#[test]
|
||||
fn object_ord_breaks_ties_on_values() {
|
||||
use core::cmp::Ordering;
|
||||
let a: Object = [(val(0), val(1))].into_iter().collect();
|
||||
let b: Object = [(val(0), val(2))].into_iter().collect();
|
||||
assert_eq!(a.cmp(&b), Ordering::Less);
|
||||
}
|
||||
|
||||
/// `PartialOrd` must agree with `Ord` for every input pair.
|
||||
#[test]
|
||||
fn object_partial_cmp_matches_cmp() {
|
||||
let a: Object = [(val(0), val(0)), (val(1), val(1))].into_iter().collect();
|
||||
let b: Object = [(val(0), val(0)), (val(2), val(2))].into_iter().collect();
|
||||
assert_eq!(a.partial_cmp(&b), Some(a.cmp(&b)));
|
||||
assert_eq!(b.partial_cmp(&a), Some(b.cmp(&a)));
|
||||
assert_eq!(a.partial_cmp(&a), Some(core::cmp::Ordering::Equal));
|
||||
}
|
||||
|
||||
// ---- Debug / keys_sorted determinism ------------------------------------
|
||||
|
||||
/// `Debug` output is byte-identical for equal Objects regardless of insertion order.
|
||||
#[test]
|
||||
fn object_debug_invariant_to_insertion_order() {
|
||||
let mut a = Object::new();
|
||||
let mut b = Object::new();
|
||||
for i in 0..8_u64 {
|
||||
a.insert(val(i), val(i));
|
||||
}
|
||||
for i in (0..8_u64).rev() {
|
||||
b.insert(val(i), val(i));
|
||||
}
|
||||
assert_eq!(format!("{a:?}"), format!("{b:?}"));
|
||||
}
|
||||
|
||||
/// `keys_sorted` yields exactly `iter_sorted().map(|(k,_)| k)`.
|
||||
#[test]
|
||||
fn object_keys_sorted_matches_iter_sorted_keys() {
|
||||
let obj: Object = make_pairs(16).into_iter().collect();
|
||||
let from_keys: Vec<&Value> = obj.keys_sorted().collect();
|
||||
let from_iter: Vec<&Value> = obj.iter_sorted().map(|(k, _)| k).collect();
|
||||
assert_eq!(from_keys, from_iter);
|
||||
}
|
||||
|
||||
// ---- Serde: non-string keys & determinism --------------------------------
|
||||
|
||||
/// `Serialize` stringifies non-string keys, and equal Objects produce identical JSON
|
||||
/// regardless of insertion order.
|
||||
#[test]
|
||||
fn object_serialize_non_string_keys_and_deterministic() {
|
||||
let pairs = [
|
||||
(Value::from("alpha"), val(1)),
|
||||
(Value::Bool(true), val(2)),
|
||||
(val(7), val(3)),
|
||||
];
|
||||
let a: Object = pairs.iter().cloned().collect();
|
||||
let mut b = Object::new();
|
||||
for (k, v) in pairs.iter().rev().cloned() {
|
||||
b.insert(k, v);
|
||||
}
|
||||
let ja = serde_json::to_string(&a).expect("ser a");
|
||||
let jb = serde_json::to_string(&b).expect("ser b");
|
||||
assert_eq!(ja, jb, "serialization must be deterministic");
|
||||
|
||||
// Non-string keys appear as quoted strings in the resulting JSON.
|
||||
let v: serde_json::Value = serde_json::from_str(&ja).expect("parse");
|
||||
let obj = v.as_object().expect("json object");
|
||||
assert!(
|
||||
obj.contains_key("true"),
|
||||
"bool key was not stringified: {ja}"
|
||||
);
|
||||
assert!(
|
||||
obj.contains_key("7"),
|
||||
"number key was not stringified: {ja}"
|
||||
);
|
||||
assert!(obj.contains_key("alpha"));
|
||||
}
|
||||
|
||||
// ---- Extend / append duplicate-key semantics -----------------------------
|
||||
|
||||
/// `extend` overwrites existing entries (last-write-wins) and preserves length when
|
||||
/// only existing keys are touched.
|
||||
#[test]
|
||||
fn object_extend_last_wins_and_empty_noop() {
|
||||
let mut obj: Object = [(val(0), val(0)), (val(1), val(1))].into_iter().collect();
|
||||
obj.extend([(val(0), val(99))]);
|
||||
assert_eq!(obj.get(&val(0)), Some(&val(99)));
|
||||
assert_eq!(obj.len(), 2);
|
||||
|
||||
let before = obj.len();
|
||||
obj.extend(core::iter::empty::<(Value, Value)>());
|
||||
assert_eq!(obj.len(), before, "empty extend is a no-op");
|
||||
}
|
||||
|
||||
/// `append` drains `other` into `self`, overwriting on overlapping keys.
|
||||
#[test]
|
||||
fn object_append_overlapping_keys_drain_and_overwrite() {
|
||||
let mut a: Object = [(val(0), val(0)), (val(1), val(1))].into_iter().collect();
|
||||
let mut b: Object = [(val(1), val(99)), (val(2), val(2))].into_iter().collect();
|
||||
a.append(&mut b);
|
||||
assert!(b.is_empty(), "append must drain `other`");
|
||||
assert_eq!(a.len(), 3);
|
||||
assert_eq!(a.get(&val(1)), Some(&val(99)));
|
||||
assert_eq!(a.get(&val(2)), Some(&val(2)));
|
||||
}
|
||||
|
||||
// ---- Iterator trait surface ---------------------------------------------
|
||||
|
||||
/// `DoubleEndedIterator`/`ExactSizeIterator`/`FusedIterator` and `size_hint` all
|
||||
/// behave correctly across partial consumption from both ends.
|
||||
#[test]
|
||||
fn object_iter_sorted_double_ended_and_exact_size() {
|
||||
let obj: Object = make_pairs(4).into_iter().collect();
|
||||
let mut it = obj.iter_sorted();
|
||||
assert_eq!(it.len(), 4);
|
||||
assert_eq!(it.size_hint(), (4, Some(4)));
|
||||
|
||||
let first = it.next().expect("front");
|
||||
let last = it.next_back().expect("back");
|
||||
assert_eq!(it.len(), 2);
|
||||
assert_eq!(it.size_hint(), (2, Some(2)));
|
||||
assert_ne!(first.0, last.0, "front and back must differ for n=4");
|
||||
|
||||
// Drain remaining.
|
||||
while it.next().is_some() {}
|
||||
assert_eq!(it.len(), 0);
|
||||
// FusedIterator: stays None after exhaustion.
|
||||
assert!(it.next().is_none());
|
||||
assert!(it.next().is_none());
|
||||
assert!(it.next_back().is_none());
|
||||
}
|
||||
|
||||
/// `IntoIter` also honors `DoubleEndedIterator` and `ExactSizeIterator`.
|
||||
#[test]
|
||||
fn object_into_iter_double_ended_and_exact_size() {
|
||||
let obj: Object = make_pairs(4).into_iter().collect();
|
||||
let mut it = obj.into_iter();
|
||||
assert_eq!(it.len(), 4);
|
||||
let _ = it.next().expect("front");
|
||||
let _ = it.next_back().expect("back");
|
||||
assert_eq!(it.len(), 2);
|
||||
let collected: Vec<_> = it.collect();
|
||||
assert_eq!(collected.len(), 2);
|
||||
}
|
||||
|
||||
/// `IterMut` decrements its `len()` after consuming from the front.
|
||||
#[test]
|
||||
fn object_iter_mut_exact_size() {
|
||||
let mut obj: Object = make_pairs(3).into_iter().collect();
|
||||
let mut it = obj.iter_mut();
|
||||
assert_eq!(it.len(), 3);
|
||||
let _ = it.next().expect("front");
|
||||
assert_eq!(it.len(), 2);
|
||||
}
|
||||
|
||||
/// `Iter` is `Clone`; the clone iterates independently from the same point.
|
||||
#[test]
|
||||
fn object_iter_sorted_clone_is_independent() {
|
||||
let obj: Object = make_pairs(4).into_iter().collect();
|
||||
let mut a = obj.iter_sorted();
|
||||
let _ = a.next();
|
||||
let b = a.clone();
|
||||
let rest_a: Vec<_> = a.collect();
|
||||
let rest_b: Vec<_> = b.collect();
|
||||
assert_eq!(rest_a, rest_b);
|
||||
}
|
||||
|
||||
// ---- default / insert ---------------------------------------------------
|
||||
|
||||
/// `Object::default()` and `Object::new()` produce equal, empty Objects.
|
||||
#[test]
|
||||
fn object_default_equals_new_and_is_empty() {
|
||||
let a = Object::default();
|
||||
let b = Object::new();
|
||||
assert_eq!(a, b);
|
||||
assert!(a.is_empty());
|
||||
assert_eq!(a.len(), 0);
|
||||
}
|
||||
|
||||
/// `insert` returns `None` for a fresh key and `Some(old)` when overwriting.
|
||||
#[test]
|
||||
fn object_insert_returns_previous_value() {
|
||||
let mut obj = Object::new();
|
||||
assert_eq!(obj.insert(val(0), val(1)), None);
|
||||
assert_eq!(obj.insert(val(0), val(2)), Some(val(1)));
|
||||
assert_eq!(obj.get(&val(0)), Some(&val(2)));
|
||||
}
|
||||
@@ -544,16 +544,14 @@ fn make_context(case: &TestCase) -> Result<Value> {
|
||||
let map = ctx.as_object_mut()?;
|
||||
// Only inject if the caller didn't already provide requestContext
|
||||
// in the context object, to avoid clobbering custom test setups.
|
||||
map.entry(Value::from("requestContext")).or_insert(rc_val);
|
||||
map.get_or_insert_with(Value::from("requestContext"), || rc_val);
|
||||
} else if let Some(ref api_ver) = case.api_version {
|
||||
let map = ctx.as_object_mut()?;
|
||||
if let std::collections::btree_map::Entry::Vacant(e) =
|
||||
map.entry(Value::from("requestContext"))
|
||||
{
|
||||
if !map.contains_key(&Value::from("requestContext")) {
|
||||
let mut req_ctx = Value::new_object();
|
||||
let rc_map = req_ctx.as_object_mut()?;
|
||||
rc_map.insert(Value::from("apiVersion"), Value::from(api_ver.clone()));
|
||||
e.insert(req_ctx);
|
||||
map.insert(Value::from("requestContext"), req_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,3 +197,34 @@ fn get_policy_parameters() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_package_import_lookup_does_not_false_cycle() -> Result<()> {
|
||||
let mut engine = Engine::new();
|
||||
engine.add_policy(
|
||||
"registry.rego".to_string(),
|
||||
r#"package registry
|
||||
import data.registry.packages.package_a
|
||||
import rego.v1
|
||||
|
||||
allow_stage1 if package_a.allow_stage1
|
||||
"#
|
||||
.to_string(),
|
||||
)?;
|
||||
engine.add_policy(
|
||||
"package_a.rego".to_string(),
|
||||
r#"package registry.packages.package_a
|
||||
import data.registry
|
||||
import rego.v1
|
||||
|
||||
allow_stage2 if registry.allow_stage1
|
||||
allow_stage1 := true
|
||||
"#
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
let result = engine.eval_rule("data.registry.packages.package_a.allow_stage2".to_string())?;
|
||||
assert_eq!(result, Value::Bool(true));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -380,7 +380,11 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
|
||||
}
|
||||
let path = Path::new(&path_str);
|
||||
let path_dir = path.strip_prefix(tests_path)?.parent().unwrap();
|
||||
let path_dir_str = path_dir.to_string_lossy().to_string();
|
||||
// Normalize to forward slashes so the folder filter at the
|
||||
// `folders.iter().any(|f| &path_dir_str == f)` check below matches
|
||||
// CLI arguments like `v0/aggregates` on Windows, where
|
||||
// `to_string_lossy` yields backslash separators by default.
|
||||
let path_dir_str = path_dir.to_string_lossy().replace('\\', "/");
|
||||
let folder_name = folder_name_from_path(path_dir);
|
||||
let skip_rvm_for_folder = folder_name
|
||||
.as_deref()
|
||||
|
||||
@@ -95,3 +95,37 @@ cases:
|
||||
}
|
||||
query: data.rules.present
|
||||
want_result: true
|
||||
- note: import_cross_package_no_false_cycle
|
||||
modules:
|
||||
- |
|
||||
package registry
|
||||
import data.registry.packages.package_a
|
||||
import rego.v1
|
||||
|
||||
allow_stage1 if package_a.allow_stage1
|
||||
- |
|
||||
package registry.packages.package_a
|
||||
import data.registry
|
||||
import rego.v1
|
||||
|
||||
allow_stage2 if registry.allow_stage1
|
||||
allow_stage1 := true
|
||||
query: data.registry.packages.package_a.allow_stage2
|
||||
want_result: true
|
||||
- note: dynamic_data_index_evaluates_matching_rules
|
||||
modules:
|
||||
- |
|
||||
package registry
|
||||
import rego.v1
|
||||
|
||||
allow_stage1 if {
|
||||
name := "package_a"
|
||||
data.registry.packages[name].allow_stage2
|
||||
}
|
||||
- |
|
||||
package registry.packages.package_a
|
||||
import rego.v1
|
||||
|
||||
allow_stage2 := true
|
||||
query: data.registry.allow_stage1
|
||||
want_result: true
|
||||
|
||||
@@ -137,3 +137,45 @@ cases:
|
||||
- "Return { value: 7 }" # Return result set
|
||||
want_result:
|
||||
set!: [1, null, 2]
|
||||
|
||||
# Set iteration resumes from `Bound::Excluded(current_item)`, so the
|
||||
# ComprehensionAdd path must snapshot the current value into
|
||||
# `IterationState::Set.current_item` before advancing — otherwise
|
||||
# iteration stops after the first element. This case exercises
|
||||
# Set-source comprehension end-to-end to lock that requirement in.
|
||||
- note: set_source_visits_all_elements
|
||||
description: Comprehension over a Set source must yield every element, not just the first
|
||||
example_rego: |
|
||||
src := {1, 2, 3, 4}
|
||||
{x | x := src[_]} # {1, 2, 3, 4}
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
instruction_params:
|
||||
comprehension_start_params:
|
||||
- mode: "Set"
|
||||
collection_reg: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 7
|
||||
body_start: 11
|
||||
comprehension_end: 13
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }" # Build source set {1,2,3,4} in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "SetAdd { set: 0, value: 3 }"
|
||||
- "Load { dest: 6, literal_idx: 3 }"
|
||||
- "SetAdd { set: 0, value: 6 }"
|
||||
- "SetNew { dest: 7 }" # Initialize result set
|
||||
- "ComprehensionStart { params_index: 0 }" # Iterate over Set source
|
||||
- "ComprehensionAdd { value_reg: 5 }" # Add current value to result
|
||||
- "Halt"
|
||||
- "Return { value: 7 }"
|
||||
want_result:
|
||||
set!: [1, 2, 3, 4]
|
||||
|
||||
Reference in New Issue
Block a user