mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat(azure-policy): add alias normalization and denormalization (#635)
* feat: add Azure Policy alias normalization/denormalization Add normalizer and denormalizer for ARM JSON resources, enabling Azure Policy alias short names to become direct paths into a flat structure. - Normalizer: flattens properties wrappers, lowercases keys, resolves per-alias versioned ARM paths, handles sub-resource array flattening, element-level field remaps, and array base renames - Denormalizer: reverses all transformations with casing restoration - AliasRegistry: loads production alias catalogs and data policy manifests - Types: serde deserialization for ARM provider alias formats - YAML test suite: 13 test files covering normalize, denormalize, round-trip, data-plane, edge cases, malformed input, sub-resources, and registry API - Benchmark suite for normalization performance * feat: add FFI and C# bindings for alias normalization - FFI: alias_registry.rs with C-compatible API for loading catalogs, normalizing resources, and denormalizing back to ARM JSON - C#: AliasRegistry wrapper class with NativeMethods P/Invoke bindings and integration tests - Updated Cargo.lock files for new serde_json dependency
This commit is contained in:
committed by
GitHub
parent
35fb5d5953
commit
d36f952133
197
Cargo.lock
generated
197
Cargo.lock
generated
@@ -63,9 +63,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.21"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"anstyle-parse",
|
"anstyle-parse",
|
||||||
@@ -78,15 +78,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.13"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
version = "0.2.7"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"utf8parse",
|
"utf8parse",
|
||||||
]
|
]
|
||||||
@@ -140,9 +140,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "borrow-or-share"
|
name = "borrow-or-share"
|
||||||
@@ -162,9 +162,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecount"
|
name = "bytecount"
|
||||||
@@ -186,9 +186,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.55"
|
version = "1.2.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -263,9 +263,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.60"
|
version = "4.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -273,9 +273,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.60"
|
version = "4.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -285,14 +285,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.55"
|
version = "4.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -312,9 +312,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
@@ -433,8 +433,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -497,9 +497,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.8"
|
version = "1.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
|
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
@@ -835,15 +835,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.85"
|
version = "0.3.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -893,9 +893,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.180"
|
version = "0.2.183"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
@@ -926,9 +926,9 @@ checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
@@ -1040,9 +1040,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
@@ -1064,9 +1064,9 @@ checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "owo-colors"
|
name = "owo-colors"
|
||||||
version = "4.2.3"
|
version = "4.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
|
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "page_size"
|
name = "page_size"
|
||||||
@@ -1192,7 +1192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1224,9 +1224,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.44"
|
version = "1.0.45"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
]
|
]
|
||||||
@@ -1305,8 +1305,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1338,9 +1338,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -1349,9 +1349,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.8"
|
version = "0.8.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regorus"
|
name = "regorus"
|
||||||
@@ -1366,6 +1366,7 @@ dependencies = [
|
|||||||
"dashmap",
|
"dashmap",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"globset",
|
"globset",
|
||||||
|
"hashbrown 0.16.1",
|
||||||
"icu_casemap",
|
"icu_casemap",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
@@ -1416,9 +1417,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.22"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
@@ -1467,8 +1468,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1505,9 +1506,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.8"
|
version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
@@ -1558,12 +1559,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.114"
|
version = "2.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1574,8 +1575,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1606,8 +1607,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1663,9 +1664,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
@@ -1711,9 +1712,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.22.0"
|
version = "1.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -1771,9 +1772,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -1784,32 +1785,32 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -1850,9 +1851,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.85"
|
version = "0.3.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
|
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -1909,8 +1910,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1920,8 +1921,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1996,7 +1997,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
"wasm-metadata",
|
"wasm-metadata",
|
||||||
"wit-bindgen-core",
|
"wit-bindgen-core",
|
||||||
"wit-component",
|
"wit-component",
|
||||||
@@ -2011,8 +2012,8 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
"wit-bindgen-core",
|
"wit-bindgen-core",
|
||||||
"wit-bindgen-rust",
|
"wit-bindgen-rust",
|
||||||
]
|
]
|
||||||
@@ -2090,29 +2091,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2131,8 +2132,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2166,8 +2167,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.106",
|
"proc-macro2 1.0.106",
|
||||||
"quote 1.0.44",
|
"quote 1.0.45",
|
||||||
"syn 2.0.114",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2184,6 +2185,6 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.17"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ default = ["full-opa", "arc", "rvm"]
|
|||||||
|
|
||||||
arc = []
|
arc = []
|
||||||
ast = []
|
ast = []
|
||||||
azure_policy = ["dep:jsonschema", "dep:chrono", "dep:ipnet", "dep:icu_casemap", "arc", "dashmap"]
|
azure_policy = ["dep:jsonschema", "dep:chrono", "dep:ipnet", "dep:icu_casemap", "dep:hashbrown", "arc", "dashmap"]
|
||||||
azure-rbac = ["regex", "time", "net"]
|
azure-rbac = ["regex", "time", "net"]
|
||||||
base64 = ["dep:data-encoding"]
|
base64 = ["dep:data-encoding"]
|
||||||
base64url = ["dep:data-encoding"]
|
base64url = ["dep:data-encoding"]
|
||||||
@@ -99,6 +99,7 @@ rand = ["dep:rand"]
|
|||||||
anyhow = { version = "1.0.102", default-features = false }
|
anyhow = { version = "1.0.102", default-features = false }
|
||||||
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
|
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.89", default-features = false, features = ["alloc"] }
|
||||||
|
hashbrown = { version = "0.16", default-features = false, features = ["default-hasher"], optional = true }
|
||||||
lazy_static = { version = "1.4.0", default-features = false }
|
lazy_static = { version = "1.4.0", default-features = false }
|
||||||
thiserror = { version = "2.0", default-features = false }
|
thiserror = { version = "2.0", default-features = false }
|
||||||
|
|
||||||
@@ -197,6 +198,11 @@ name = "rvm_benchmark"
|
|||||||
harness = false
|
harness = false
|
||||||
required-features = ["rvm"]
|
required-features = ["rvm"]
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "normalization_benchmark"
|
||||||
|
harness = false
|
||||||
|
required-features = ["azure_policy"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name="regorus"
|
name="regorus"
|
||||||
harness=false
|
harness=false
|
||||||
|
|||||||
560
benches/normalization_benchmark.rs
Normal file
560
benches/normalization_benchmark.rs
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
use std::hint::black_box;
|
||||||
|
|
||||||
|
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||||
|
use regorus::languages::azure_policy::aliases::{denormalizer, normalizer, AliasRegistry};
|
||||||
|
use regorus::Value;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
// ─── Alias catalog (reused across benchmarks) ───────────────────────────────
|
||||||
|
|
||||||
|
const ALIASES_JSON: &str = r#"[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "networkSecurityGroups",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.access",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].priority",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.priority",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].direction",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.direction",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].sourceAddressPrefix",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.sourceAddressPrefix",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].destinationPortRange",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.destinationPortRange",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
|
||||||
|
"defaultPath": "properties.securityRules[*].name",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules[*].protocol",
|
||||||
|
"defaultPath": "properties.defaultSecurityRules[*].properties.protocol",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Storage",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "storageAccounts",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||||
|
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/accessTier",
|
||||||
|
"defaultPath": "properties.accessTier",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/isHnsEnabled",
|
||||||
|
"defaultPath": "properties.isHnsEnabled",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/minimumTlsVersion",
|
||||||
|
"defaultPath": "properties.minimumTlsVersion",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess",
|
||||||
|
"defaultPath": "properties.allowBlobPublicAccess",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/sku.name",
|
||||||
|
"defaultPath": "sku.name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]"#;
|
||||||
|
|
||||||
|
fn build_registry() -> AliasRegistry {
|
||||||
|
let mut reg = AliasRegistry::new();
|
||||||
|
reg.load_from_json(ALIASES_JSON).unwrap();
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a serde_json::Value to regorus::Value.
|
||||||
|
fn to_regorus(v: serde_json::Value) -> Value {
|
||||||
|
Value::from(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Input resources ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn simple_storage_resource() -> Value {
|
||||||
|
to_regorus(json!({
|
||||||
|
"name": "myStorageAccount",
|
||||||
|
"type": "Microsoft.Storage/storageAccounts",
|
||||||
|
"location": "westus2",
|
||||||
|
"kind": "StorageV2",
|
||||||
|
"sku": { "name": "Standard_LRS", "tier": "Standard" },
|
||||||
|
"tags": { "environment": "production", "team": "platform" },
|
||||||
|
"properties": {
|
||||||
|
"supportsHttpsTrafficOnly": true,
|
||||||
|
"accessTier": "Hot",
|
||||||
|
"isHnsEnabled": false,
|
||||||
|
"minimumTlsVersion": "TLS1_2",
|
||||||
|
"allowBlobPublicAccess": false
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nsg_resource(rule_count: usize) -> Value {
|
||||||
|
let rules: Vec<serde_json::Value> = (0..rule_count)
|
||||||
|
.map(|i| {
|
||||||
|
json!({
|
||||||
|
"name": format!("rule-{}", i),
|
||||||
|
"properties": {
|
||||||
|
"protocol": "Tcp",
|
||||||
|
"access": if i % 2 == 0 { "Allow" } else { "Deny" },
|
||||||
|
"priority": 100 + i,
|
||||||
|
"direction": "Inbound",
|
||||||
|
"sourceAddressPrefix": format!("10.0.{}.0/24", i % 256),
|
||||||
|
"destinationPortRange": format!("{}", 80 + i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
to_regorus(json!({
|
||||||
|
"name": "myNsg",
|
||||||
|
"type": "Microsoft.Network/networkSecurityGroups",
|
||||||
|
"location": "eastus",
|
||||||
|
"properties": {
|
||||||
|
"securityRules": rules
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Benchmarks ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn bench_normalize_simple(c: &mut Criterion) {
|
||||||
|
let registry = build_registry();
|
||||||
|
let resource = simple_storage_resource();
|
||||||
|
|
||||||
|
c.bench_function("normalize/simple_storage", |b| {
|
||||||
|
b.iter(|| normalizer::normalize(black_box(&resource), Some(®istry), None))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_normalize_no_aliases(c: &mut Criterion) {
|
||||||
|
let resource = simple_storage_resource();
|
||||||
|
|
||||||
|
c.bench_function("normalize/simple_no_aliases", |b| {
|
||||||
|
b.iter(|| normalizer::normalize(black_box(&resource), None, None))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_normalize_nsg_scaling(c: &mut Criterion) {
|
||||||
|
let registry = build_registry();
|
||||||
|
let mut group = c.benchmark_group("normalize/nsg_rules");
|
||||||
|
|
||||||
|
for rule_count in [5, 20, 100] {
|
||||||
|
let resource = nsg_resource(rule_count);
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::from_parameter(rule_count),
|
||||||
|
&resource,
|
||||||
|
|b, res| b.iter(|| normalizer::normalize(black_box(res), Some(®istry), None)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_denormalize_simple(c: &mut Criterion) {
|
||||||
|
let registry = build_registry();
|
||||||
|
let resource = simple_storage_resource();
|
||||||
|
let normalized = normalizer::normalize(&resource, Some(®istry), None);
|
||||||
|
|
||||||
|
c.bench_function("denormalize/simple_storage", |b| {
|
||||||
|
b.iter(|| denormalizer::denormalize(black_box(&normalized), Some(®istry), None))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_denormalize_nsg_scaling(c: &mut Criterion) {
|
||||||
|
let registry = build_registry();
|
||||||
|
let mut group = c.benchmark_group("denormalize/nsg_rules");
|
||||||
|
|
||||||
|
for rule_count in [5, 20, 100] {
|
||||||
|
let resource = nsg_resource(rule_count);
|
||||||
|
let normalized = normalizer::normalize(&resource, Some(®istry), None);
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::from_parameter(rule_count),
|
||||||
|
&normalized,
|
||||||
|
|b, norm| b.iter(|| denormalizer::denormalize(black_box(norm), Some(®istry), None)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_round_trip(c: &mut Criterion) {
|
||||||
|
let registry = build_registry();
|
||||||
|
let resource = nsg_resource(20);
|
||||||
|
|
||||||
|
c.bench_function("round_trip/nsg_20_rules", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let n = normalizer::normalize(black_box(&resource), Some(®istry), None);
|
||||||
|
denormalizer::denormalize(&n, Some(®istry), None)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_normalize_and_wrap(c: &mut Criterion) {
|
||||||
|
let registry = build_registry();
|
||||||
|
let resource = nsg_resource(20);
|
||||||
|
let context = to_regorus(json!({"resourceGroup": {"name": "rg1"}}));
|
||||||
|
let parameters = to_regorus(json!({"env": "prod"}));
|
||||||
|
|
||||||
|
c.bench_function("normalize_and_wrap/nsg_20_rules", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
registry.normalize_and_wrap(
|
||||||
|
black_box(&resource),
|
||||||
|
None,
|
||||||
|
Some(context.clone()),
|
||||||
|
Some(parameters.clone()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_registry_load(c: &mut Criterion) {
|
||||||
|
c.bench_function("registry/load_from_json", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut reg = AliasRegistry::new();
|
||||||
|
reg.load_from_json(black_box(ALIASES_JSON)).unwrap();
|
||||||
|
reg
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Large-payload benchmarks ───────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// These stress the hot paths identified in the performance analysis:
|
||||||
|
// - Nested set helpers (alias-heavy catalog with deep properties)
|
||||||
|
// - Array element remap/cleanup/rewrap (large sub-resource arrays)
|
||||||
|
// - Scalar denormalization lookups (many aliases × many fields)
|
||||||
|
|
||||||
|
/// Build a large alias catalog with `n` scalar aliases for storage accounts.
|
||||||
|
/// Each alias maps to a nested `properties.section_i.field_j` path, creating
|
||||||
|
/// deep nested-set workloads.
|
||||||
|
fn large_alias_catalog(n: usize) -> String {
|
||||||
|
let mut aliases = Vec::new();
|
||||||
|
for i in 0..n {
|
||||||
|
let section = i / 10;
|
||||||
|
let field = i % 10;
|
||||||
|
aliases.push(format!(
|
||||||
|
r#"{{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/section{section}Field{field}",
|
||||||
|
"defaultPath": "properties.section{section}.field{field}",
|
||||||
|
"paths": []
|
||||||
|
}}"#,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
format!(
|
||||||
|
r#"[{{
|
||||||
|
"namespace": "Microsoft.Storage",
|
||||||
|
"resourceTypes": [{{
|
||||||
|
"resourceType": "storageAccounts",
|
||||||
|
"aliases": [{aliases}]
|
||||||
|
}}]
|
||||||
|
}}]"#,
|
||||||
|
aliases = aliases.join(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a storage account resource whose `properties` contain nested sections
|
||||||
|
/// matching the large alias catalog.
|
||||||
|
fn large_storage_resource(alias_count: usize) -> Value {
|
||||||
|
let mut sections = serde_json::Map::new();
|
||||||
|
for i in 0..alias_count {
|
||||||
|
let section = i / 10;
|
||||||
|
let field = i % 10;
|
||||||
|
let section_key = format!("section{section}");
|
||||||
|
let section_obj = sections
|
||||||
|
.entry(section_key)
|
||||||
|
.or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
|
||||||
|
if let serde_json::Value::Object(m) = section_obj {
|
||||||
|
m.insert(format!("field{field}"), serde_json::Value::from(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::from(json!({
|
||||||
|
"name": "bigStorage",
|
||||||
|
"type": "Microsoft.Storage/storageAccounts",
|
||||||
|
"location": "westus2",
|
||||||
|
"properties": sections
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_normalize_large_catalog(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("normalize/large_catalog");
|
||||||
|
for alias_count in [50, 200] {
|
||||||
|
let catalog_json = large_alias_catalog(alias_count);
|
||||||
|
let mut reg = AliasRegistry::new();
|
||||||
|
reg.load_from_json(&catalog_json).unwrap();
|
||||||
|
let resource = large_storage_resource(alias_count);
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::from_parameter(alias_count),
|
||||||
|
&(reg, resource),
|
||||||
|
|b, (reg, res)| b.iter(|| normalizer::normalize(black_box(res), Some(reg), None)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_denormalize_large_catalog(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("denormalize/large_catalog");
|
||||||
|
for alias_count in [50, 200] {
|
||||||
|
let catalog_json = large_alias_catalog(alias_count);
|
||||||
|
let mut reg = AliasRegistry::new();
|
||||||
|
reg.load_from_json(&catalog_json).unwrap();
|
||||||
|
let resource = large_storage_resource(alias_count);
|
||||||
|
let normalized = normalizer::normalize(&resource, Some(®), None);
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::from_parameter(alias_count),
|
||||||
|
&(reg, normalized),
|
||||||
|
|b, (reg, norm)| b.iter(|| denormalizer::denormalize(black_box(norm), Some(reg), None)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_nsg_large_subarrays(c: &mut Criterion) {
|
||||||
|
let registry = build_registry();
|
||||||
|
let mut group = c.benchmark_group("round_trip/nsg_sub_resource");
|
||||||
|
for rule_count in [50, 200, 500] {
|
||||||
|
let resource = nsg_resource(rule_count);
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::from_parameter(rule_count),
|
||||||
|
&resource,
|
||||||
|
|b, res| {
|
||||||
|
b.iter(|| {
|
||||||
|
let n = normalizer::normalize(black_box(res), Some(®istry), None);
|
||||||
|
denormalizer::denormalize(&n, Some(®istry), None)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Versioned-path benchmarks ──────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Exercise the precomputed versioned-path aggregates by building a catalog
|
||||||
|
// where wildcard (array) aliases have version-specific paths that differ from
|
||||||
|
// the default, then running normalize/denormalize with an explicit api_version.
|
||||||
|
|
||||||
|
/// NSG-like alias catalog where wildcard aliases have versioned paths that
|
||||||
|
/// differ from the default. This forces the normalize/denormalize path through
|
||||||
|
/// the versioned aggregate lookup rather than the default-aggregate fast path.
|
||||||
|
const VERSIONED_ALIASES_JSON: &str = r#"[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "networkSecurityGroups",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||||
|
"paths": [
|
||||||
|
{ "path": "properties.securityRules[*].properties.transportProtocol", "apiVersions": ["2020-01-01"] },
|
||||||
|
{ "path": "properties.securityRules[*].properties.protocol", "apiVersions": ["2022-01-01"] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.access",
|
||||||
|
"paths": [
|
||||||
|
{ "path": "properties.securityRules[*].properties.accessLevel", "apiVersions": ["2020-01-01"] },
|
||||||
|
{ "path": "properties.securityRules[*].properties.access", "apiVersions": ["2022-01-01"] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].priority",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.priority",
|
||||||
|
"paths": [
|
||||||
|
{ "path": "properties.securityRules[*].properties.rulePriority", "apiVersions": ["2020-01-01"] },
|
||||||
|
{ "path": "properties.securityRules[*].properties.priority", "apiVersions": ["2022-01-01"] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].direction",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.direction",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].sourceAddressPrefix",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.sourceAddressPrefix",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].destinationPortRange",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.destinationPortRange",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
|
||||||
|
"defaultPath": "properties.securityRules[*].name",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/provisioningState",
|
||||||
|
"defaultPath": "properties.provisioningState",
|
||||||
|
"paths": [
|
||||||
|
{ "path": "properties.state", "apiVersions": ["2020-01-01"] },
|
||||||
|
{ "path": "properties.provisioningState", "apiVersions": ["2022-01-01"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]"#;
|
||||||
|
|
||||||
|
fn build_versioned_registry() -> AliasRegistry {
|
||||||
|
let mut reg = AliasRegistry::new();
|
||||||
|
reg.load_from_json(VERSIONED_ALIASES_JSON).unwrap();
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build an NSG resource for versioned-path benchmarks.
|
||||||
|
/// Uses the 2020-01-01 field names (`transportProtocol`, `accessLevel`,
|
||||||
|
/// `rulePriority`) so that versioned path resolution actually differs from
|
||||||
|
/// the default.
|
||||||
|
fn nsg_versioned_resource(rule_count: usize) -> Value {
|
||||||
|
let rules: Vec<serde_json::Value> = (0..rule_count)
|
||||||
|
.map(|i| {
|
||||||
|
json!({
|
||||||
|
"name": format!("rule-{}", i),
|
||||||
|
"properties": {
|
||||||
|
"transportProtocol": "Tcp",
|
||||||
|
"accessLevel": if i % 2 == 0 { "Allow" } else { "Deny" },
|
||||||
|
"rulePriority": 100 + i,
|
||||||
|
"direction": "Inbound",
|
||||||
|
"sourceAddressPrefix": format!("10.0.{}.0/24", i % 256),
|
||||||
|
"destinationPortRange": format!("{}", 80 + i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
to_regorus(json!({
|
||||||
|
"name": "myNsg",
|
||||||
|
"type": "Microsoft.Network/networkSecurityGroups",
|
||||||
|
"location": "eastus",
|
||||||
|
"properties": {
|
||||||
|
"state": "Succeeded",
|
||||||
|
"securityRules": rules
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_normalize_versioned(c: &mut Criterion) {
|
||||||
|
let registry = build_versioned_registry();
|
||||||
|
let mut group = c.benchmark_group("normalize_versioned/nsg_rules");
|
||||||
|
|
||||||
|
for rule_count in [5, 20, 100] {
|
||||||
|
let resource = nsg_versioned_resource(rule_count);
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::from_parameter(rule_count),
|
||||||
|
&resource,
|
||||||
|
|b, res| {
|
||||||
|
b.iter(|| {
|
||||||
|
normalizer::normalize(black_box(res), Some(®istry), Some("2020-01-01"))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_denormalize_versioned(c: &mut Criterion) {
|
||||||
|
let registry = build_versioned_registry();
|
||||||
|
let mut group = c.benchmark_group("denormalize_versioned/nsg_rules");
|
||||||
|
|
||||||
|
for rule_count in [5, 20, 100] {
|
||||||
|
let resource = nsg_versioned_resource(rule_count);
|
||||||
|
let normalized = normalizer::normalize(&resource, Some(®istry), Some("2020-01-01"));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::from_parameter(rule_count),
|
||||||
|
&normalized,
|
||||||
|
|b, norm| {
|
||||||
|
b.iter(|| {
|
||||||
|
denormalizer::denormalize(black_box(norm), Some(®istry), Some("2020-01-01"))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_round_trip_versioned(c: &mut Criterion) {
|
||||||
|
let registry = build_versioned_registry();
|
||||||
|
let mut group = c.benchmark_group("round_trip_versioned/nsg_rules");
|
||||||
|
|
||||||
|
for rule_count in [20, 100] {
|
||||||
|
let resource = nsg_versioned_resource(rule_count);
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::from_parameter(rule_count),
|
||||||
|
&resource,
|
||||||
|
|b, res| {
|
||||||
|
b.iter(|| {
|
||||||
|
let n =
|
||||||
|
normalizer::normalize(black_box(res), Some(®istry), Some("2020-01-01"));
|
||||||
|
denormalizer::denormalize(&n, Some(®istry), Some("2020-01-01"))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
normalization_benches,
|
||||||
|
bench_normalize_simple,
|
||||||
|
bench_normalize_no_aliases,
|
||||||
|
bench_normalize_nsg_scaling,
|
||||||
|
bench_denormalize_simple,
|
||||||
|
bench_denormalize_nsg_scaling,
|
||||||
|
bench_round_trip,
|
||||||
|
bench_normalize_and_wrap,
|
||||||
|
bench_registry_load,
|
||||||
|
bench_normalize_large_catalog,
|
||||||
|
bench_denormalize_large_catalog,
|
||||||
|
bench_nsg_large_subarrays,
|
||||||
|
bench_normalize_versioned,
|
||||||
|
bench_denormalize_versioned,
|
||||||
|
bench_round_trip_versioned,
|
||||||
|
);
|
||||||
|
criterion_main!(normalization_benches);
|
||||||
191
bindings/csharp/Regorus.Tests/AliasRegistryTests.cs
Normal file
191
bindings/csharp/Regorus.Tests/AliasRegistryTests.cs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Regorus;
|
||||||
|
|
||||||
|
namespace Regorus.Tests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class AliasRegistryTests
|
||||||
|
{
|
||||||
|
private const string AliasesJson = @"[{
|
||||||
|
""namespace"": ""Microsoft.Storage"",
|
||||||
|
""resourceTypes"": [{
|
||||||
|
""resourceType"": ""storageAccounts"",
|
||||||
|
""aliases"": [{
|
||||||
|
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
|
||||||
|
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
|
||||||
|
""paths"": []
|
||||||
|
}, {
|
||||||
|
""name"": ""Microsoft.Storage/storageAccounts/accessTier"",
|
||||||
|
""defaultPath"": ""properties.accessTier"",
|
||||||
|
""paths"": []
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]";
|
||||||
|
|
||||||
|
private const string ManifestJson = @"{
|
||||||
|
""dataNamespace"": ""Microsoft.KeyVault.Data"",
|
||||||
|
""aliases"": [],
|
||||||
|
""resourceTypeAliases"": [{
|
||||||
|
""resourceType"": ""vaults/certificates"",
|
||||||
|
""aliases"": [{
|
||||||
|
""name"": ""Microsoft.KeyVault.Data/vaults/certificates/keySize"",
|
||||||
|
""paths"": [{ ""path"": ""keySize"", ""apiVersions"": [""7.0""] }]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}";
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Create_and_dispose_succeeds()
|
||||||
|
{
|
||||||
|
using var registry = new AliasRegistry();
|
||||||
|
Assert.AreEqual(0, registry.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LoadJson_populates_registry()
|
||||||
|
{
|
||||||
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
|
Assert.AreEqual(1, registry.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LoadManifest_populates_registry()
|
||||||
|
{
|
||||||
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadManifest(ManifestJson);
|
||||||
|
Assert.AreEqual(1, registry.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NormalizeAndWrap_produces_envelope()
|
||||||
|
{
|
||||||
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
|
|
||||||
|
var resource = @"{
|
||||||
|
""name"": ""acct1"",
|
||||||
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||||
|
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
|
||||||
|
}";
|
||||||
|
|
||||||
|
var result = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
|
||||||
|
var envelope = JsonNode.Parse(result!)!;
|
||||||
|
Assert.IsNotNull(envelope["resource"]);
|
||||||
|
Assert.IsNotNull(envelope["parameters"]);
|
||||||
|
Assert.IsNotNull(envelope["context"]);
|
||||||
|
|
||||||
|
// Normalized resource should have lowercased alias field names
|
||||||
|
var res = envelope["resource"]!;
|
||||||
|
Assert.AreEqual(true, res["supportshttpstrafficonly"]?.GetValue<bool>());
|
||||||
|
Assert.AreEqual("Hot", res["accesstier"]?.GetValue<string>());
|
||||||
|
Assert.AreEqual("acct1", res["name"]?.GetValue<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NormalizeAndWrap_with_context_and_parameters()
|
||||||
|
{
|
||||||
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
|
|
||||||
|
var resource = @"{
|
||||||
|
""name"": ""acct1"",
|
||||||
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||||
|
""properties"": { ""supportsHttpsTrafficOnly"": true }
|
||||||
|
}";
|
||||||
|
var context = @"{""resourceGroup"": {""name"": ""rg1""}}";
|
||||||
|
var parameters = @"{""env"": ""prod""}";
|
||||||
|
|
||||||
|
var result = registry.NormalizeAndWrap(resource, "2023-01-01", context, parameters);
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
|
||||||
|
var envelope = JsonNode.Parse(result!)!;
|
||||||
|
Assert.AreEqual("rg1", envelope["context"]!["resourceGroup"]!["name"]?.GetValue<string>());
|
||||||
|
Assert.AreEqual("prod", envelope["parameters"]!["env"]?.GetValue<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Denormalize_restores_properties()
|
||||||
|
{
|
||||||
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
|
|
||||||
|
var normalized = @"{
|
||||||
|
""name"": ""acct1"",
|
||||||
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||||
|
""supportshttpstrafficonly"": true,
|
||||||
|
""accesstier"": ""Hot""
|
||||||
|
}";
|
||||||
|
|
||||||
|
var result = registry.Denormalize(normalized, "2023-01-01");
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
|
||||||
|
var arm = JsonNode.Parse(result!)!;
|
||||||
|
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
|
||||||
|
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
|
||||||
|
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Round_trip_normalize_then_denormalize()
|
||||||
|
{
|
||||||
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
|
|
||||||
|
var resource = @"{
|
||||||
|
""name"": ""acct1"",
|
||||||
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||||
|
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
var envelopeJson = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
|
||||||
|
Assert.IsNotNull(envelopeJson);
|
||||||
|
|
||||||
|
var envelope = JsonNode.Parse(envelopeJson!)!;
|
||||||
|
var normalizedResource = envelope["resource"]!.ToJsonString();
|
||||||
|
|
||||||
|
// Denormalize
|
||||||
|
var armJson = registry.Denormalize(normalizedResource, "2023-01-01");
|
||||||
|
Assert.IsNotNull(armJson);
|
||||||
|
|
||||||
|
var arm = JsonNode.Parse(armJson!)!;
|
||||||
|
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
|
||||||
|
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
|
||||||
|
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void DataPlane_manifest_normalize()
|
||||||
|
{
|
||||||
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadManifest(ManifestJson);
|
||||||
|
|
||||||
|
var resource = @"{
|
||||||
|
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
|
||||||
|
""keySize"": 2048
|
||||||
|
}";
|
||||||
|
|
||||||
|
var result = registry.NormalizeAndWrap(resource, "7.0", "{}", "{}");
|
||||||
|
Assert.IsNotNull(result);
|
||||||
|
|
||||||
|
var envelope = JsonNode.Parse(result!)!;
|
||||||
|
Assert.AreEqual(2048, envelope["resource"]!["keysize"]?.GetValue<int>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(InvalidOperationException))]
|
||||||
|
public void LoadJson_invalid_throws()
|
||||||
|
{
|
||||||
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson("not valid json");
|
||||||
|
}
|
||||||
|
}
|
||||||
153
bindings/csharp/Regorus/AliasRegistry.cs
Normal file
153
bindings/csharp/Regorus/AliasRegistry.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Regorus.Internal;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
namespace Regorus
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages Azure Policy alias definitions used for resource normalization
|
||||||
|
/// and policy compilation.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe sealed class AliasRegistry : SafeHandleWrapper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create an empty alias registry.
|
||||||
|
/// </summary>
|
||||||
|
public AliasRegistry()
|
||||||
|
: base(RegorusAliasRegistryHandle.Create(), nameof(AliasRegistry))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="json">JSON array of ProviderAliases (e.g. from Get-AzPolicyAlias or ResourceTypesAndAliases.json)</param>
|
||||||
|
public void LoadJson(string json)
|
||||||
|
{
|
||||||
|
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||||
|
{
|
||||||
|
UseHandle(regPtr =>
|
||||||
|
{
|
||||||
|
CheckAndDropResult(API.regorus_alias_registry_load_json(
|
||||||
|
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load a data-plane policy manifest from a JSON string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="json">JSON object containing a DataPolicyManifest</param>
|
||||||
|
public void LoadManifest(string json)
|
||||||
|
{
|
||||||
|
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||||
|
{
|
||||||
|
UseHandle(regPtr =>
|
||||||
|
{
|
||||||
|
CheckAndDropResult(API.regorus_alias_registry_load_manifest(
|
||||||
|
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of resource types loaded in the registry.
|
||||||
|
/// </summary>
|
||||||
|
public long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return UseHandle(regPtr =>
|
||||||
|
{
|
||||||
|
return ResultHelpers.GetIntResult(
|
||||||
|
API.regorus_alias_registry_len((RegorusAliasRegistry*)regPtr));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalize an ARM resource JSON and wrap it into the standard input envelope
|
||||||
|
/// expected by a compiled Azure Policy program.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resourceJson">Raw ARM resource JSON</param>
|
||||||
|
/// <param name="apiVersion">API version string (e.g. "2023-01-01"), or null to use default alias paths</param>
|
||||||
|
/// <param name="contextJson">Additional context JSON object (pass "{}" if none)</param>
|
||||||
|
/// <param name="parametersJson">Policy parameter values JSON (pass "{}" if none)</param>
|
||||||
|
/// <returns>JSON string: { "resource": <normalized>, "context": <context>, "parameters": <params> }</returns>
|
||||||
|
public string? NormalizeAndWrap(string resourceJson, string? apiVersion = null, string contextJson = "{}", string parametersJson = "{}")
|
||||||
|
{
|
||||||
|
return Utf8Marshaller.WithUtf8(resourceJson, resPtr =>
|
||||||
|
Utf8Marshaller.WithUtf8(contextJson, ctxPtr =>
|
||||||
|
Utf8Marshaller.WithUtf8(parametersJson, paramsPtr =>
|
||||||
|
{
|
||||||
|
if (apiVersion is null)
|
||||||
|
{
|
||||||
|
return UseHandle(regPtr =>
|
||||||
|
{
|
||||||
|
return ResultHelpers.GetStringResult(
|
||||||
|
API.regorus_alias_registry_normalize_and_wrap(
|
||||||
|
(RegorusAliasRegistry*)regPtr,
|
||||||
|
(byte*)resPtr, null,
|
||||||
|
(byte*)ctxPtr, (byte*)paramsPtr));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
|
||||||
|
UseHandle(regPtr =>
|
||||||
|
{
|
||||||
|
return ResultHelpers.GetStringResult(
|
||||||
|
API.regorus_alias_registry_normalize_and_wrap(
|
||||||
|
(RegorusAliasRegistry*)regPtr,
|
||||||
|
(byte*)resPtr, (byte*)apiPtr,
|
||||||
|
(byte*)ctxPtr, (byte*)paramsPtr));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="normalizedJson">The normalized resource JSON</param>
|
||||||
|
/// <param name="apiVersion">API version string, or null to use default alias paths</param>
|
||||||
|
/// <returns>Denormalized ARM JSON string</returns>
|
||||||
|
public string? Denormalize(string normalizedJson, string? apiVersion = null)
|
||||||
|
{
|
||||||
|
return Utf8Marshaller.WithUtf8(normalizedJson, normPtr =>
|
||||||
|
{
|
||||||
|
if (apiVersion is null)
|
||||||
|
{
|
||||||
|
return UseHandle(regPtr =>
|
||||||
|
{
|
||||||
|
return ResultHelpers.GetStringResult(
|
||||||
|
API.regorus_alias_registry_denormalize(
|
||||||
|
(RegorusAliasRegistry*)regPtr,
|
||||||
|
(byte*)normPtr, null));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
|
||||||
|
UseHandle(regPtr =>
|
||||||
|
{
|
||||||
|
return ResultHelpers.GetStringResult(
|
||||||
|
API.regorus_alias_registry_denormalize(
|
||||||
|
(RegorusAliasRegistry*)regPtr,
|
||||||
|
(byte*)normPtr, (byte*)apiPtr));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? CheckAndDropResult(RegorusResult result)
|
||||||
|
{
|
||||||
|
return ResultHelpers.GetStringResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -669,6 +669,55 @@ namespace Regorus.Internal
|
|||||||
internal static extern RegorusResult regorus_effect_schema_clear();
|
internal static extern RegorusResult regorus_effect_schema_clear();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Alias Registry Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new, empty AliasRegistry.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern RegorusAliasRegistry* regorus_alias_registry_new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drop an AliasRegistry.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern void regorus_alias_registry_drop(RegorusAliasRegistry* registry);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load control-plane alias data (array of ProviderAliases) into the registry.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern RegorusResult regorus_alias_registry_load_json(RegorusAliasRegistry* registry, byte* json);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load a data-plane policy manifest into the registry.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern RegorusResult regorus_alias_registry_load_manifest(RegorusAliasRegistry* registry, byte* json);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the number of resource types loaded in the alias registry.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern RegorusResult regorus_alias_registry_len(RegorusAliasRegistry* registry);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalize an ARM resource JSON and wrap it into the standard input envelope.
|
||||||
|
/// Returns a JSON string.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_normalize_and_wrap", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern RegorusResult regorus_alias_registry_normalize_and_wrap(
|
||||||
|
RegorusAliasRegistry* registry, byte* resource_json, byte* api_version, byte* context_json, byte* parameters_json);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_denormalize", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern RegorusResult regorus_alias_registry_denormalize(
|
||||||
|
RegorusAliasRegistry* registry, byte* normalized_json, byte* api_version);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Native Structures
|
#region Native Structures
|
||||||
@@ -874,5 +923,13 @@ namespace Regorus.Internal
|
|||||||
public byte* content;
|
public byte* content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper for AliasRegistry.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal unsafe partial struct RegorusAliasRegistry
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,4 +183,52 @@ namespace Regorus
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class RegorusAliasRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||||
|
{
|
||||||
|
private RegorusAliasRegistryHandle() : base(ownsHandle: true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static RegorusAliasRegistryHandle Create()
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
var raw = Internal.API.regorus_alias_registry_new();
|
||||||
|
if (raw is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to create Regorus alias registry.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle = new RegorusAliasRegistryHandle();
|
||||||
|
handle.SetHandle((IntPtr)raw);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static RegorusAliasRegistryHandle FromPointer(IntPtr pointer)
|
||||||
|
{
|
||||||
|
if (pointer == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle = new RegorusAliasRegistryHandle();
|
||||||
|
handle.SetHandle(pointer);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool ReleaseHandle()
|
||||||
|
{
|
||||||
|
if (!IsInvalid)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
Internal.API.regorus_alias_registry_drop((Internal.RegorusAliasRegistry*)handle);
|
||||||
|
}
|
||||||
|
SetHandle(IntPtr.Zero);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
141
bindings/ffi/Cargo.lock
generated
141
bindings/ffi/Cargo.lock
generated
@@ -57,9 +57,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.13"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
@@ -119,9 +119,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "borrow-or-share"
|
name = "borrow-or-share"
|
||||||
@@ -141,9 +141,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecount"
|
name = "bytecount"
|
||||||
@@ -172,9 +172,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.55"
|
version = "1.2.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -258,9 +258,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
@@ -672,15 +672,15 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.85"
|
version = "0.3.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -727,15 +727,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.180"
|
version = "0.2.183"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.11.0"
|
version = "0.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
@@ -766,9 +766,9 @@ checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "msvc_spectre_libs"
|
name = "msvc_spectre_libs"
|
||||||
@@ -860,9 +860,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
@@ -967,9 +967,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.44"
|
version = "1.0.45"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -1061,9 +1061,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -1072,9 +1072,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.8"
|
version = "0.8.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regorus"
|
name = "regorus"
|
||||||
@@ -1086,6 +1086,7 @@ dependencies = [
|
|||||||
"dashmap",
|
"dashmap",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"globset",
|
"globset",
|
||||||
|
"hashbrown 0.16.1",
|
||||||
"icu_casemap",
|
"icu_casemap",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
@@ -1137,9 +1138,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
@@ -1156,9 +1157,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.22"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
@@ -1217,9 +1218,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "1.0.4"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
|
checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
@@ -1275,9 +1276,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.114"
|
version = "2.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1297,12 +1298,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.24.0"
|
version = "3.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.4.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
@@ -1341,9 +1342,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.11+spec-1.1.0"
|
version = "0.9.12+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
|
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -1351,7 +1352,7 @@ dependencies = [
|
|||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"toml_parser",
|
"toml_parser",
|
||||||
"toml_writer",
|
"toml_writer",
|
||||||
"winnow",
|
"winnow 0.7.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1365,18 +1366,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_parser"
|
name = "toml_parser"
|
||||||
version = "1.0.6+spec-1.1.0"
|
version = "1.1.0+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winnow",
|
"winnow 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_writer"
|
name = "toml_writer"
|
||||||
version = "1.0.6+spec-1.1.0"
|
version = "1.1.0+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-general-category"
|
name = "unicode-general-category"
|
||||||
@@ -1386,9 +1387,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
@@ -1428,9 +1429,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.22.0"
|
version = "1.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -1478,9 +1479,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -1491,9 +1492,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -1501,9 +1502,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -1514,9 +1515,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -1625,9 +1626,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.14"
|
version = "0.7.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
@@ -1748,18 +1755,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1823,6 +1830,6 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.17"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|||||||
479
bindings/ffi/src/alias_registry.rs
Normal file
479
bindings/ffi/src/alias_registry.rs
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! FFI bindings for `AliasRegistry` – Azure Policy alias catalog management.
|
||||||
|
|
||||||
|
#![cfg(feature = "azure_policy")]
|
||||||
|
|
||||||
|
use crate::common::{from_c_str, to_ref, RegorusResult, RegorusStatus};
|
||||||
|
use crate::panic_guard::with_unwind_guard;
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use alloc::format;
|
||||||
|
use alloc::string::String;
|
||||||
|
use anyhow::Result;
|
||||||
|
use core::ffi::c_char;
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
use regorus::languages::azure_policy::aliases::AliasRegistry;
|
||||||
|
|
||||||
|
/// Opaque wrapper for `AliasRegistry`.
|
||||||
|
pub struct RegorusAliasRegistry {
|
||||||
|
registry: AliasRegistry,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Lifecycle
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Create a new, empty `AliasRegistry`.
|
||||||
|
///
|
||||||
|
/// The caller must eventually call `regorus_alias_registry_drop` to free the handle.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn regorus_alias_registry_new() -> *mut RegorusAliasRegistry {
|
||||||
|
let wrapper = RegorusAliasRegistry {
|
||||||
|
registry: AliasRegistry::new(),
|
||||||
|
};
|
||||||
|
Box::into_raw(Box::new(wrapper))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drop a `RegorusAliasRegistry`.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn regorus_alias_registry_drop(registry: *mut RegorusAliasRegistry) {
|
||||||
|
if let Ok(r) = to_ref(registry) {
|
||||||
|
unsafe {
|
||||||
|
let _ = Box::from_raw(ptr::from_mut(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Loading
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Load control-plane alias data (array of `ProviderAliases`) into the registry.
|
||||||
|
///
|
||||||
|
/// `json` must be a valid null-terminated UTF-8 string containing the JSON
|
||||||
|
/// array returned by `Get-AzPolicyAlias` or the static
|
||||||
|
/// `ResourceTypesAndAliases.json` file.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn regorus_alias_registry_load_json(
|
||||||
|
registry: *mut RegorusAliasRegistry,
|
||||||
|
json: *const c_char,
|
||||||
|
) -> RegorusResult {
|
||||||
|
with_unwind_guard(|| {
|
||||||
|
let output = || -> Result<()> {
|
||||||
|
let json_str = from_c_str(json)?;
|
||||||
|
to_ref(registry)?.registry.load_from_json(&json_str)?;
|
||||||
|
Ok(())
|
||||||
|
}();
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(()) => RegorusResult::ok_void(),
|
||||||
|
Err(e) => RegorusResult::err_with_message(
|
||||||
|
RegorusStatus::InvalidDataFormat,
|
||||||
|
format!("Failed to load alias catalog: {e}"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a data-plane policy manifest into the registry.
|
||||||
|
///
|
||||||
|
/// `json` must be a valid null-terminated UTF-8 string containing a single
|
||||||
|
/// `DataPolicyManifest` JSON object.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn regorus_alias_registry_load_manifest(
|
||||||
|
registry: *mut RegorusAliasRegistry,
|
||||||
|
json: *const c_char,
|
||||||
|
) -> RegorusResult {
|
||||||
|
with_unwind_guard(|| {
|
||||||
|
let output = || -> Result<()> {
|
||||||
|
let json_str = from_c_str(json)?;
|
||||||
|
to_ref(registry)?
|
||||||
|
.registry
|
||||||
|
.load_data_policy_manifest_json(&json_str)?;
|
||||||
|
Ok(())
|
||||||
|
}();
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(()) => RegorusResult::ok_void(),
|
||||||
|
Err(e) => RegorusResult::err_with_message(
|
||||||
|
RegorusStatus::InvalidDataFormat,
|
||||||
|
format!("Failed to load data-plane manifest: {e}"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Queries
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Return the number of resource types loaded in the alias registry.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn regorus_alias_registry_len(registry: *mut RegorusAliasRegistry) -> RegorusResult {
|
||||||
|
with_unwind_guard(|| {
|
||||||
|
let output = || -> Result<i64> {
|
||||||
|
let len = to_ref(registry)?.registry.len();
|
||||||
|
Ok(len as i64)
|
||||||
|
}();
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(n) => RegorusResult::ok_int(n),
|
||||||
|
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Normalize / Denormalize
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Normalize an ARM resource JSON and wrap it into the standard input envelope.
|
||||||
|
///
|
||||||
|
/// Returns a JSON string:
|
||||||
|
/// `{ "resource": <normalized>, "context": <context>, "parameters": <params> }`.
|
||||||
|
///
|
||||||
|
/// * `resource_json` – raw ARM resource JSON
|
||||||
|
/// * `api_version` – API version string (e.g. `"2023-01-01"`), or null to use
|
||||||
|
/// the default alias paths
|
||||||
|
/// * `context_json` – JSON object for additional context (pass `"{}"` if none)
|
||||||
|
/// * `parameters_json` – JSON object of policy parameter values (pass `"{}"` if none)
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
|
||||||
|
registry: *mut RegorusAliasRegistry,
|
||||||
|
resource_json: *const c_char,
|
||||||
|
api_version: *const c_char,
|
||||||
|
context_json: *const c_char,
|
||||||
|
parameters_json: *const c_char,
|
||||||
|
) -> RegorusResult {
|
||||||
|
with_unwind_guard(|| {
|
||||||
|
let output = || -> Result<String> {
|
||||||
|
let resource_str = from_c_str(resource_json)?;
|
||||||
|
let api_ver = if api_version.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let s = from_c_str(api_version)?;
|
||||||
|
if s.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(s)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let context_str = from_c_str(context_json)?;
|
||||||
|
let params_str = from_c_str(parameters_json)?;
|
||||||
|
|
||||||
|
let resource = regorus::Value::from_json_str(&resource_str)?;
|
||||||
|
let context = regorus::Value::from_json_str(&context_str)?;
|
||||||
|
let params = regorus::Value::from_json_str(¶ms_str)?;
|
||||||
|
|
||||||
|
let wrapped = to_ref(registry)?.registry.normalize_and_wrap(
|
||||||
|
&resource,
|
||||||
|
api_ver.as_deref(),
|
||||||
|
Some(context),
|
||||||
|
Some(params),
|
||||||
|
);
|
||||||
|
wrapped.to_json_str()
|
||||||
|
}();
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(s) => RegorusResult::ok_string(s),
|
||||||
|
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
||||||
|
///
|
||||||
|
/// * `normalized_json` – the normalized resource JSON
|
||||||
|
/// * `api_version` – API version string, or null to use the default alias paths
|
||||||
|
///
|
||||||
|
/// Returns the denormalized ARM JSON string.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn regorus_alias_registry_denormalize(
|
||||||
|
registry: *mut RegorusAliasRegistry,
|
||||||
|
normalized_json: *const c_char,
|
||||||
|
api_version: *const c_char,
|
||||||
|
) -> RegorusResult {
|
||||||
|
with_unwind_guard(|| {
|
||||||
|
let output = || -> Result<String> {
|
||||||
|
let normalized_str = from_c_str(normalized_json)?;
|
||||||
|
let api_ver = if api_version.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let s = from_c_str(api_version)?;
|
||||||
|
if s.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(s)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let normalized = regorus::Value::from_json_str(&normalized_str)?;
|
||||||
|
|
||||||
|
let result = to_ref(registry)?
|
||||||
|
.registry
|
||||||
|
.denormalize(&normalized, api_ver.as_deref());
|
||||||
|
result.to_json_str()
|
||||||
|
}();
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(s) => RegorusResult::ok_string(s),
|
||||||
|
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::common::regorus_result_drop;
|
||||||
|
use core::ffi::CStr;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
/// Helper: create a C string from a Rust &str.
|
||||||
|
fn c(s: &str) -> CString {
|
||||||
|
CString::new(s).expect("CString::new failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper: assert a RegorusResult has Ok status and extract string output.
|
||||||
|
fn assert_ok_string(r: &RegorusResult) -> String {
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
||||||
|
assert!(!r.output.is_null(), "expected non-null output");
|
||||||
|
let s = unsafe { CStr::from_ptr(r.output) }
|
||||||
|
.to_str()
|
||||||
|
.expect("invalid UTF-8 in output")
|
||||||
|
.to_string();
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper: assert a RegorusResult has Ok status with integer output.
|
||||||
|
fn assert_ok_int(r: &RegorusResult) -> i64 {
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
||||||
|
r.int_value
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALIASES: &str = r#"[{
|
||||||
|
"namespace": "Microsoft.Storage",
|
||||||
|
"resourceTypes": [{
|
||||||
|
"resourceType": "storageAccounts",
|
||||||
|
"aliases": [{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||||
|
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||||
|
"paths": []
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]"#;
|
||||||
|
|
||||||
|
const MANIFEST: &str = r#"{
|
||||||
|
"dataNamespace": "Microsoft.KeyVault.Data",
|
||||||
|
"aliases": [],
|
||||||
|
"resourceTypeAliases": [{
|
||||||
|
"resourceType": "vaults/certificates",
|
||||||
|
"aliases": [{
|
||||||
|
"name": "Microsoft.KeyVault.Data/vaults/certificates/keySize",
|
||||||
|
"paths": [{ "path": "keySize", "apiVersions": ["7.0"] }]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lifecycle_new_and_drop() {
|
||||||
|
let reg = regorus_alias_registry_new();
|
||||||
|
assert!(!reg.is_null());
|
||||||
|
regorus_alias_registry_drop(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_json_and_check_len() {
|
||||||
|
let reg = regorus_alias_registry_new();
|
||||||
|
let json = c(ALIASES);
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_load_json(reg, json.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_len(reg);
|
||||||
|
assert_eq!(assert_ok_int(&r), 1);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
regorus_alias_registry_drop(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_manifest_and_check_len() {
|
||||||
|
let reg = regorus_alias_registry_new();
|
||||||
|
let json = c(MANIFEST);
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_load_manifest(reg, json.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_len(reg);
|
||||||
|
assert_eq!(assert_ok_int(&r), 1);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
regorus_alias_registry_drop(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_invalid_json_returns_error() {
|
||||||
|
let reg = regorus_alias_registry_new();
|
||||||
|
let bad = c("not valid json");
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_load_json(reg, bad.as_ptr());
|
||||||
|
assert_ne!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
regorus_alias_registry_drop(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalize_and_wrap_round_trip() {
|
||||||
|
let reg = regorus_alias_registry_new();
|
||||||
|
let aliases = c(ALIASES);
|
||||||
|
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
let resource = c(r#"{
|
||||||
|
"name": "acct1",
|
||||||
|
"type": "Microsoft.Storage/storageAccounts",
|
||||||
|
"properties": { "supportsHttpsTrafficOnly": true }
|
||||||
|
}"#);
|
||||||
|
let api = c("2023-01-01");
|
||||||
|
let ctx = c(r#"{"resourceGroup": {"name": "rg1"}}"#);
|
||||||
|
let params = c(r#"{"env": "prod"}"#);
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
let r = regorus_alias_registry_normalize_and_wrap(
|
||||||
|
reg,
|
||||||
|
resource.as_ptr(),
|
||||||
|
api.as_ptr(),
|
||||||
|
ctx.as_ptr(),
|
||||||
|
params.as_ptr(),
|
||||||
|
);
|
||||||
|
let envelope_json = assert_ok_string(&r);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
// Parse and verify structure
|
||||||
|
let envelope: serde_json::Value =
|
||||||
|
serde_json::from_str(&envelope_json).expect("invalid JSON output");
|
||||||
|
assert!(
|
||||||
|
envelope.get("resource").is_some(),
|
||||||
|
"envelope missing 'resource'"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
envelope.get("parameters").is_some(),
|
||||||
|
"envelope missing 'parameters'"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
envelope.get("context").is_some(),
|
||||||
|
"envelope missing 'context'"
|
||||||
|
);
|
||||||
|
|
||||||
|
// The normalized resource should have lowercased alias fields
|
||||||
|
let res = &envelope["resource"];
|
||||||
|
assert_eq!(res["supportshttpstrafficonly"], true);
|
||||||
|
assert_eq!(res["name"], "acct1");
|
||||||
|
|
||||||
|
// Context and parameters should be passed through
|
||||||
|
assert_eq!(envelope["context"]["resourceGroup"]["name"], "rg1");
|
||||||
|
assert_eq!(envelope["parameters"]["env"], "prod");
|
||||||
|
|
||||||
|
// Denormalize the resource portion
|
||||||
|
let resource_json = serde_json::to_string(&res).expect("serialize resource");
|
||||||
|
let norm_cstr = c(&resource_json);
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_denormalize(reg, norm_cstr.as_ptr(), api.as_ptr());
|
||||||
|
let denorm_json = assert_ok_string(&r);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
let denorm: serde_json::Value =
|
||||||
|
serde_json::from_str(&denorm_json).expect("invalid denorm JSON");
|
||||||
|
// Should be back under properties with restored casing
|
||||||
|
assert_eq!(
|
||||||
|
denorm["properties"]["supportsHttpsTrafficOnly"], true,
|
||||||
|
"expected restored casing under properties"
|
||||||
|
);
|
||||||
|
|
||||||
|
regorus_alias_registry_drop(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn denormalize_invalid_json_returns_error() {
|
||||||
|
let reg = regorus_alias_registry_new();
|
||||||
|
let aliases = c(ALIASES);
|
||||||
|
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
let bad = c("not json");
|
||||||
|
let api = c("2023-01-01");
|
||||||
|
let r = regorus_alias_registry_denormalize(reg, bad.as_ptr(), api.as_ptr());
|
||||||
|
assert_ne!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
regorus_alias_registry_drop(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalize_data_plane_manifest() {
|
||||||
|
let reg = regorus_alias_registry_new();
|
||||||
|
let manifest = c(MANIFEST);
|
||||||
|
let r = regorus_alias_registry_load_manifest(reg, manifest.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
let resource = c(r#"{
|
||||||
|
"type": "Microsoft.KeyVault.Data/vaults/certificates",
|
||||||
|
"keySize": 2048
|
||||||
|
}"#);
|
||||||
|
let api = c("7.0");
|
||||||
|
let ctx = c("{}");
|
||||||
|
let params = c("{}");
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_normalize_and_wrap(
|
||||||
|
reg,
|
||||||
|
resource.as_ptr(),
|
||||||
|
api.as_ptr(),
|
||||||
|
ctx.as_ptr(),
|
||||||
|
params.as_ptr(),
|
||||||
|
);
|
||||||
|
let envelope_json = assert_ok_string(&r);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
let envelope: serde_json::Value =
|
||||||
|
serde_json::from_str(&envelope_json).expect("invalid JSON output");
|
||||||
|
assert_eq!(envelope["resource"]["keysize"], 2048);
|
||||||
|
|
||||||
|
regorus_alias_registry_drop(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_registry_normalize() {
|
||||||
|
let reg = regorus_alias_registry_new();
|
||||||
|
let resource = c(r#"{"name": "test", "type": "Unknown/type", "properties": {"foo": 1}}"#);
|
||||||
|
let api = c("");
|
||||||
|
let ctx = c("{}");
|
||||||
|
let params = c("{}");
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_normalize_and_wrap(
|
||||||
|
reg,
|
||||||
|
resource.as_ptr(),
|
||||||
|
api.as_ptr(),
|
||||||
|
ctx.as_ptr(),
|
||||||
|
params.as_ptr(),
|
||||||
|
);
|
||||||
|
let json = assert_ok_string(&r);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
let envelope: serde_json::Value = serde_json::from_str(&json).expect("invalid JSON");
|
||||||
|
// Without aliases, properties should still be flattened
|
||||||
|
assert_eq!(envelope["resource"]["foo"], 1);
|
||||||
|
assert_eq!(envelope["resource"]["name"], "test");
|
||||||
|
|
||||||
|
regorus_alias_registry_drop(reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ use core::ffi::{c_char, c_longlong, c_void, CStr};
|
|||||||
use core::{mem, ptr};
|
use core::{mem, ptr};
|
||||||
|
|
||||||
/// Status of a call on `RegorusEngine`.
|
/// Status of a call on `RegorusEngine`.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub enum RegorusStatus {
|
pub enum RegorusStatus {
|
||||||
/// The operation was successful.
|
/// The operation was successful.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
|
mod alias_registry;
|
||||||
mod allocator;
|
mod allocator;
|
||||||
mod common;
|
mod common;
|
||||||
mod compile;
|
mod compile;
|
||||||
|
|||||||
88
bindings/java/Cargo.lock
generated
88
bindings/java/Cargo.lock
generated
@@ -69,9 +69,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "borrow-or-share"
|
name = "borrow-or-share"
|
||||||
@@ -91,9 +91,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecount"
|
name = "bytecount"
|
||||||
@@ -109,9 +109,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.55"
|
version = "1.2.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -512,9 +512,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni"
|
name = "jni"
|
||||||
@@ -567,9 +567,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.85"
|
version = "0.3.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -616,9 +616,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.180"
|
version = "0.2.183"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
@@ -649,9 +649,9 @@ checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "msvc_spectre_libs"
|
name = "msvc_spectre_libs"
|
||||||
@@ -743,9 +743,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "outref"
|
name = "outref"
|
||||||
@@ -842,9 +842,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.44"
|
version = "1.0.45"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -936,9 +936,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -947,9 +947,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.8"
|
version = "0.8.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regorus"
|
name = "regorus"
|
||||||
@@ -1024,9 +1024,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.22"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
@@ -1153,9 +1153,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.114"
|
version = "2.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1211,9 +1211,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
@@ -1247,9 +1247,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.22.0"
|
version = "1.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -1307,9 +1307,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -1320,9 +1320,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -1330,9 +1330,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -1343,9 +1343,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -1580,18 +1580,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1654,6 +1654,6 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.17"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|||||||
96
bindings/python/Cargo.lock
generated
96
bindings/python/Cargo.lock
generated
@@ -69,9 +69,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "borrow-or-share"
|
name = "borrow-or-share"
|
||||||
@@ -91,9 +91,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecount"
|
name = "bytecount"
|
||||||
@@ -103,9 +103,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.55"
|
version = "1.2.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -496,15 +496,15 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.85"
|
version = "0.3.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -551,9 +551,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.180"
|
version = "0.2.183"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
@@ -584,9 +584,9 @@ checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "msvc_spectre_libs"
|
name = "msvc_spectre_libs"
|
||||||
@@ -678,9 +678,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-float"
|
name = "ordered-float"
|
||||||
@@ -746,9 +746,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.13.0"
|
version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
|
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postcard"
|
name = "postcard"
|
||||||
@@ -851,9 +851,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.44"
|
version = "1.0.45"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -945,9 +945,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -956,9 +956,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.8"
|
version = "0.8.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regorus"
|
name = "regorus"
|
||||||
@@ -1025,9 +1025,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.22"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
@@ -1129,9 +1129,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.114"
|
version = "2.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1151,9 +1151,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.13.4"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba"
|
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
@@ -1193,9 +1193,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
@@ -1229,9 +1229,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.22.0"
|
version = "1.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -1279,9 +1279,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -1292,9 +1292,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -1302,9 +1302,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -1315,9 +1315,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.108"
|
version = "0.2.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -1534,18 +1534,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1608,6 +1608,6 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.17"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|||||||
95
bindings/wasm/Cargo.lock
generated
95
bindings/wasm/Cargo.lock
generated
@@ -80,9 +80,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "borrow-or-share"
|
name = "borrow-or-share"
|
||||||
@@ -102,9 +102,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecount"
|
name = "bytecount"
|
||||||
@@ -120,9 +120,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.55"
|
version = "1.2.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
|
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -297,26 +297,25 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -553,9 +552,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
@@ -608,9 +607,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.180"
|
version = "0.2.183"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
@@ -647,9 +646,9 @@ checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minicov"
|
name = "minicov"
|
||||||
@@ -761,9 +760,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oorandom"
|
name = "oorandom"
|
||||||
@@ -826,15 +825,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.16"
|
version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-utils"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postcard"
|
name = "postcard"
|
||||||
@@ -878,9 +871,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.44"
|
version = "1.0.45"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -972,9 +965,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -983,9 +976,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.8"
|
version = "0.8.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regorus"
|
name = "regorus"
|
||||||
@@ -1042,9 +1035,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.22"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
@@ -1148,9 +1141,9 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.11"
|
version = "0.4.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
@@ -1172,9 +1165,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.114"
|
version = "2.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1230,9 +1223,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
@@ -1266,9 +1259,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.22.0"
|
version = "1.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -1670,18 +1663,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
|
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.36"
|
version = "0.8.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
|
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1744,6 +1737,6 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.17"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|||||||
114
src/languages/azure_policy/aliases/denormalizer/casing.rs
Normal file
114
src/languages/azure_policy/aliases/denormalizer/casing.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! Key casing restoration from alias metadata.
|
||||||
|
|
||||||
|
use alloc::collections::BTreeMap;
|
||||||
|
use alloc::string::{String, ToString as _};
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
use super::super::obj_map::{make_array, make_value, new_map, obj_insert, val_str, ROOT_FIELDS};
|
||||||
|
use super::super::types::ResolvedEntry;
|
||||||
|
|
||||||
|
fn insert_default_casing(map: &mut BTreeMap<String, String>) {
|
||||||
|
for &field in ROOT_FIELDS {
|
||||||
|
map.insert(field.to_ascii_lowercase(), field.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonical casing for standard nested root-field object members that are
|
||||||
|
// not described by alias metadata but still need round-trip restoration.
|
||||||
|
for canonical in [
|
||||||
|
"principalId",
|
||||||
|
"tenantId",
|
||||||
|
"userAssignedIdentities",
|
||||||
|
"promotionCode",
|
||||||
|
"createdBy",
|
||||||
|
"createdByType",
|
||||||
|
"createdAt",
|
||||||
|
"lastModifiedBy",
|
||||||
|
"lastModifiedByType",
|
||||||
|
"lastModifiedAt",
|
||||||
|
] {
|
||||||
|
map.entry(canonical.to_ascii_lowercase())
|
||||||
|
.or_insert_with(|| canonical.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the default casing map used when alias metadata is unavailable.
|
||||||
|
pub fn default_casing_map() -> BTreeMap<String, String> {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
insert_default_casing(&mut map);
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a mapping from lowercase key → original-cased key from alias entries.
|
||||||
|
pub fn build_casing_map(entries: &BTreeMap<String, ResolvedEntry>) -> BTreeMap<String, String> {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
insert_default_casing(&mut map);
|
||||||
|
|
||||||
|
for entry in entries.values() {
|
||||||
|
for segment in entry.short_name.split('.') {
|
||||||
|
let clean = segment.replace("[*]", "");
|
||||||
|
if !clean.is_empty() {
|
||||||
|
map.entry(clean.to_ascii_lowercase())
|
||||||
|
.or_insert_with(|| clean.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for segment in entry.default_path.split('.') {
|
||||||
|
let clean = segment.replace("[*]", "");
|
||||||
|
if !clean.is_empty() && !clean.eq_ignore_ascii_case("properties") {
|
||||||
|
map.entry(clean.to_ascii_lowercase())
|
||||||
|
.or_insert_with(|| clean.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also include segments from all version-specific ARM paths so
|
||||||
|
// casing can be restored correctly for versioned aliases.
|
||||||
|
for (_ver, path) in &entry.versioned_paths {
|
||||||
|
for segment in path.split('.') {
|
||||||
|
let clean = segment.replace("[*]", "");
|
||||||
|
if !clean.is_empty() && !clean.eq_ignore_ascii_case("properties") {
|
||||||
|
map.entry(clean.to_ascii_lowercase())
|
||||||
|
.or_insert_with(|| clean.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore the original casing of a key using the casing map.
|
||||||
|
pub fn restore_casing(key: &str, casing_map: &BTreeMap<String, String>) -> String {
|
||||||
|
casing_map
|
||||||
|
.get(&key.to_ascii_lowercase())
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| key.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively restore key casing in a JSON value.
|
||||||
|
pub fn denormalize_value(value: &Value, casing_map: &BTreeMap<String, String>) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Object(obj) => {
|
||||||
|
let mut result = new_map();
|
||||||
|
for (k, v) in obj.iter() {
|
||||||
|
if let Some(key_s) = val_str(k) {
|
||||||
|
let restored_key = restore_casing(key_s, casing_map);
|
||||||
|
obj_insert(&mut result, &restored_key, denormalize_value(v, casing_map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
make_value(result)
|
||||||
|
}
|
||||||
|
Value::Array(arr) => {
|
||||||
|
let items: Vec<Value> = arr
|
||||||
|
.iter()
|
||||||
|
.map(|v| denormalize_value(v, casing_map))
|
||||||
|
.collect();
|
||||||
|
make_array(items)
|
||||||
|
}
|
||||||
|
_ => value.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/languages/azure_policy/aliases/denormalizer/helpers.rs
Normal file
13
src/languages/azure_policy/aliases/denormalizer/helpers.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! Small helper functions used by the denormalizer.
|
||||||
|
|
||||||
|
use crate::Rc;
|
||||||
|
|
||||||
|
use super::super::obj_map::ObjMap;
|
||||||
|
|
||||||
|
/// Find a key in an ObjMap using case-insensitive comparison.
|
||||||
|
pub fn find_key_ci(obj: &ObjMap, key: &str) -> Option<Rc<str>> {
|
||||||
|
obj.keys().find(|k| k.eq_ignore_ascii_case(key)).cloned()
|
||||||
|
}
|
||||||
227
src/languages/azure_policy/aliases/denormalizer/mod.rs
Normal file
227
src/languages/azure_policy/aliases/denormalizer/mod.rs
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! Normalized `input.resource` → ARM JSON reverse transformation.
|
||||||
|
|
||||||
|
mod casing;
|
||||||
|
pub(crate) mod helpers;
|
||||||
|
mod sub_resource;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
use alloc::collections::BTreeSet;
|
||||||
|
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
use crate::Rc;
|
||||||
|
|
||||||
|
use super::obj_map::{
|
||||||
|
extract_type_field, is_root_field_collision, make_value, new_map, obj_insert,
|
||||||
|
set_nested_verbatim, val_str, ROOT_FIELDS,
|
||||||
|
};
|
||||||
|
use super::types::ResolvedAliases;
|
||||||
|
use super::AliasRegistry;
|
||||||
|
|
||||||
|
use super::normalizer::{apply_element_remap, ElementRemap};
|
||||||
|
|
||||||
|
use casing::{build_casing_map, default_casing_map, denormalize_value, restore_casing};
|
||||||
|
use helpers::find_key_ci;
|
||||||
|
|
||||||
|
use super::obj_map::remove_element_field;
|
||||||
|
|
||||||
|
/// Denormalize a normalized resource back to ARM JSON structure.
|
||||||
|
pub fn denormalize(
|
||||||
|
normalized: &Value,
|
||||||
|
registry: Option<&AliasRegistry>,
|
||||||
|
api_version: Option<&str>,
|
||||||
|
) -> Value {
|
||||||
|
let aliases = registry.and_then(|r| extract_type_field(normalized).and_then(|rt| r.get(rt)));
|
||||||
|
denormalize_with_aliases(normalized, aliases, api_version)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal denormalization with pre-resolved alias data.
|
||||||
|
pub fn denormalize_with_aliases(
|
||||||
|
normalized: &Value,
|
||||||
|
aliases: Option<&ResolvedAliases>,
|
||||||
|
api_version: Option<&str>,
|
||||||
|
) -> Value {
|
||||||
|
let obj = match normalized.as_object() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => return normalized.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let entries = aliases.map(|a| &a.entries);
|
||||||
|
let casing_map = entries
|
||||||
|
.map(build_casing_map)
|
||||||
|
.unwrap_or_else(default_casing_map);
|
||||||
|
let empty_set = BTreeSet::new();
|
||||||
|
let sub_resource_set = aliases.map_or(&empty_set, |a| &a.sub_resource_arrays);
|
||||||
|
|
||||||
|
let is_data_plane =
|
||||||
|
extract_type_field(normalized).is_some_and(|t| t.to_ascii_lowercase().contains(".data/"));
|
||||||
|
|
||||||
|
let mut result = new_map();
|
||||||
|
let mut properties = new_map();
|
||||||
|
|
||||||
|
// Phase 1: Root fields → ARM root with original casing.
|
||||||
|
for &field in ROOT_FIELDS {
|
||||||
|
let lc = field.to_ascii_lowercase();
|
||||||
|
// Fast-path: direct BTreeMap lookup (O(log N)) for the common case
|
||||||
|
// where normalized input was produced by our normalizer with lowercase keys.
|
||||||
|
// Falls back to linear case-insensitive scan for externally-supplied mixed-case input.
|
||||||
|
let lc_key = Value::String(Rc::from(lc.as_str()));
|
||||||
|
let found = obj.get(&lc_key).or_else(|| {
|
||||||
|
obj.iter()
|
||||||
|
.find(|(k, _)| val_str(k).is_some_and(|s| s.eq_ignore_ascii_case(&lc)))
|
||||||
|
.map(|(_, v)| v)
|
||||||
|
});
|
||||||
|
if let Some(val) = found {
|
||||||
|
let restored = denormalize_value(val, &casing_map);
|
||||||
|
obj_insert(&mut result, field, restored);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2a: Non-aliased, non-root fields.
|
||||||
|
for (key, val) in obj.iter() {
|
||||||
|
let key_s = match val_str(key) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
if ROOT_FIELDS.iter().any(|f| f.eq_ignore_ascii_case(key_s)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lookup_key = key_s.strip_prefix("_p_").unwrap_or(key_s);
|
||||||
|
let lookup_key_lc = lookup_key.to_ascii_lowercase();
|
||||||
|
let has_alias = entries.is_some_and(|e| e.contains_key(lookup_key_lc.as_str()));
|
||||||
|
if has_alias {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let denorm_val = denormalize_value(val, &casing_map);
|
||||||
|
|
||||||
|
if key_s.starts_with("_p_") {
|
||||||
|
let restored = restore_casing(lookup_key, &casing_map);
|
||||||
|
obj_insert(&mut properties, &restored, denorm_val);
|
||||||
|
} else if is_data_plane {
|
||||||
|
let restored = restore_casing(key_s, &casing_map);
|
||||||
|
obj_insert(&mut result, &restored, denorm_val);
|
||||||
|
} else {
|
||||||
|
let restored = restore_casing(key_s, &casing_map);
|
||||||
|
obj_insert(&mut properties, &restored, denorm_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2b: Aliased scalar fields → versioned ARM paths.
|
||||||
|
if let Some(entries) = entries {
|
||||||
|
for (lc_key, entry) in entries {
|
||||||
|
if entry.is_wildcard {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if sub_resource_set.contains(lc_key.as_str()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let normalized_key = if is_root_field_collision(&entry.short_name, &entry.default_path)
|
||||||
|
{
|
||||||
|
alloc::format!("_p_{}", entry.short_name.to_ascii_lowercase())
|
||||||
|
} else {
|
||||||
|
lc_key.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fast-path: direct BTreeMap lookup for lowercase keys,
|
||||||
|
// with case-insensitive fallback for mixed-case external input.
|
||||||
|
let nk_val = Value::String(Rc::from(normalized_key.as_str()));
|
||||||
|
let val = obj.get(&nk_val).or_else(|| {
|
||||||
|
obj.iter()
|
||||||
|
.find(|(k, _)| {
|
||||||
|
val_str(k).is_some_and(|s| s.eq_ignore_ascii_case(&normalized_key))
|
||||||
|
})
|
||||||
|
.map(|(_, v)| v)
|
||||||
|
});
|
||||||
|
let val = match val {
|
||||||
|
Some(v) => v,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let arm_path = entry.select_path(api_version);
|
||||||
|
let denorm_val = denormalize_value(val, &casing_map);
|
||||||
|
|
||||||
|
if let Some(props_path) = arm_path.strip_prefix("properties.") {
|
||||||
|
set_nested_verbatim(&mut properties, props_path, denorm_val);
|
||||||
|
} else {
|
||||||
|
set_nested_verbatim(&mut result, arm_path, denorm_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2c + 2d: Use precomputed renames/remaps.
|
||||||
|
// Look up versioned aggregates when api_version is provided,
|
||||||
|
// falling back to default aggregates.
|
||||||
|
if let Some(aliases) = aliases {
|
||||||
|
let agg = api_version.map_or(&aliases.default_aggregates, |ver| {
|
||||||
|
let ver_lc = ver.to_ascii_lowercase();
|
||||||
|
aliases
|
||||||
|
.versioned_aggregates
|
||||||
|
.get(&ver_lc)
|
||||||
|
.unwrap_or(&aliases.default_aggregates)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Phase 2c: Precomputed array base renames.
|
||||||
|
for (alias_base_lc, arm_base) in &agg.array_renames_denormalize {
|
||||||
|
if let Some(key) = find_key_ci(&properties, alias_base_lc) {
|
||||||
|
if let Some(val) = properties.remove(key.as_ref()) {
|
||||||
|
obj_insert(&mut properties, arm_base, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2d: Precomputed reverse element-level field remaps.
|
||||||
|
for rev in &agg.reverse_element_remaps {
|
||||||
|
let remap = ElementRemap {
|
||||||
|
array_chain: rev.array_chain.clone(),
|
||||||
|
source_field: rev.source_field.clone(),
|
||||||
|
target_field: if rev.target_field.contains('.') {
|
||||||
|
rev.target_field
|
||||||
|
.split('.')
|
||||||
|
.map(|segment| restore_casing(segment, &casing_map))
|
||||||
|
.collect::<alloc::vec::Vec<_>>()
|
||||||
|
.join(".")
|
||||||
|
} else {
|
||||||
|
restore_casing(&rev.target_field, &casing_map)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
apply_element_remap(&mut properties, &remap, false);
|
||||||
|
remove_element_field(&mut properties, &rev.array_chain, &rev.cleanup_field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 3: Re-wrap sub-resource array elements.
|
||||||
|
if let Some(aliases) = aliases {
|
||||||
|
if !aliases.sub_resource_arrays.is_empty() {
|
||||||
|
sub_resource::rewrap_sub_resource_arrays(
|
||||||
|
&mut properties,
|
||||||
|
&aliases.sub_resource_arrays,
|
||||||
|
&aliases.entries,
|
||||||
|
api_version,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
let existing = Rc::make_mut(existing_rc);
|
||||||
|
for (k, v) in properties {
|
||||||
|
existing.entry(Value::String(k)).or_insert(v);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj_insert(&mut result, "properties", make_value(properties));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_value(result)
|
||||||
|
}
|
||||||
228
src/languages/azure_policy/aliases/denormalizer/sub_resource.rs
Normal file
228
src/languages/azure_policy/aliases/denormalizer/sub_resource.rs
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! Sub-resource array re-wrapping during denormalization.
|
||||||
|
|
||||||
|
use alloc::collections::{BTreeMap, BTreeSet};
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
use super::super::obj_map::{make_value, new_map, obj_insert, val_str, ObjMap};
|
||||||
|
use super::super::types::ResolvedEntry;
|
||||||
|
use super::helpers::find_key_ci;
|
||||||
|
|
||||||
|
/// Sub-resource array element envelope fields that remain at the element root.
|
||||||
|
const ELEMENT_ENVELOPE_FIELDS: &[&str] = &["name", "type", "id", "etag"];
|
||||||
|
|
||||||
|
/// Re-wrap sub-resource array elements by moving non-envelope fields back
|
||||||
|
/// under each element's `properties` object.
|
||||||
|
pub fn rewrap_sub_resource_arrays(
|
||||||
|
properties: &mut ObjMap,
|
||||||
|
sub_arrays: &BTreeSet<String>,
|
||||||
|
entries: &BTreeMap<String, ResolvedEntry>,
|
||||||
|
api_version: Option<&str>,
|
||||||
|
) {
|
||||||
|
let mut sorted: Vec<&String> = sub_arrays.iter().collect();
|
||||||
|
sorted.sort_by(|a, b| {
|
||||||
|
let depth_a = a.chars().filter(|&c| c == '.').count();
|
||||||
|
let depth_b = b.chars().filter(|&c| c == '.').count();
|
||||||
|
depth_b.cmp(&depth_a)
|
||||||
|
});
|
||||||
|
|
||||||
|
for sub_array_path in sorted {
|
||||||
|
let envelope_fields = classify_envelope_fields(sub_array_path, entries, api_version);
|
||||||
|
let parts: Vec<&str> = sub_array_path.split('.').collect();
|
||||||
|
|
||||||
|
if parts.len() == 1 {
|
||||||
|
if let Some(key) = parts.first().and_then(|p| find_key_ci(properties, p)) {
|
||||||
|
if let Some(Value::Array(arr)) = properties.get_mut(key.as_ref()) {
|
||||||
|
let inner = crate::Rc::make_mut(arr);
|
||||||
|
for elem in inner.iter_mut() {
|
||||||
|
*elem = rewrap_element(elem, &envelope_fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some((&array_name, parent_parts)) = parts.split_last() {
|
||||||
|
rewrap_nested_array(properties, parent_parts, array_name, &envelope_fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine which element-level fields are envelope fields for a given
|
||||||
|
/// sub-resource array.
|
||||||
|
fn classify_envelope_fields(
|
||||||
|
sub_array_path: &str,
|
||||||
|
entries: &BTreeMap<String, ResolvedEntry>,
|
||||||
|
api_version: Option<&str>,
|
||||||
|
) -> BTreeSet<String> {
|
||||||
|
let mut envelope = BTreeSet::new();
|
||||||
|
for &f in ELEMENT_ENVELOPE_FIELDS {
|
||||||
|
envelope.insert(f.to_ascii_lowercase());
|
||||||
|
}
|
||||||
|
|
||||||
|
let wildcard_prefix: String = {
|
||||||
|
let parts: Vec<&str> = sub_array_path.split('.').collect();
|
||||||
|
let mut prefix = String::new();
|
||||||
|
for (i, part) in parts.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
prefix.push_str("[*].");
|
||||||
|
}
|
||||||
|
prefix.push_str(&part.to_ascii_lowercase());
|
||||||
|
}
|
||||||
|
prefix.push_str("[*].");
|
||||||
|
prefix
|
||||||
|
};
|
||||||
|
|
||||||
|
for (lc_key, entry) in entries {
|
||||||
|
if !lc_key.starts_with(&wildcard_prefix) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let field = &lc_key[wildcard_prefix.len()..];
|
||||||
|
let first_segment = field.split('.').next().unwrap_or(field);
|
||||||
|
|
||||||
|
let arm_path = entry.select_path(api_version);
|
||||||
|
let arm_after_last_wildcard = arm_path.rsplit("[*].").next().unwrap_or("");
|
||||||
|
|
||||||
|
if !arm_after_last_wildcard.starts_with("properties.") {
|
||||||
|
envelope.insert(first_segment.to_ascii_lowercase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
envelope
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively navigate nested arrays and re-wrap elements of the innermost
|
||||||
|
/// sub-resource array.
|
||||||
|
fn rewrap_nested_array(
|
||||||
|
obj: &mut ObjMap,
|
||||||
|
parent_parts: &[&str],
|
||||||
|
array_name: &str,
|
||||||
|
envelope_fields: &BTreeSet<String>,
|
||||||
|
) {
|
||||||
|
if parent_parts.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent_key = match parent_parts.first().and_then(|&p| find_key_ci(obj, p)) {
|
||||||
|
Some(k) => k,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent_arr = match obj.get_mut(parent_key.as_ref()) {
|
||||||
|
Some(Value::Array(arr)) => crate::Rc::make_mut(arr),
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
for element in parent_arr.iter_mut() {
|
||||||
|
if let Value::Object(obj_rc) = element {
|
||||||
|
let inner_btree = crate::Rc::make_mut(obj_rc);
|
||||||
|
|
||||||
|
if parent_parts.len() > 1 {
|
||||||
|
rewrap_nested_array_in_btree(
|
||||||
|
inner_btree,
|
||||||
|
parent_parts.get(1..).unwrap_or_default(),
|
||||||
|
array_name,
|
||||||
|
envelope_fields,
|
||||||
|
);
|
||||||
|
} else if let Some(arr_key) = find_key_ci_btree(inner_btree, array_name) {
|
||||||
|
if let Some(Value::Array(arr)) = inner_btree.get_mut(&arr_key) {
|
||||||
|
let inner = crate::Rc::make_mut(arr);
|
||||||
|
for inner_elem in inner.iter_mut() {
|
||||||
|
*inner_elem = rewrap_element(inner_elem, envelope_fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
parent_parts: &[&str],
|
||||||
|
array_name: &str,
|
||||||
|
envelope_fields: &BTreeSet<String>,
|
||||||
|
) {
|
||||||
|
if parent_parts.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent_key = match parent_parts
|
||||||
|
.first()
|
||||||
|
.and_then(|&p| find_key_ci_btree(btree, p))
|
||||||
|
{
|
||||||
|
Some(k) => k,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent_arr = match btree.get_mut(&parent_key) {
|
||||||
|
Some(Value::Array(arr)) => crate::Rc::make_mut(arr),
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
for element in parent_arr.iter_mut() {
|
||||||
|
if let Value::Object(obj_rc) = element {
|
||||||
|
let inner_btree = crate::Rc::make_mut(obj_rc);
|
||||||
|
|
||||||
|
if parent_parts.len() > 1 {
|
||||||
|
rewrap_nested_array_in_btree(
|
||||||
|
inner_btree,
|
||||||
|
parent_parts.get(1..).unwrap_or_default(),
|
||||||
|
array_name,
|
||||||
|
envelope_fields,
|
||||||
|
);
|
||||||
|
} else if let Some(arr_key) = find_key_ci_btree(inner_btree, array_name) {
|
||||||
|
if let Some(Value::Array(arr)) = inner_btree.get_mut(&arr_key) {
|
||||||
|
let inner = crate::Rc::make_mut(arr);
|
||||||
|
for inner_elem in inner.iter_mut() {
|
||||||
|
*inner_elem = rewrap_element(inner_elem, envelope_fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
btree
|
||||||
|
.keys()
|
||||||
|
.find(|k| val_str(k).is_some_and(|s| s.eq_ignore_ascii_case(key)))
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-wrap a single sub-resource array element by moving non-envelope
|
||||||
|
/// fields back under a `properties` object.
|
||||||
|
fn rewrap_element(element: &Value, envelope_fields: &BTreeSet<String>) -> Value {
|
||||||
|
let obj = match element.as_object() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => return element.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut envelope = new_map();
|
||||||
|
let mut props = new_map();
|
||||||
|
|
||||||
|
for (key, val) in obj.iter() {
|
||||||
|
let key_s = match val_str(key) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
if envelope_fields.contains(&key_s.to_ascii_lowercase()) {
|
||||||
|
obj_insert(&mut envelope, key_s, val.clone());
|
||||||
|
} else {
|
||||||
|
obj_insert(&mut props, key_s, val.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !props.is_empty() {
|
||||||
|
obj_insert(&mut envelope, "properties", make_value(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
make_value(envelope)
|
||||||
|
}
|
||||||
55
src/languages/azure_policy/aliases/denormalizer/tests.rs
Normal file
55
src/languages/azure_policy/aliases/denormalizer/tests.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! Inline denormalizer unit tests.
|
||||||
|
|
||||||
|
use alloc::collections::BTreeMap;
|
||||||
|
use alloc::string::ToString as _;
|
||||||
|
use alloc::vec;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use super::super::types::ResolvedEntry;
|
||||||
|
use super::casing::build_casing_map;
|
||||||
|
|
||||||
|
fn make_entry(short: &str, default: &str, versioned: Vec<(&str, &str)>) -> ResolvedEntry {
|
||||||
|
ResolvedEntry::new(
|
||||||
|
short.to_string(),
|
||||||
|
default.to_string(),
|
||||||
|
versioned
|
||||||
|
.into_iter()
|
||||||
|
.map(|(v, p)| (v.to_string(), p.to_string()))
|
||||||
|
.collect(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_casing_map_extracts_from_aliases() {
|
||||||
|
let mut entries = BTreeMap::new();
|
||||||
|
entries.insert(
|
||||||
|
"supportshttpstrafficonly".to_string(),
|
||||||
|
make_entry(
|
||||||
|
"supportsHttpsTrafficOnly",
|
||||||
|
"properties.supportsHttpsTrafficOnly",
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
entries.insert(
|
||||||
|
"networkacls.defaultaction".to_string(),
|
||||||
|
make_entry(
|
||||||
|
"networkAcls.defaultAction",
|
||||||
|
"properties.networkAcls.defaultAction",
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let map = build_casing_map(&entries);
|
||||||
|
assert_eq!(
|
||||||
|
map.get("supportshttpstrafficonly"),
|
||||||
|
Some(&"supportsHttpsTrafficOnly".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(map.get("networkacls"), Some(&"networkAcls".to_string()));
|
||||||
|
assert_eq!(map.get("defaultaction"), Some(&"defaultAction".to_string()));
|
||||||
|
assert_eq!(map.get("managedby"), Some(&"managedBy".to_string()));
|
||||||
|
assert_eq!(map.get("apiversion"), Some(&"apiVersion".to_string()));
|
||||||
|
}
|
||||||
1310
src/languages/azure_policy/aliases/mod.rs
Normal file
1310
src/languages/azure_policy/aliases/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! Per-alias path resolution: reads values from versioned ARM paths and places
|
||||||
|
//! them at alias short name paths in the normalized output.
|
||||||
|
|
||||||
|
use alloc::string::String;
|
||||||
|
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
use super::super::obj_map::remove_element_field;
|
||||||
|
use super::super::obj_map::{
|
||||||
|
collision_safe_key, is_root_field_collision, obj_contains, obj_insert, obj_remove,
|
||||||
|
set_nested_lowercased, ObjMap,
|
||||||
|
};
|
||||||
|
use super::super::types::ResolvedAliases;
|
||||||
|
use super::element_remap::apply_element_remap_precomputed;
|
||||||
|
use super::flatten::normalize_value;
|
||||||
|
|
||||||
|
/// Apply per-alias path resolution to the normalized result.
|
||||||
|
///
|
||||||
|
/// Uses precomputed element remaps and array renames from [`ResolvedAliases`]
|
||||||
|
/// when `api_version` is `None` (the common case). Falls back to dynamic
|
||||||
|
/// computation when a specific `api_version` is provided.
|
||||||
|
pub fn apply_alias_entries(
|
||||||
|
result: &mut ObjMap,
|
||||||
|
raw: &Value,
|
||||||
|
aliases: &ResolvedAliases,
|
||||||
|
api_version: Option<&str>,
|
||||||
|
) {
|
||||||
|
let entries = &aliases.entries;
|
||||||
|
let sub_resource_set = &aliases.sub_resource_arrays;
|
||||||
|
|
||||||
|
for (lc_key, entry) in entries {
|
||||||
|
if entry.is_wildcard {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip sub-resource array root entries.
|
||||||
|
if sub_resource_set.contains(lc_key.as_str()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use precomputed segments for all paths (default and versioned).
|
||||||
|
let segments = entry.select_path_segments(api_version);
|
||||||
|
let value = navigate_arm_path_segments(raw, segments);
|
||||||
|
|
||||||
|
if let Some(value) = value {
|
||||||
|
let value = normalize_value(&value, &entry.short_name, None);
|
||||||
|
|
||||||
|
let target = if is_root_field_collision(&entry.short_name, &entry.default_path) {
|
||||||
|
collision_safe_key(&entry.short_name)
|
||||||
|
} else {
|
||||||
|
entry.short_name.clone()
|
||||||
|
};
|
||||||
|
set_nested_lowercased(result, &target, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up precomputed aggregates: default or per-version.
|
||||||
|
let agg = api_version.map_or(&aliases.default_aggregates, |ver| {
|
||||||
|
let ver_lc = ver.to_ascii_lowercase();
|
||||||
|
aliases
|
||||||
|
.versioned_aggregates
|
||||||
|
.get(&ver_lc)
|
||||||
|
.unwrap_or(&aliases.default_aggregates)
|
||||||
|
});
|
||||||
|
|
||||||
|
for remap in &agg.element_remaps {
|
||||||
|
apply_element_remap_precomputed(result, remap);
|
||||||
|
// Remove the original ARM field so the normalized output only has the
|
||||||
|
// alias short name. Without this, the stale source key survives and
|
||||||
|
// casing restoration during denormalization can produce a duplicate.
|
||||||
|
remove_element_field(result, &remap.array_chain, &remap.source_field);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (source_lc, target_lc) in &agg.array_renames_normalize {
|
||||||
|
if !obj_contains(result, target_lc.as_str()) {
|
||||||
|
if let Some(val) = obj_remove(result, source_lc.as_str()) {
|
||||||
|
obj_insert(result, target_lc, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Navigate an ARM path using precomputed segments (avoids per-call split).
|
||||||
|
fn navigate_arm_path_segments(value: &Value, segments: &[String]) -> Option<Value> {
|
||||||
|
let mut current = value;
|
||||||
|
for segment in segments {
|
||||||
|
current = current
|
||||||
|
.as_object()
|
||||||
|
.ok()?
|
||||||
|
.get(&Value::from(segment.as_str()))?;
|
||||||
|
}
|
||||||
|
Some(current.clone())
|
||||||
|
}
|
||||||
252
src/languages/azure_policy/aliases/normalizer/element_remap.rs
Normal file
252
src/languages/azure_policy/aliases/normalizer/element_remap.rs
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! Element-level field remapping for array aliases with versioned paths.
|
||||||
|
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
use super::super::types::PrecomputedRemap;
|
||||||
|
|
||||||
|
/// Describes a field remapping inside each element of a (possibly nested) array.
|
||||||
|
pub struct ElementRemap {
|
||||||
|
/// Chain of array navigations for nested `[*]` levels.
|
||||||
|
pub(crate) array_chain: Vec<Vec<String>>,
|
||||||
|
/// Dot-separated path to read within the innermost array element.
|
||||||
|
pub(crate) source_field: String,
|
||||||
|
/// Dot-separated path to write within the innermost array element.
|
||||||
|
pub(crate) target_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply an element-level field remap to each element of an array (or nested
|
||||||
|
/// array chain).
|
||||||
|
///
|
||||||
|
/// When `lowercase` is `true` (normalizer), target path segments are
|
||||||
|
/// lowercased. When `false` (denormalizer), they are written verbatim
|
||||||
|
/// so that restored casing is preserved.
|
||||||
|
pub fn apply_element_remap(result: &mut ObjMap, remap: &ElementRemap, lowercase: bool) {
|
||||||
|
apply_remap_at_depth(
|
||||||
|
result,
|
||||||
|
&remap.array_chain,
|
||||||
|
0,
|
||||||
|
&remap.source_field,
|
||||||
|
&remap.target_field,
|
||||||
|
lowercase,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a precomputed element remap (from [`PrecomputedRemap`]) without
|
||||||
|
/// any per-call string splitting or allocation.
|
||||||
|
pub fn apply_element_remap_precomputed(result: &mut ObjMap, remap: &PrecomputedRemap) {
|
||||||
|
apply_remap_at_depth(
|
||||||
|
result,
|
||||||
|
&remap.array_chain,
|
||||||
|
0,
|
||||||
|
&remap.source_field,
|
||||||
|
&remap.target_field,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively navigate nested arrays via `array_chain` and apply a field
|
||||||
|
/// remap in each innermost element.
|
||||||
|
fn apply_remap_at_depth(
|
||||||
|
obj: &mut ObjMap,
|
||||||
|
array_chain: &[Vec<String>],
|
||||||
|
depth: usize,
|
||||||
|
source_field: &str,
|
||||||
|
target_field: &str,
|
||||||
|
lowercase: bool,
|
||||||
|
) {
|
||||||
|
let Some(nav) = array_chain.get(depth) else {
|
||||||
|
remap_deep_field(obj, source_field, target_field, lowercase);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let first = match nav.first() {
|
||||||
|
Some(f) => f.as_str(),
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Navigate through intermediate segments to reach the array value.
|
||||||
|
let arr_val = if nav.len() == 1 {
|
||||||
|
match obj_get_mut(obj, first) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut cur: &mut Value = match obj_get_mut(obj, first) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
for segment in nav.iter().skip(1) {
|
||||||
|
cur = match cur.as_object_mut() {
|
||||||
|
Ok(inner) => match inner.get_mut(&Value::from(segment.as_str())) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
},
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cur
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Value::Array(elements) = arr_val {
|
||||||
|
let inner = crate::Rc::make_mut(elements);
|
||||||
|
for elem in inner.iter_mut() {
|
||||||
|
if let Value::Object(obj_rc) = elem {
|
||||||
|
let inner_btree = crate::Rc::make_mut(obj_rc);
|
||||||
|
remap_at_depth_in_btree(
|
||||||
|
inner_btree,
|
||||||
|
array_chain,
|
||||||
|
depth.saturating_add(1),
|
||||||
|
source_field,
|
||||||
|
target_field,
|
||||||
|
lowercase,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
array_chain: &[Vec<String>],
|
||||||
|
depth: usize,
|
||||||
|
source_field: &str,
|
||||||
|
target_field: &str,
|
||||||
|
lowercase: bool,
|
||||||
|
) {
|
||||||
|
let Some(nav) = array_chain.get(depth) else {
|
||||||
|
remap_deep_field_in_btree(btree, source_field, target_field, lowercase);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let first = match nav.first() {
|
||||||
|
Some(f) => f.as_str(),
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let key_val = Value::from(first);
|
||||||
|
let arr_val = if nav.len() == 1 {
|
||||||
|
match btree.get_mut(&key_val) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut cur: &mut Value = match btree.get_mut(&key_val) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
for segment in nav.iter().skip(1) {
|
||||||
|
cur = match cur.as_object_mut() {
|
||||||
|
Ok(inner) => match inner.get_mut(&Value::from(segment.as_str())) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
},
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cur
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Value::Array(elements) = arr_val {
|
||||||
|
let inner = crate::Rc::make_mut(elements);
|
||||||
|
for elem in inner.iter_mut() {
|
||||||
|
if let Value::Object(obj_rc) = elem {
|
||||||
|
let inner_btree = crate::Rc::make_mut(obj_rc);
|
||||||
|
remap_at_depth_in_btree(
|
||||||
|
inner_btree,
|
||||||
|
array_chain,
|
||||||
|
depth.saturating_add(1),
|
||||||
|
source_field,
|
||||||
|
target_field,
|
||||||
|
lowercase,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
) {
|
||||||
|
let val = match read_dotted_path_btree(btree, source) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let segments: Vec<&str> = target.split('.').collect();
|
||||||
|
if segments.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if segments.len() == 1 {
|
||||||
|
if let Some(seg) = segments.first() {
|
||||||
|
btree.insert(Value::String(crate::Rc::from(*seg)), val);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set_nested_in_btree(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> {
|
||||||
|
let segments: Vec<&str> = path.split('.').collect();
|
||||||
|
let first = segments.first()?;
|
||||||
|
let mut cur: &Value = btree.get(&Value::from(*first))?;
|
||||||
|
for &seg in segments.iter().skip(1) {
|
||||||
|
cur = cur.as_object().ok()?.get(&Value::from(seg))?;
|
||||||
|
}
|
||||||
|
Some(cur.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remap a value from one (possibly nested) dot-separated path to another
|
||||||
|
/// in an ObjMap. Used only at the top level when `depth >= array_chain.len()`.
|
||||||
|
fn remap_deep_field(obj: &mut ObjMap, source: &str, target: &str, lowercase: bool) {
|
||||||
|
let val = match read_dotted_path(obj, source) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let segments: Vec<&str> = target.split('.').collect();
|
||||||
|
if segments.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if segments.len() == 1 {
|
||||||
|
if let Some(seg) = segments.first() {
|
||||||
|
obj_insert(obj, seg, val);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if lowercase {
|
||||||
|
set_nested_lowercased(obj, target, val);
|
||||||
|
} else {
|
||||||
|
set_nested_verbatim(obj, target, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a value at a dot-separated path from an ObjMap.
|
||||||
|
fn read_dotted_path(obj: &ObjMap, path: &str) -> Option<Value> {
|
||||||
|
let segments: Vec<&str> = path.split('.').collect();
|
||||||
|
let first = segments.first()?;
|
||||||
|
let mut cur: &Value = obj_get(obj, first)?;
|
||||||
|
for &seg in segments.iter().skip(1) {
|
||||||
|
cur = cur.as_object().ok()?.get(&Value::from(seg))?;
|
||||||
|
}
|
||||||
|
Some(cur.clone())
|
||||||
|
}
|
||||||
132
src/languages/azure_policy/aliases/normalizer/flatten.rs
Normal file
132
src/languages/azure_policy/aliases/normalizer/flatten.rs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! Value normalization helpers: recursive key lowercasing, sub-resource array
|
||||||
|
//! flattening, and element merging.
|
||||||
|
|
||||||
|
use alloc::collections::BTreeSet;
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
use super::super::obj_map::{make_array, make_value, new_map, obj_contains, obj_insert, val_str};
|
||||||
|
|
||||||
|
/// Lowercase all keys of a JSON object (shallow — values are untouched).
|
||||||
|
/// Non-object values are returned as-is.
|
||||||
|
pub fn lowercase_object_keys(value: &Value) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Object(obj) => {
|
||||||
|
let mut result = new_map();
|
||||||
|
for (k, v) in obj.iter() {
|
||||||
|
if let Some(s) = val_str(k) {
|
||||||
|
obj_insert(&mut result, &s.to_ascii_lowercase(), v.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
make_value(result)
|
||||||
|
}
|
||||||
|
_ => value.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively normalize a value, flattening sub-resource array elements.
|
||||||
|
pub fn normalize_value(
|
||||||
|
value: &Value,
|
||||||
|
field_path: &str,
|
||||||
|
sub_arrays: Option<&BTreeSet<String>>,
|
||||||
|
) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Array(arr) => {
|
||||||
|
let items: Vec<Value> = if is_sub_resource_array(field_path, sub_arrays) {
|
||||||
|
arr.iter()
|
||||||
|
.map(|elem| flatten_element(elem, field_path, sub_arrays))
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
arr.iter()
|
||||||
|
.map(|elem| normalize_value(elem, field_path, sub_arrays))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
make_array(items)
|
||||||
|
}
|
||||||
|
Value::Object(obj) => {
|
||||||
|
let mut result = new_map();
|
||||||
|
for (k, v) in obj.iter() {
|
||||||
|
let key_s = match val_str(k) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let child_path = alloc::format!("{}.{}", field_path, key_s);
|
||||||
|
obj_insert(
|
||||||
|
&mut result,
|
||||||
|
&key_s.to_ascii_lowercase(),
|
||||||
|
normalize_value(v, &child_path, sub_arrays),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
make_value(result)
|
||||||
|
}
|
||||||
|
_ => value.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flatten a sub-resource array element by merging its `properties` into
|
||||||
|
/// the element root.
|
||||||
|
pub fn flatten_element(
|
||||||
|
element: &Value,
|
||||||
|
array_path: &str,
|
||||||
|
sub_arrays: Option<&BTreeSet<String>>,
|
||||||
|
) -> Value {
|
||||||
|
let obj = match element.as_object() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => return element.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = new_map();
|
||||||
|
|
||||||
|
// Copy non-`properties` fields from the element envelope (keys lowercased).
|
||||||
|
for (key, val) in obj.iter() {
|
||||||
|
let key_s = match val_str(key) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
if key_s.eq_ignore_ascii_case("properties") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let child_path = alloc::format!("{}.{}", array_path, key_s);
|
||||||
|
obj_insert(
|
||||||
|
&mut result,
|
||||||
|
&key_s.to_ascii_lowercase(),
|
||||||
|
normalize_value(val, &child_path, sub_arrays),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge `properties` into the element (keys lowercased).
|
||||||
|
let props_val = obj
|
||||||
|
.iter()
|
||||||
|
.find(|(k, _)| val_str(k).is_some_and(|s| s.eq_ignore_ascii_case("properties")))
|
||||||
|
.map(|(_, v)| v);
|
||||||
|
if let Some(Value::Object(props)) = props_val {
|
||||||
|
for (key, val) in props.iter() {
|
||||||
|
let key_s = match val_str(key) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let lc_key = key_s.to_ascii_lowercase();
|
||||||
|
if obj_contains(&result, &lc_key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let child_path = alloc::format!("{}.{}", array_path, key_s);
|
||||||
|
obj_insert(
|
||||||
|
&mut result,
|
||||||
|
&lc_key,
|
||||||
|
normalize_value(val, &child_path, sub_arrays),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_value(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a field path corresponds to a sub-resource array.
|
||||||
|
fn is_sub_resource_array(field_path: &str, sub_arrays: Option<&BTreeSet<String>>) -> bool {
|
||||||
|
sub_arrays.is_some_and(|set| set.contains(&field_path.to_ascii_lowercase()))
|
||||||
|
}
|
||||||
157
src/languages/azure_policy/aliases/normalizer/mod.rs
Normal file
157
src/languages/azure_policy/aliases/normalizer/mod.rs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! ARM JSON → normalized `input.resource` transformation.
|
||||||
|
//!
|
||||||
|
//! The normalizer flattens `properties` wrappers from raw ARM resource JSON so
|
||||||
|
//! that alias short names become direct paths into the normalized structure.
|
||||||
|
|
||||||
|
mod alias_resolution;
|
||||||
|
mod element_remap;
|
||||||
|
mod flatten;
|
||||||
|
|
||||||
|
// Re-export items used by the denormalizer.
|
||||||
|
pub(crate) use element_remap::{apply_element_remap, ElementRemap};
|
||||||
|
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
use super::obj_map::{
|
||||||
|
extract_type_field, make_value, new_map, obj_contains, obj_insert, val_str, ObjMap, ROOT_FIELDS,
|
||||||
|
};
|
||||||
|
use super::types::ResolvedAliases;
|
||||||
|
use super::AliasRegistry;
|
||||||
|
|
||||||
|
use flatten::{lowercase_object_keys, normalize_value};
|
||||||
|
|
||||||
|
/// Normalize a raw ARM resource JSON value into the `input.resource` structure.
|
||||||
|
///
|
||||||
|
/// The resource type is extracted from the `type` field of `arm_resource` and
|
||||||
|
/// used to look up alias entries in the registry.
|
||||||
|
pub fn normalize(
|
||||||
|
arm_resource: &Value,
|
||||||
|
registry: Option<&AliasRegistry>,
|
||||||
|
api_version: Option<&str>,
|
||||||
|
) -> Value {
|
||||||
|
let aliases = registry.and_then(|r| extract_type_field(arm_resource).and_then(|rt| r.get(rt)));
|
||||||
|
normalize_with_aliases(arm_resource, aliases, api_version)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal normalization with pre-resolved alias data.
|
||||||
|
///
|
||||||
|
/// Core implementation used by [`normalize`] after looking up the alias
|
||||||
|
/// entries from the registry. Also used directly in unit tests.
|
||||||
|
pub fn normalize_with_aliases(
|
||||||
|
arm_resource: &Value,
|
||||||
|
aliases: Option<&ResolvedAliases>,
|
||||||
|
api_version: Option<&str>,
|
||||||
|
) -> Value {
|
||||||
|
let obj = match arm_resource.as_object() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(_) => return arm_resource.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sub_arrays_ref = aliases.map(|a| &a.sub_resource_arrays);
|
||||||
|
let mut result = new_map();
|
||||||
|
|
||||||
|
let is_data_plane =
|
||||||
|
extract_type_field(arm_resource).is_some_and(|t| t.to_ascii_lowercase().contains(".data/"));
|
||||||
|
|
||||||
|
if is_data_plane {
|
||||||
|
for (key, val) in obj {
|
||||||
|
let key_s = match val_str(key) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
if key_s.eq_ignore_ascii_case("properties") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let lc_key = key_s.to_ascii_lowercase();
|
||||||
|
let val =
|
||||||
|
if key_s.eq_ignore_ascii_case("tags") || key_s.eq_ignore_ascii_case("identity") {
|
||||||
|
lowercase_object_keys(val)
|
||||||
|
} else {
|
||||||
|
normalize_value(val, key_s, sub_arrays_ref)
|
||||||
|
};
|
||||||
|
obj_insert(&mut result, &lc_key, val);
|
||||||
|
}
|
||||||
|
merge_properties(obj, &mut result, sub_arrays_ref);
|
||||||
|
} else {
|
||||||
|
// Copy root-level fields (keys lowercased).
|
||||||
|
for &field in ROOT_FIELDS {
|
||||||
|
let found = obj
|
||||||
|
.iter()
|
||||||
|
.find(|(k, _)| val_str(k).is_some_and(|s| s.eq_ignore_ascii_case(field)))
|
||||||
|
.map(|(_, v)| v);
|
||||||
|
if let Some(val) = found {
|
||||||
|
let val = if field.eq_ignore_ascii_case("tags")
|
||||||
|
|| field.eq_ignore_ascii_case("identity")
|
||||||
|
{
|
||||||
|
// Keep these shallow to avoid lowercasing dynamic nested-map
|
||||||
|
// keys such as userAssignedIdentities member names.
|
||||||
|
lowercase_object_keys(val)
|
||||||
|
} else {
|
||||||
|
normalize_value(val, field, sub_arrays_ref)
|
||||||
|
};
|
||||||
|
obj_insert(&mut result, &field.to_ascii_lowercase(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
merge_properties(obj, &mut result, sub_arrays_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-alias path resolution.
|
||||||
|
if let Some(aliases) = aliases {
|
||||||
|
alias_resolution::apply_alias_entries(&mut result, arm_resource, aliases, api_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
make_value(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merge `properties` fields into the result map, skipping keys that already
|
||||||
|
/// exist.
|
||||||
|
fn merge_properties(
|
||||||
|
obj: &alloc::collections::BTreeMap<Value, Value>,
|
||||||
|
result: &mut ObjMap,
|
||||||
|
sub_arrays: Option<&alloc::collections::BTreeSet<alloc::string::String>>,
|
||||||
|
) {
|
||||||
|
let props_val = obj
|
||||||
|
.iter()
|
||||||
|
.find(|(k, _)| val_str(k).is_some_and(|s| s.eq_ignore_ascii_case("properties")))
|
||||||
|
.map(|(_, v)| v);
|
||||||
|
if let Some(Value::Object(props)) = props_val {
|
||||||
|
for (key, val) in props.iter() {
|
||||||
|
let key_s = match val_str(key) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let lc_key = key_s.to_ascii_lowercase();
|
||||||
|
if obj_contains(result, &lc_key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let normalized = normalize_value(val, key_s, sub_arrays);
|
||||||
|
obj_insert(result, &lc_key, normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrap a normalized resource into the full `input` envelope.
|
||||||
|
///
|
||||||
|
/// Produces: `{ "resource": <normalized>, "context": <context>, "parameters": <params> }`
|
||||||
|
pub fn build_input_envelope(
|
||||||
|
normalized_resource: Value,
|
||||||
|
context: Option<Value>,
|
||||||
|
parameters: Option<Value>,
|
||||||
|
) -> Value {
|
||||||
|
let mut envelope = new_map();
|
||||||
|
obj_insert(&mut envelope, "resource", normalized_resource);
|
||||||
|
obj_insert(
|
||||||
|
&mut envelope,
|
||||||
|
"context",
|
||||||
|
context.unwrap_or_else(|| make_value(new_map())),
|
||||||
|
);
|
||||||
|
obj_insert(
|
||||||
|
&mut envelope,
|
||||||
|
"parameters",
|
||||||
|
parameters.unwrap_or_else(|| make_value(new_map())),
|
||||||
|
);
|
||||||
|
make_value(envelope)
|
||||||
|
}
|
||||||
475
src/languages/azure_policy/aliases/obj_map.rs
Normal file
475
src/languages/azure_policy/aliases/obj_map.rs
Normal file
@@ -0,0 +1,475 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! 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
|
||||||
|
//! the output boundary via [`make_value`].
|
||||||
|
|
||||||
|
use alloc::string::{String, ToString as _};
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
|
use crate::Rc;
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
/// A string-keyed map of JSON values.
|
||||||
|
///
|
||||||
|
/// All normalizer / denormalizer code works with this type internally.
|
||||||
|
/// Convert to [`Value::Object`] via [`make_value`] when producing output.
|
||||||
|
pub type ObjMap = HashMap<Rc<str>, Value>;
|
||||||
|
|
||||||
|
/// Create an empty [`ObjMap`].
|
||||||
|
pub fn new_map() -> ObjMap {
|
||||||
|
ObjMap::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up a value by string key.
|
||||||
|
pub fn obj_get<'a>(map: &'a ObjMap, key: &str) -> Option<&'a Value> {
|
||||||
|
map.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up a mutable value reference by string key.
|
||||||
|
pub fn obj_get_mut<'a>(map: &'a mut ObjMap, key: &str) -> Option<&'a mut Value> {
|
||||||
|
map.get_mut(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a key-value pair.
|
||||||
|
pub fn obj_insert(map: &mut ObjMap, key: &str, val: Value) {
|
||||||
|
map.insert(Rc::from(key), val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a key exists.
|
||||||
|
pub fn obj_contains(map: &ObjMap, key: &str) -> bool {
|
||||||
|
map.contains_key(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a key, returning its value if present.
|
||||||
|
pub fn obj_remove(map: &mut ObjMap, key: &str) -> Option<Value> {
|
||||||
|
map.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a `Vec<Value>` into a `Value::Array`.
|
||||||
|
pub fn make_array(items: Vec<Value>) -> Value {
|
||||||
|
Value::Array(Rc::new(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a `&str` from a `Value::String`.
|
||||||
|
pub fn val_str(v: &Value) -> Option<&str> {
|
||||||
|
match v {
|
||||||
|
Value::String(s) => Some(s.as_ref()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the `type` field value from a resource JSON object.
|
||||||
|
///
|
||||||
|
/// Performs a case-insensitive key lookup so both `"type"` and `"Type"` work.
|
||||||
|
pub fn extract_type_field(resource: &Value) -> Option<&str> {
|
||||||
|
resource.as_object().ok().and_then(|obj| {
|
||||||
|
obj.iter()
|
||||||
|
.find(|(k, _)| val_str(k).is_some_and(|s| s.eq_ignore_ascii_case("type")))
|
||||||
|
.and_then(|(_, v)| val_str(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a `Value::Object` (BTreeMap<Value, Value>) 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() {
|
||||||
|
if let Value::String(s) = k {
|
||||||
|
map.insert(Rc::clone(s), v.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a value at a dot-separated path in an [`ObjMap`], creating
|
||||||
|
/// intermediate `Value::Object` nodes as needed. All keys are lowercased.
|
||||||
|
pub fn set_nested_lowercased(result: &mut ObjMap, path: &str, value: Value) {
|
||||||
|
let segments: Vec<&str> = path.split('.').collect();
|
||||||
|
if segments.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if segments.len() == 1 {
|
||||||
|
if let Some(&seg) = segments.first() {
|
||||||
|
obj_insert(result, &seg.to_ascii_lowercase(), value);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Build the nested structure from inside-out.
|
||||||
|
set_nested_inner(result, &segments, value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a value at a dot-separated path in an [`ObjMap`], creating
|
||||||
|
/// intermediate `Value::Object` nodes as needed. Keys preserve their casing.
|
||||||
|
pub fn set_nested_verbatim(result: &mut ObjMap, path: &str, value: Value) {
|
||||||
|
let segments: Vec<&str> = path.split('.').collect();
|
||||||
|
if segments.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if segments.len() == 1 {
|
||||||
|
if let Some(&seg) = segments.first() {
|
||||||
|
obj_insert(result, seg, value);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set_nested_inner(result, &segments, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Core implementation of nested-set. Navigates the first N-1 segments,
|
||||||
|
/// creating intermediate objects, then inserts the value at the last segment.
|
||||||
|
fn set_nested_inner(obj: &mut ObjMap, segments: &[&str], value: Value, lowercase: bool) {
|
||||||
|
let Some(&first) = segments.first() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if segments.len() == 1 {
|
||||||
|
let key = if lowercase {
|
||||||
|
first.to_ascii_lowercase()
|
||||||
|
} else {
|
||||||
|
first.to_string()
|
||||||
|
};
|
||||||
|
obj_insert(obj, &key, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let seg = if lowercase {
|
||||||
|
first.to_ascii_lowercase()
|
||||||
|
} else {
|
||||||
|
first.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure an intermediate object exists at `seg`.
|
||||||
|
if !obj_contains(obj, &seg) {
|
||||||
|
obj_insert(obj, &seg, make_value(new_map()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descend directly into the BTreeMap, avoiding ObjMap round-trip.
|
||||||
|
if let Some(Value::Object(inner_rc)) = obj_get_mut(obj, &seg) {
|
||||||
|
let inner_btree = Rc::make_mut(inner_rc);
|
||||||
|
set_nested_in_btree(
|
||||||
|
inner_btree,
|
||||||
|
segments.get(1..).unwrap_or_default(),
|
||||||
|
value,
|
||||||
|
lowercase,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a value at a path directly in a `BTreeMap<Value, Value>`, 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,
|
||||||
|
) {
|
||||||
|
let Some(&first) = segments.first() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let key_str: String = if lowercase {
|
||||||
|
first.to_ascii_lowercase()
|
||||||
|
} else {
|
||||||
|
first.to_string()
|
||||||
|
};
|
||||||
|
let key_val = Value::String(Rc::from(key_str.as_str()));
|
||||||
|
|
||||||
|
if segments.len() == 1 {
|
||||||
|
btree.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 let Some(Value::Object(inner_rc)) = btree.get_mut(&key_val) {
|
||||||
|
let inner = Rc::make_mut(inner_rc);
|
||||||
|
set_nested_in_btree(
|
||||||
|
inner,
|
||||||
|
segments.get(1..).unwrap_or_default(),
|
||||||
|
value,
|
||||||
|
lowercase,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fields that exist at the ARM resource root (not under `properties`).
|
||||||
|
///
|
||||||
|
/// These are the standard ARM resource envelope fields as defined by the
|
||||||
|
/// Azure Resource Manager resource model. They are preserved at the
|
||||||
|
/// resource root during normalization and denormalization.
|
||||||
|
pub const ROOT_FIELDS: &[&str] = &[
|
||||||
|
"name",
|
||||||
|
"type",
|
||||||
|
"location",
|
||||||
|
"kind",
|
||||||
|
"id",
|
||||||
|
"tags",
|
||||||
|
"identity",
|
||||||
|
"sku",
|
||||||
|
"plan",
|
||||||
|
"zones",
|
||||||
|
"managedBy",
|
||||||
|
"etag",
|
||||||
|
"apiVersion",
|
||||||
|
"fullName",
|
||||||
|
"systemData",
|
||||||
|
"extendedLocation",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Check whether an alias short name collides with a reserved ARM root field
|
||||||
|
/// and needs a collision-safe key.
|
||||||
|
pub fn is_root_field_collision(short_name: &str, default_path: &str) -> bool {
|
||||||
|
ROOT_FIELDS
|
||||||
|
.iter()
|
||||||
|
.any(|f| f.eq_ignore_ascii_case(short_name))
|
||||||
|
&& default_path.to_ascii_lowercase().starts_with("properties.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a collision-safe key for an alias whose short name collides with a
|
||||||
|
/// root ARM field. The key is `_p_` + the lowercased short name.
|
||||||
|
pub fn collision_safe_key(short_name: &str) -> String {
|
||||||
|
alloc::format!("_p_{}", short_name.to_ascii_lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Element-level field removal ────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Shared by both normalizer (stale source cleanup after remap) and
|
||||||
|
// denormalizer (cleanup after reverse remap).
|
||||||
|
|
||||||
|
/// Remove a (possibly dot-separated) field from each element of a (possibly
|
||||||
|
/// nested) array, navigating via the given `array_chain`.
|
||||||
|
pub fn remove_element_field(obj: &mut ObjMap, array_chain: &[Vec<String>], field: &str) {
|
||||||
|
remove_field_at_depth(obj, array_chain, 0, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_field_at_depth(obj: &mut ObjMap, array_chain: &[Vec<String>], depth: usize, field: &str) {
|
||||||
|
let Some(nav) = array_chain.get(depth) else {
|
||||||
|
let segments: Vec<&str> = field.split('.').collect();
|
||||||
|
if segments.len() == 1 {
|
||||||
|
if let Some(&seg) = segments.first() {
|
||||||
|
obj_remove(obj, seg);
|
||||||
|
}
|
||||||
|
} else if segments.len() > 1 {
|
||||||
|
remove_at_dotted_path(obj, &segments);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let first = match nav.first() {
|
||||||
|
Some(f) => f.as_str(),
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let arr_val = if nav.len() == 1 {
|
||||||
|
match obj_get_mut(obj, first) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut cur: &mut Value = match obj_get_mut(obj, first) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
for segment in nav.iter().skip(1) {
|
||||||
|
cur = match cur.as_object_mut() {
|
||||||
|
Ok(inner) => match inner.get_mut(&Value::from(segment.as_str())) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
},
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cur
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Value::Array(elements) = arr_val {
|
||||||
|
let inner = Rc::make_mut(elements);
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BTreeMap-native recursion for element-level field removal.
|
||||||
|
fn remove_field_at_depth_in_btree(
|
||||||
|
btree: &mut alloc::collections::BTreeMap<Value, Value>,
|
||||||
|
array_chain: &[Vec<String>],
|
||||||
|
depth: usize,
|
||||||
|
field: &str,
|
||||||
|
) {
|
||||||
|
let Some(nav) = array_chain.get(depth) else {
|
||||||
|
let segments: Vec<&str> = field.split('.').collect();
|
||||||
|
if segments.len() == 1 {
|
||||||
|
if let Some(&seg) = segments.first() {
|
||||||
|
btree.remove(&Value::from(seg));
|
||||||
|
}
|
||||||
|
} else if segments.len() > 1 {
|
||||||
|
remove_at_dotted_path_in_btree(btree, &segments);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let first = match nav.first() {
|
||||||
|
Some(f) => f.as_str(),
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let key_val = Value::from(first);
|
||||||
|
let arr_val = if nav.len() == 1 {
|
||||||
|
match btree.get_mut(&key_val) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut cur: &mut Value = match btree.get_mut(&key_val) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
for segment in nav.iter().skip(1) {
|
||||||
|
cur = match cur.as_object_mut() {
|
||||||
|
Ok(inner) => match inner.get_mut(&Value::from(segment.as_str())) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
},
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cur
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Value::Array(elements) = arr_val {
|
||||||
|
let inner = Rc::make_mut(elements);
|
||||||
|
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 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],
|
||||||
|
) {
|
||||||
|
let Some((&leaf, parent_segs)) = segments.split_last() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if parent_segs.is_empty() {
|
||||||
|
btree.remove(&Value::from(leaf));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(&first) = parent_segs.first() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let first_key = Value::from(first);
|
||||||
|
let parent_val = match btree.get_mut(&first_key) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
if parent_segs.len() == 1 {
|
||||||
|
if let Value::Object(inner_rc) = parent_val {
|
||||||
|
let inner_btree = Rc::make_mut(inner_rc);
|
||||||
|
inner_btree.remove(&Value::from(leaf));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut cur = parent_val;
|
||||||
|
for &seg in parent_segs.iter().skip(1) {
|
||||||
|
cur = match cur.as_object_mut() {
|
||||||
|
Ok(inner) => match inner.get_mut(&Value::from(seg)) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
},
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if let Value::Object(inner_rc) = cur {
|
||||||
|
let inner_btree = Rc::make_mut(inner_rc);
|
||||||
|
inner_btree.remove(&Value::from(leaf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the leaf segment at a dot-separated path from an ObjMap.
|
||||||
|
fn remove_at_dotted_path(obj: &mut ObjMap, segments: &[&str]) {
|
||||||
|
let Some((&leaf, parent_segs)) = segments.split_last() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if parent_segs.is_empty() {
|
||||||
|
obj_remove(obj, leaf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(&first) = parent_segs.first() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let parent_val = match obj_get_mut(obj, first) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
if parent_segs.len() == 1 {
|
||||||
|
if let Value::Object(inner_rc) = parent_val {
|
||||||
|
let inner_btree = Rc::make_mut(inner_rc);
|
||||||
|
inner_btree.remove(&Value::from(leaf));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut cur = parent_val;
|
||||||
|
for &seg in parent_segs.iter().skip(1) {
|
||||||
|
cur = match cur.as_object_mut() {
|
||||||
|
Ok(inner) => match inner.get_mut(&Value::from(seg)) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return,
|
||||||
|
},
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if let Value::Object(inner_rc) = cur {
|
||||||
|
let inner_btree = Rc::make_mut(inner_rc);
|
||||||
|
inner_btree.remove(&Value::from(leaf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
686
src/languages/azure_policy/aliases/types.rs
Normal file
686
src/languages/azure_policy/aliases/types.rs
Normal file
@@ -0,0 +1,686 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! Data types for Azure Policy alias definitions.
|
||||||
|
//!
|
||||||
|
//! These types deserialize production alias catalog data from multiple sources:
|
||||||
|
//! 1. ARM API response: `GET /providers?$expand=resourceTypes/aliases`
|
||||||
|
//! 2. Static `ResourceTypesAndAliases.json` (used by PolicyTester)
|
||||||
|
//! 3. `az provider list --expand resourceTypes/aliases` CLI output
|
||||||
|
//! (where `defaultPath` may be serialized as `{ path, apiVersions }`)
|
||||||
|
//!
|
||||||
|
//! All data is captured for completeness; fields not yet used by the compiler
|
||||||
|
//! are retained so the types stay in sync with the production schema.
|
||||||
|
|
||||||
|
use alloc::collections::{BTreeMap, BTreeSet};
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
|
// ─── Top-level response wrappers ────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// ARM API response envelope: `{ "value": [...] }`
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct ArmProvidersResponse {
|
||||||
|
pub value: Vec<ProviderAliases>,
|
||||||
|
/// Pagination link (ARM may paginate large responses).
|
||||||
|
#[serde(rename = "nextLink", default)]
|
||||||
|
pub next_link: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Provider / resource type ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A resource provider's alias definitions.
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct ProviderAliases {
|
||||||
|
/// The provider namespace (e.g., `"Microsoft.Storage"`).
|
||||||
|
pub namespace: String,
|
||||||
|
|
||||||
|
/// Resource types with their alias entries.
|
||||||
|
#[serde(default, rename = "resourceTypes")]
|
||||||
|
pub resource_types: Vec<ResourceTypeAliases>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Aliases for a single resource type within a provider.
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ResourceTypeAliases {
|
||||||
|
/// The resource type name (e.g., `"storageAccounts"`).
|
||||||
|
pub resource_type: String,
|
||||||
|
|
||||||
|
/// All alias entries for this resource type.
|
||||||
|
#[serde(default)]
|
||||||
|
pub aliases: Vec<AliasEntry>,
|
||||||
|
|
||||||
|
/// Resource capabilities as a comma-separated string
|
||||||
|
/// (e.g., `"SupportsTags, SupportsLocation"`).
|
||||||
|
#[serde(default)]
|
||||||
|
pub capabilities: Option<String>,
|
||||||
|
|
||||||
|
/// Default API version for the resource type.
|
||||||
|
#[serde(default)]
|
||||||
|
pub default_api_version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Alias ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A single alias entry within a resource type.
|
||||||
|
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AliasEntry {
|
||||||
|
/// Fully qualified alias name
|
||||||
|
/// (e.g., `"Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"`).
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// The default ARM JSON path used when no versioned path matches.
|
||||||
|
///
|
||||||
|
/// In most formats this is a plain string. The `az CLI` format serializes
|
||||||
|
/// it as `{ "path": "...", "apiVersions": [...] }`. The custom
|
||||||
|
/// deserializer accepts both, extracting just the path string.
|
||||||
|
#[serde(default, deserialize_with = "deserialize_default_path")]
|
||||||
|
pub default_path: Option<String>,
|
||||||
|
|
||||||
|
/// Optional metadata for the default path (type, modifiability).
|
||||||
|
#[serde(default)]
|
||||||
|
pub default_metadata: Option<AliasPathMetadata>,
|
||||||
|
|
||||||
|
/// Extraction pattern for the default path (present in ARM responses for
|
||||||
|
/// some aliases; absent from the static file).
|
||||||
|
#[serde(default)]
|
||||||
|
pub default_pattern: Option<AliasPattern>,
|
||||||
|
|
||||||
|
/// Alias-level type as a comma-separated string of flags:
|
||||||
|
/// `"PlainText"`, `"Mask"`, `"Deprecated"`, `"Preview"`, or combinations
|
||||||
|
/// like `"Mask, Deprecated"`. `None` when absent.
|
||||||
|
#[serde(default, rename = "type")]
|
||||||
|
pub alias_type: Option<String>,
|
||||||
|
|
||||||
|
/// Versioned path entries. Empty for the vast majority of aliases that
|
||||||
|
/// have only a `defaultPath`.
|
||||||
|
#[serde(default)]
|
||||||
|
pub paths: Vec<AliasPath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Alias path ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A versioned path mapping for an alias.
|
||||||
|
///
|
||||||
|
/// When an alias maps to different ARM JSON paths across API versions, each
|
||||||
|
/// distinct path is recorded as an `AliasPath` entry.
|
||||||
|
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AliasPath {
|
||||||
|
/// The ARM JSON path (e.g., `"properties.encryption.services.blob.enabled"`).
|
||||||
|
pub path: String,
|
||||||
|
|
||||||
|
/// API versions for which this path is valid. Empty means all versions.
|
||||||
|
#[serde(default)]
|
||||||
|
pub api_versions: Vec<String>,
|
||||||
|
|
||||||
|
/// Optional per-version metadata.
|
||||||
|
#[serde(default)]
|
||||||
|
pub metadata: Option<AliasPathMetadata>,
|
||||||
|
|
||||||
|
/// Extraction pattern for this specific path.
|
||||||
|
#[serde(default)]
|
||||||
|
pub pattern: Option<AliasPattern>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Alias path metadata ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Metadata associated with an alias path (default or versioned).
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct AliasPathMetadata {
|
||||||
|
/// The data type of the alias value as a string token
|
||||||
|
/// (e.g., `"String"`, `"Integer"`, `"Boolean"`, `"Array"`, `"Object"`).
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: Option<String>,
|
||||||
|
|
||||||
|
/// Attribute flags as a comma-separated string
|
||||||
|
/// (e.g., `"Modifiable"`, `"Modifiable, SupportsCreate, SupportsRead"`).
|
||||||
|
pub attributes: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Alias pattern ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Extraction pattern for an alias path (URI template or regex).
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AliasPattern {
|
||||||
|
/// The pattern phrase (URI template or regex string).
|
||||||
|
pub phrase: String,
|
||||||
|
|
||||||
|
/// The variable to extract from the pattern.
|
||||||
|
#[serde(default)]
|
||||||
|
pub variable: Option<String>,
|
||||||
|
|
||||||
|
/// Pattern type (e.g., `"Extract"`).
|
||||||
|
#[serde(default, rename = "type")]
|
||||||
|
pub pattern_type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Custom deserializer: defaultPath (string or object) ────────────────────
|
||||||
|
|
||||||
|
/// Accepts either a plain string `"properties.foo"` or an az CLI object
|
||||||
|
/// `{ "path": "properties.foo", "apiVersions": [...] }`, extracting just the
|
||||||
|
/// path string. Handles `null` gracefully.
|
||||||
|
fn deserialize_default_path<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum RawDefaultPath {
|
||||||
|
Str(String),
|
||||||
|
Obj {
|
||||||
|
path: String,
|
||||||
|
#[serde(default, rename = "apiVersions")]
|
||||||
|
_api_versions: Vec<String>,
|
||||||
|
},
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
|
match Option::<RawDefaultPath>::deserialize(deserializer)? {
|
||||||
|
None | Some(RawDefaultPath::Null) => Ok(None),
|
||||||
|
Some(RawDefaultPath::Str(s)) => Ok(Some(s)),
|
||||||
|
Some(RawDefaultPath::Obj { path, .. }) => Ok(Some(path)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Data Policy Manifest types (data-plane aliases) ────────────────────────
|
||||||
|
|
||||||
|
/// A single alias entry in a data policy manifest.
|
||||||
|
///
|
||||||
|
/// Unlike control-plane [`AliasEntry`], data-plane aliases have no
|
||||||
|
/// `defaultPath` — the path is always taken from `paths[0].path`.
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct DataManifestAlias {
|
||||||
|
/// Fully qualified alias name
|
||||||
|
/// (e.g., `"Microsoft.KeyVault.Data/vaults/certificates/attributes.expiresOn"`).
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// Versioned path entries. `paths[0].path` serves as the default path.
|
||||||
|
#[serde(default)]
|
||||||
|
pub paths: Vec<DataManifestAliasPath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A path entry in a data policy manifest alias.
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DataManifestAliasPath {
|
||||||
|
/// The ARM JSON path (e.g., `"attributes.expiresOn"`).
|
||||||
|
pub path: String,
|
||||||
|
|
||||||
|
/// API versions for which this path is valid.
|
||||||
|
#[serde(default)]
|
||||||
|
pub api_versions: Vec<String>,
|
||||||
|
|
||||||
|
/// Schema versions for which this path is valid (used by some data-plane
|
||||||
|
/// providers instead of `apiVersions`).
|
||||||
|
#[serde(default)]
|
||||||
|
pub schema_versions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-resource-type alias group in a data policy manifest.
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DataManifestResourceTypeAliases {
|
||||||
|
/// Resource type suffix (e.g., `"vaults/certificates"`).
|
||||||
|
pub resource_type: String,
|
||||||
|
|
||||||
|
/// Aliases for this resource type.
|
||||||
|
#[serde(default)]
|
||||||
|
pub aliases: Vec<DataManifestAlias>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A data policy manifest describing data-plane aliases for a namespace.
|
||||||
|
///
|
||||||
|
/// This format is used by `dataPolicyManifests/` files, as opposed to the
|
||||||
|
/// control-plane `ProviderAliases` format used by `Get-AzPolicyAlias`.
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DataPolicyManifest {
|
||||||
|
/// The data namespace (e.g., `"Microsoft.KeyVault.Data"`).
|
||||||
|
pub data_namespace: String,
|
||||||
|
|
||||||
|
/// Top-level aliases not scoped to a specific resource type.
|
||||||
|
#[serde(default)]
|
||||||
|
pub aliases: Vec<DataManifestAlias>,
|
||||||
|
|
||||||
|
/// Per-resource-type alias groups.
|
||||||
|
#[serde(default)]
|
||||||
|
pub resource_type_aliases: Vec<DataManifestResourceTypeAliases>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Convenience loading functions ──────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Load from the static `ResourceTypesAndAliases.json` file (bare array).
|
||||||
|
pub fn load_from_static_file(json: &str) -> Result<Vec<ProviderAliases>, serde_json::Error> {
|
||||||
|
serde_json::from_str(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load from an ARM API `GET /providers` response (`{ "value": [...] }`).
|
||||||
|
pub fn load_from_arm_response(json: &str) -> Result<Vec<ProviderAliases>, serde_json::Error> {
|
||||||
|
let resp: ArmProvidersResponse = serde_json::from_str(json)?;
|
||||||
|
Ok(resp.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load from either format: tries ARM envelope first, then bare array.
|
||||||
|
pub fn load_auto(json: &str) -> Result<Vec<ProviderAliases>, serde_json::Error> {
|
||||||
|
let trimmed = json.trim_start();
|
||||||
|
if trimmed.starts_with('[') {
|
||||||
|
load_from_static_file(json)
|
||||||
|
} else {
|
||||||
|
load_from_arm_response(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Utility methods ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
impl AliasEntry {
|
||||||
|
/// Returns `true` if this alias is marked as deprecated (case-insensitive).
|
||||||
|
pub fn is_deprecated(&self) -> bool {
|
||||||
|
has_flag(self.alias_type.as_deref(), "Deprecated")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this alias is marked as preview.
|
||||||
|
pub fn is_preview(&self) -> bool {
|
||||||
|
has_flag(self.alias_type.as_deref(), "Preview")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this alias's value should be masked (secret).
|
||||||
|
pub fn is_secret(&self) -> bool {
|
||||||
|
has_flag(self.alias_type.as_deref(), "Mask")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the effective path string (defaultPath or first versioned path).
|
||||||
|
pub fn effective_path(&self) -> Option<&str> {
|
||||||
|
self.default_path
|
||||||
|
.as_deref()
|
||||||
|
.or_else(|| self.paths.first().map(|p| p.path.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AliasPathMetadata {
|
||||||
|
/// Returns `true` if this path supports modification (Modifiable flag).
|
||||||
|
pub fn is_modifiable(&self) -> bool {
|
||||||
|
has_flag(self.attributes.as_deref(), "Modifiable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a comma-separated flags string contains a specific flag
|
||||||
|
/// (case-insensitive).
|
||||||
|
pub(crate) fn has_flag(flags: Option<&str>, flag: &str) -> bool {
|
||||||
|
flags.is_some_and(|s| {
|
||||||
|
s.split(',')
|
||||||
|
.any(|part| part.trim().eq_ignore_ascii_case(flag))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parsed alias data for a single resource type, keyed by short name.
|
||||||
|
///
|
||||||
|
/// The short name is derived by stripping the resource type prefix from the
|
||||||
|
/// fully qualified alias name:
|
||||||
|
/// `Microsoft.Storage/storageAccounts/sku.name` → `sku.name`
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ResolvedAliases {
|
||||||
|
/// Fully qualified resource type (e.g., `"Microsoft.Storage/storageAccounts"`).
|
||||||
|
pub resource_type: String,
|
||||||
|
/// Map from alias short name (case-insensitive key, stored lowercase) to
|
||||||
|
/// the resolved ARM path.
|
||||||
|
pub entries: BTreeMap<String, ResolvedEntry>,
|
||||||
|
/// Array field names whose elements are sub-resources (have their own
|
||||||
|
/// `properties` wrapper to flatten). Stored pre-lowercased so consumers
|
||||||
|
/// can look up directly without per-call allocation.
|
||||||
|
pub sub_resource_arrays: BTreeSet<String>,
|
||||||
|
|
||||||
|
// ── Precomputed aggregate fields ────────────────────────────────────
|
||||||
|
/// Precomputed aggregates for the default path (api_version = None).
|
||||||
|
pub default_aggregates: VersionedAggregates,
|
||||||
|
/// Precomputed aggregates keyed by lowercase api_version string.
|
||||||
|
/// Computed at registry-load time for every distinct version found in
|
||||||
|
/// any entry's `versioned_paths`.
|
||||||
|
pub versioned_aggregates: BTreeMap<String, VersionedAggregates>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Precomputed aggregate data for a specific API version, or for the default path.
|
||||||
|
///
|
||||||
|
/// Stored at [`ResolvedAliases`] level so it is computed once at registry-load
|
||||||
|
/// time rather than reconstructed on every normalize/denormalize call.
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
|
pub struct VersionedAggregates {
|
||||||
|
/// Precomputed element-level field remaps for wildcard aliases.
|
||||||
|
pub element_remaps: Vec<PrecomputedRemap>,
|
||||||
|
/// Precomputed reverse element remaps for denormalization.
|
||||||
|
pub reverse_element_remaps: Vec<PrecomputedReverseRemap>,
|
||||||
|
/// Deduplicated array base renames for normalization: `(arm_base_lc, short_base_lc)`.
|
||||||
|
pub array_renames_normalize: Vec<(String, String)>,
|
||||||
|
/// Deduplicated array base renames for denormalization: `(short_base_lc, arm_base)`.
|
||||||
|
pub array_renames_denormalize: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A precomputed element-level field remap, stored at `ResolvedAliases` level
|
||||||
|
/// so it's computed once at registry-load time rather than per-call.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct PrecomputedRemap {
|
||||||
|
/// Chain of array navigations for nested `[*]` levels.
|
||||||
|
pub array_chain: Vec<Vec<String>>,
|
||||||
|
/// Field to read within each element.
|
||||||
|
pub source_field: String,
|
||||||
|
/// Field to write within each element.
|
||||||
|
pub target_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A precomputed reverse remap for denormalization, including the forward
|
||||||
|
/// target field name for cleanup after remapping.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct PrecomputedReverseRemap {
|
||||||
|
/// Chain of array navigations for nested `[*]` levels.
|
||||||
|
pub array_chain: Vec<Vec<String>>,
|
||||||
|
/// Field to read within each element (was the forward target).
|
||||||
|
pub source_field: String,
|
||||||
|
/// Field to write within each element (was the forward source).
|
||||||
|
pub target_field: String,
|
||||||
|
/// The forward target field to remove after remapping.
|
||||||
|
pub cleanup_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A resolved alias entry with its default path and optional versioned paths.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ResolvedEntry {
|
||||||
|
/// The original-cased alias short name (e.g., `"accountType"`, not
|
||||||
|
/// `"accounttype"`). The entries map uses lowercase keys for
|
||||||
|
/// case-insensitive lookup, but the normalizer needs the original casing
|
||||||
|
/// to write values at correctly-cased paths in the output.
|
||||||
|
pub short_name: String,
|
||||||
|
/// The default ARM JSON path.
|
||||||
|
pub default_path: String,
|
||||||
|
/// Versioned path overrides: `(api_version, arm_path)` pairs.
|
||||||
|
pub versioned_paths: Vec<(String, String)>,
|
||||||
|
/// Optional metadata from the alias catalog (type, modifiability).
|
||||||
|
pub metadata: Option<AliasPathMetadata>,
|
||||||
|
|
||||||
|
// ── Precomputed fields (derived at registry-load time) ──────────────
|
||||||
|
/// Whether `short_name` contains `[*]` (i.e., this is a wildcard/array alias).
|
||||||
|
pub is_wildcard: bool,
|
||||||
|
/// Precomputed `default_path.split('.').collect()` for fast ARM path navigation.
|
||||||
|
pub default_path_segments: Vec<String>,
|
||||||
|
/// Precomputed path segments for each versioned path, in the same order
|
||||||
|
/// as `versioned_paths`.
|
||||||
|
pub versioned_path_segments: Vec<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolvedEntry {
|
||||||
|
/// Build a `ResolvedEntry` and precompute derived fields.
|
||||||
|
pub fn new(
|
||||||
|
short_name: String,
|
||||||
|
default_path: String,
|
||||||
|
versioned_paths: Vec<(String, String)>,
|
||||||
|
metadata: Option<AliasPathMetadata>,
|
||||||
|
) -> Self {
|
||||||
|
let is_wildcard = short_name.contains("[*]");
|
||||||
|
let default_path_segments = default_path.split('.').map(String::from).collect();
|
||||||
|
let versioned_path_segments = versioned_paths
|
||||||
|
.iter()
|
||||||
|
.map(|(_, p)| p.split('.').map(String::from).collect())
|
||||||
|
.collect();
|
||||||
|
Self {
|
||||||
|
short_name,
|
||||||
|
default_path,
|
||||||
|
versioned_paths,
|
||||||
|
metadata,
|
||||||
|
is_wildcard,
|
||||||
|
default_path_segments,
|
||||||
|
versioned_path_segments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Select the ARM path for a given API version.
|
||||||
|
///
|
||||||
|
/// If `api_version` is `Some` and matches a versioned path, returns that
|
||||||
|
/// path. Otherwise returns the `default_path`.
|
||||||
|
pub fn select_path(&self, api_version: Option<&str>) -> &str {
|
||||||
|
if let Some(ver) = api_version {
|
||||||
|
for (v, path) in &self.versioned_paths {
|
||||||
|
if v.eq_ignore_ascii_case(ver) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&self.default_path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Select pre-tokenized path segments for a given API version.
|
||||||
|
///
|
||||||
|
/// Returns the versioned segments if `api_version` matches, otherwise
|
||||||
|
/// the default segments. This avoids per-call `split('.')` for both
|
||||||
|
/// default and versioned scalar alias navigation.
|
||||||
|
pub fn select_path_segments(&self, api_version: Option<&str>) -> &[String] {
|
||||||
|
if let Some(ver) = api_version {
|
||||||
|
for (i, (v, _)) in self.versioned_paths.iter().enumerate() {
|
||||||
|
if v.eq_ignore_ascii_case(ver) {
|
||||||
|
if let Some(segs) = self.versioned_path_segments.get(i) {
|
||||||
|
return segs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&self.default_path_segments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
|
||||||
|
mod tests {
|
||||||
|
use alloc::string::ToString as _;
|
||||||
|
use alloc::vec;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn make_entry(short: &str, default: &str, versioned: Vec<(&str, &str)>) -> ResolvedEntry {
|
||||||
|
ResolvedEntry::new(
|
||||||
|
short.to_string(),
|
||||||
|
default.to_string(),
|
||||||
|
versioned
|
||||||
|
.into_iter()
|
||||||
|
.map(|(v, p)| (v.to_string(), p.to_string()))
|
||||||
|
.collect(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_path_no_version_returns_default() {
|
||||||
|
let entry = make_entry(
|
||||||
|
"enabled",
|
||||||
|
"properties.enabled",
|
||||||
|
vec![("2020-01-01", "properties.isEnabled")],
|
||||||
|
);
|
||||||
|
assert_eq!(entry.select_path(None), "properties.enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_path_matching_version() {
|
||||||
|
let entry = make_entry(
|
||||||
|
"enabled",
|
||||||
|
"properties.enabled",
|
||||||
|
vec![
|
||||||
|
("2020-01-01", "properties.isEnabled"),
|
||||||
|
("2021-06-01", "properties.enabled"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
entry.select_path(Some("2020-01-01")),
|
||||||
|
"properties.isEnabled"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_path_no_matching_version_returns_default() {
|
||||||
|
let entry = make_entry(
|
||||||
|
"enabled",
|
||||||
|
"properties.enabled",
|
||||||
|
vec![("2020-01-01", "properties.isEnabled")],
|
||||||
|
);
|
||||||
|
assert_eq!(entry.select_path(Some("9999-01-01")), "properties.enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_path_case_insensitive_version() {
|
||||||
|
let entry = make_entry(
|
||||||
|
"enabled",
|
||||||
|
"properties.enabled",
|
||||||
|
vec![("2020-01-01-Preview", "properties.isEnabled")],
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
entry.select_path(Some("2020-01-01-preview")),
|
||||||
|
"properties.isEnabled"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select_path_empty_versioned_paths() {
|
||||||
|
let entry = make_entry("enabled", "properties.enabled", vec![]);
|
||||||
|
assert_eq!(entry.select_path(Some("2020-01-01")), "properties.enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_provider_aliases() {
|
||||||
|
let json = r#"{
|
||||||
|
"namespace": "Microsoft.Storage",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "storageAccounts",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/sku.name",
|
||||||
|
"defaultPath": "sku.name",
|
||||||
|
"defaultMetadata": { "type": "String", "attributes": "Modifiable" },
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/accessTier",
|
||||||
|
"defaultPath": "properties.accessTier",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.accessTier",
|
||||||
|
"apiVersions": ["2021-01-01", "2020-08-01-preview"],
|
||||||
|
"metadata": { "type": "String" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let provider: ProviderAliases = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(provider.namespace, "Microsoft.Storage");
|
||||||
|
assert_eq!(provider.resource_types.len(), 1);
|
||||||
|
|
||||||
|
let rt = &provider.resource_types[0];
|
||||||
|
assert_eq!(rt.resource_type, "storageAccounts");
|
||||||
|
assert_eq!(rt.aliases.len(), 2);
|
||||||
|
|
||||||
|
let sku_alias = &rt.aliases[0];
|
||||||
|
assert_eq!(sku_alias.default_path.as_deref(), Some("sku.name"));
|
||||||
|
assert!(sku_alias.paths.is_empty());
|
||||||
|
|
||||||
|
let access_alias = &rt.aliases[1];
|
||||||
|
assert_eq!(access_alias.paths.len(), 1);
|
||||||
|
assert_eq!(access_alias.paths[0].api_versions.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_alias_metadata() {
|
||||||
|
let json = r#"{
|
||||||
|
"name": "test/alias",
|
||||||
|
"defaultPath": "properties.value",
|
||||||
|
"defaultMetadata": { "type": "Integer", "attributes": "None" },
|
||||||
|
"paths": []
|
||||||
|
}"#;
|
||||||
|
let entry: AliasEntry = serde_json::from_str(json).unwrap();
|
||||||
|
let meta = entry.default_metadata.unwrap();
|
||||||
|
assert_eq!(meta.kind.as_deref(), Some("Integer"));
|
||||||
|
assert_eq!(meta.attributes.as_deref(), Some("None"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_az_cli_default_path_object() {
|
||||||
|
let json = r#"{
|
||||||
|
"name": "Microsoft.Compute/virtualMachines/sku.name",
|
||||||
|
"defaultPath": {
|
||||||
|
"path": "properties.hardwareProfile.vmSize",
|
||||||
|
"apiVersions": ["2024-07-01"]
|
||||||
|
},
|
||||||
|
"paths": []
|
||||||
|
}"#;
|
||||||
|
let entry: AliasEntry = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
entry.default_path.as_deref(),
|
||||||
|
Some("properties.hardwareProfile.vmSize")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_type_flags() {
|
||||||
|
let json = r#"{
|
||||||
|
"name": "test",
|
||||||
|
"type": "Mask, Deprecated",
|
||||||
|
"defaultMetadata": { "type": "String", "attributes": "Modifiable, SupportsCreate" },
|
||||||
|
"paths": []
|
||||||
|
}"#;
|
||||||
|
let entry: AliasEntry = serde_json::from_str(json).unwrap();
|
||||||
|
assert!(entry.is_secret());
|
||||||
|
assert!(entry.is_deprecated());
|
||||||
|
assert!(!entry.is_preview());
|
||||||
|
assert!(entry.default_metadata.as_ref().unwrap().is_modifiable());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_auto_detects_format() {
|
||||||
|
let array_json = r#"[{"namespace":"N","resourceTypes":[]}]"#;
|
||||||
|
let arm_json = r#"{"value":[{"namespace":"N","resourceTypes":[]}]}"#;
|
||||||
|
assert_eq!(load_auto(array_json).unwrap()[0].namespace, "N");
|
||||||
|
assert_eq!(load_auto(arm_json).unwrap()[0].namespace, "N");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_pattern() {
|
||||||
|
let json = r#"{
|
||||||
|
"name": "test",
|
||||||
|
"defaultPath": "p",
|
||||||
|
"paths": [{
|
||||||
|
"path": "p",
|
||||||
|
"apiVersions": ["2020-01-01"],
|
||||||
|
"pattern": {
|
||||||
|
"phrase": "/Subscriptions/{sub}/Providers/{prov}",
|
||||||
|
"variable": "prov",
|
||||||
|
"type": "Extract"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}"#;
|
||||||
|
let entry: AliasEntry = serde_json::from_str(json).unwrap();
|
||||||
|
let pattern = entry.paths[0].pattern.as_ref().unwrap();
|
||||||
|
assert_eq!(pattern.pattern_type.as_deref(), Some("Extract"));
|
||||||
|
assert_eq!(pattern.variable.as_deref(), Some("prov"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn capabilities_preserved() {
|
||||||
|
let json = r#"{
|
||||||
|
"namespace": "NS",
|
||||||
|
"resourceTypes": [{
|
||||||
|
"resourceType": "rt",
|
||||||
|
"capabilities": "SupportsTags, SupportsLocation",
|
||||||
|
"aliases": []
|
||||||
|
}]
|
||||||
|
}"#;
|
||||||
|
let provider: ProviderAliases = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
provider.resource_types[0].capabilities.as_deref(),
|
||||||
|
Some("SupportsTags, SupportsLocation")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,6 @@
|
|||||||
|
|
||||||
//! Azure Policy language support.
|
//! Azure Policy language support.
|
||||||
|
|
||||||
|
#[allow(clippy::pattern_type_mismatch)]
|
||||||
|
pub mod aliases;
|
||||||
pub mod strings;
|
pub mod strings;
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
|
||||||
// Licensed under the MIT License.
|
|
||||||
|
|
||||||
//! Language-specific modules for specialized parsing and evaluation
|
|
||||||
|
|
||||||
#[cfg(feature = "azure-rbac")]
|
|
||||||
pub mod azure_rbac;
|
|
||||||
2900
tests/azure_policy/aliases/test_aliases.json
Normal file
2900
tests/azure_policy/aliases/test_aliases.json
Normal file
File diff suppressed because it is too large
Load Diff
230
tests/azure_policy/aliases/versioned_aliases.json
Normal file
230
tests/azure_policy/aliases/versioned_aliases.json
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Test",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "versionedResources",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/versionedResources/simpleProp",
|
||||||
|
"defaultPath": "properties.simpleProp",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/versionedResources/accountType",
|
||||||
|
"defaultPath": "properties.accountType",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.accountType",
|
||||||
|
"apiVersions": ["2015-06-15", "2015-01-01"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "sku.name",
|
||||||
|
"apiVersions": ["2020-01-01", "2019-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/versionedResources/config.threshold",
|
||||||
|
"defaultPath": "properties.config.threshold",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.config.properties.threshold",
|
||||||
|
"apiVersions": ["2015-06-15", "2015-01-01"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "properties.config.threshold",
|
||||||
|
"apiVersions": ["2020-01-01", "2019-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/versionedResources/tier",
|
||||||
|
"defaultPath": "sku.tier",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.pricingTier",
|
||||||
|
"apiVersions": ["2015-06-15", "2015-01-01"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "sku.tier",
|
||||||
|
"apiVersions": ["2020-01-01", "2019-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/versionedResources/operationMode",
|
||||||
|
"defaultPath": "properties.mode",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.legacyMode",
|
||||||
|
"apiVersions": ["2015-01-01"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "properties.settings.mode",
|
||||||
|
"apiVersions": ["2017-06-01"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "properties.mode",
|
||||||
|
"apiVersions": ["2020-01-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceType": "complexResources",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/enabled",
|
||||||
|
"defaultPath": "properties.enabled",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/tags",
|
||||||
|
"defaultPath": "tags",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules",
|
||||||
|
"defaultPath": "properties.rules",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*]",
|
||||||
|
"defaultPath": "properties.rules[*]",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].name",
|
||||||
|
"defaultPath": "properties.rules[*].name",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].priority",
|
||||||
|
"defaultPath": "properties.rules[*].properties.priority",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.rules[*].properties.priority",
|
||||||
|
"apiVersions": ["2015-01-01"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "properties.rules[*].properties.prio",
|
||||||
|
"apiVersions": ["2020-01-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].action",
|
||||||
|
"defaultPath": "properties.rules[*].properties.action",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].protocol",
|
||||||
|
"defaultPath": "properties.rules[*].properties.protocol",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].sourcePort",
|
||||||
|
"defaultPath": "properties.rules[*].properties.sourcePort",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].destPort",
|
||||||
|
"defaultPath": "properties.rules[*].properties.destPort",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].direction",
|
||||||
|
"defaultPath": "properties.rules[*].properties.direction",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].targets",
|
||||||
|
"defaultPath": "properties.rules[*].properties.targets",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].targets[*]",
|
||||||
|
"defaultPath": "properties.rules[*].properties.targets[*]",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].filters",
|
||||||
|
"defaultPath": "properties.rules[*].properties.filters",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].filters[*]",
|
||||||
|
"defaultPath": "properties.rules[*].properties.filters[*]",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].filters[*].name",
|
||||||
|
"defaultPath": "properties.rules[*].properties.filters[*].properties.name",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/rules[*].filters[*].value",
|
||||||
|
"defaultPath": "properties.rules[*].properties.filters[*].properties.value",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/allowedIPs",
|
||||||
|
"defaultPath": "properties.allowedIPs",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/allowedIPs[*]",
|
||||||
|
"defaultPath": "properties.allowedIPs[*]",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/config.retryCount",
|
||||||
|
"defaultPath": "properties.config.retryCount",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.config.properties.retryCount",
|
||||||
|
"apiVersions": ["2015-01-01"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "properties.config.retryCount",
|
||||||
|
"apiVersions": ["2020-01-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/config.timeout",
|
||||||
|
"defaultPath": "properties.config.timeout",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/settings.encryption.enabled",
|
||||||
|
"defaultPath": "properties.settings.encryption.enabled",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.settings.encryption.enabled",
|
||||||
|
"apiVersions": ["2020-01-01"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "properties.encryptionEnabled",
|
||||||
|
"apiVersions": ["2015-01-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/complexResources/settings.encryption.keyVaultId",
|
||||||
|
"defaultPath": "properties.settings.encryption.keyVaultId",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
4
tests/azure_policy/mod.rs
Normal file
4
tests/azure_policy/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
mod normalization;
|
||||||
170
tests/azure_policy/normalization/cases/data_plane_advanced.yaml
Normal file
170
tests/azure_policy/normalization/cases/data_plane_advanced.yaml
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Extended data-plane manifest tests covering shapes beyond the basic
|
||||||
|
# KeyVault case: multiple resource types, top-level aliases, nested
|
||||||
|
# objects, array aliases, and schemaVersions.
|
||||||
|
|
||||||
|
data_manifest_json: |
|
||||||
|
{
|
||||||
|
"dataNamespace": "Microsoft.DataFactory.Data",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.DataFactory.Data/factories/pipelines/enabled",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "enabled",
|
||||||
|
"apiVersions": ["2018-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resourceTypeAliases": [
|
||||||
|
{
|
||||||
|
"resourceType": "factories/pipelines",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.DataFactory.Data/factories/pipelines/activities[*].type",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "activities[*].type",
|
||||||
|
"apiVersions": ["2018-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.DataFactory.Data/factories/pipelines/activities[*].name",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "activities[*].name",
|
||||||
|
"apiVersions": ["2018-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.DataFactory.Data/factories/pipelines/concurrency",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "concurrency",
|
||||||
|
"apiVersions": ["2018-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.DataFactory.Data/factories/pipelines/folder.name",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "folder.name",
|
||||||
|
"apiVersions": ["2018-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceType": "factories/datasets",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.DataFactory.Data/factories/datasets/linkedServiceName.referenceName",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "linkedServiceName.referenceName",
|
||||||
|
"schemaVersions": ["1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.DataFactory.Data/factories/datasets/description",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "description",
|
||||||
|
"schemaVersions": ["1"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
cases:
|
||||||
|
- note: data-plane pipeline with top-level alias and scalar fields
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/pipelines"
|
||||||
|
enabled: true
|
||||||
|
concurrency: 5
|
||||||
|
folder:
|
||||||
|
name: "etl-jobs"
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/pipelines"
|
||||||
|
enabled: true
|
||||||
|
concurrency: 5
|
||||||
|
folder:
|
||||||
|
name: "etl-jobs"
|
||||||
|
round_trip: true
|
||||||
|
|
||||||
|
- note: data-plane pipeline with array alias
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/pipelines"
|
||||||
|
activities:
|
||||||
|
- name: CopyActivity1
|
||||||
|
type: Copy
|
||||||
|
- name: WaitActivity1
|
||||||
|
type: Wait
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/pipelines"
|
||||||
|
activities:
|
||||||
|
- name: CopyActivity1
|
||||||
|
type: Copy
|
||||||
|
- name: WaitActivity1
|
||||||
|
type: Wait
|
||||||
|
round_trip: true
|
||||||
|
|
||||||
|
- note: data-plane dataset with nested alias path
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||||
|
description: "Sales data"
|
||||||
|
linkedServiceName:
|
||||||
|
referenceName: "AzureBlobStorage1"
|
||||||
|
type: LinkedServiceReference
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||||
|
description: "Sales data"
|
||||||
|
linkedservicename:
|
||||||
|
referencename: "AzureBlobStorage1"
|
||||||
|
type: LinkedServiceReference
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||||
|
description: "Sales data"
|
||||||
|
linkedServiceName:
|
||||||
|
referenceName: "AzureBlobStorage1"
|
||||||
|
type: LinkedServiceReference
|
||||||
|
|
||||||
|
- note: data-plane without aliases (unknown resource type in namespace)
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/triggers"
|
||||||
|
recurrence:
|
||||||
|
frequency: Day
|
||||||
|
interval: 1
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/triggers"
|
||||||
|
recurrence:
|
||||||
|
frequency: Day
|
||||||
|
interval: 1
|
||||||
|
round_trip: true
|
||||||
|
|
||||||
|
- note: data-plane denormalize dataset restores casing
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||||
|
description: "Sales data"
|
||||||
|
linkedservicename:
|
||||||
|
referencename: "AzureBlobStorage1"
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.DataFactory.Data/factories/datasets"
|
||||||
|
description: "Sales data"
|
||||||
|
linkedServiceName:
|
||||||
|
referenceName: "AzureBlobStorage1"
|
||||||
|
reverse_round_trip: true
|
||||||
154
tests/azure_policy/normalization/cases/data_plane_breadth.yaml
Normal file
154
tests/azure_policy/normalization/cases/data_plane_breadth.yaml
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# Broad data-plane compatibility tests covering multiple Azure data-plane
|
||||||
|
# namespaces and resource shapes. Each case exercises a distinct namespace
|
||||||
|
# to ensure the manifest-loading and data-plane normalization paths handle
|
||||||
|
# a variety of real-world patterns.
|
||||||
|
|
||||||
|
data_manifest_json: |
|
||||||
|
{
|
||||||
|
"dataNamespace": "Microsoft.Kubernetes.Data",
|
||||||
|
"aliases": [],
|
||||||
|
"resourceTypeAliases": [
|
||||||
|
{
|
||||||
|
"resourceType": "namespaces",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Kubernetes.Data/namespaces/labels",
|
||||||
|
"paths": [{ "path": "labels", "apiVersions": ["v1"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Kubernetes.Data/namespaces/annotations",
|
||||||
|
"paths": [{ "path": "annotations", "apiVersions": ["v1"] }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceType": "pods",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Kubernetes.Data/pods/containers[*].image",
|
||||||
|
"paths": [{ "path": "containers[*].image", "apiVersions": ["v1"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Kubernetes.Data/pods/containers[*].name",
|
||||||
|
"paths": [{ "path": "containers[*].name", "apiVersions": ["v1"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Kubernetes.Data/pods/containers[*].resources.limits.cpu",
|
||||||
|
"paths": [{ "path": "containers[*].resources.limits.cpu", "apiVersions": ["v1"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Kubernetes.Data/pods/hostNetwork",
|
||||||
|
"paths": [{ "path": "hostNetwork", "apiVersions": ["v1"] }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
cases:
|
||||||
|
# ── Kubernetes namespaces: flat labels/annotations ──
|
||||||
|
- note: "k8s namespace with labels and annotations"
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Kubernetes.Data/namespaces"
|
||||||
|
labels:
|
||||||
|
app: web-frontend
|
||||||
|
version: "v2"
|
||||||
|
annotations:
|
||||||
|
owner: team-alpha
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.Kubernetes.Data/namespaces"
|
||||||
|
labels:
|
||||||
|
app: web-frontend
|
||||||
|
version: "v2"
|
||||||
|
annotations:
|
||||||
|
owner: team-alpha
|
||||||
|
round_trip: true
|
||||||
|
|
||||||
|
# ── Kubernetes pods: arrays with deeply nested fields ──
|
||||||
|
- note: "k8s pod with containers array and resource limits"
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Kubernetes.Data/pods"
|
||||||
|
hostNetwork: false
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: "myregistry.azurecr.io/app:latest"
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "256Mi"
|
||||||
|
- name: sidecar
|
||||||
|
image: "myregistry.azurecr.io/sidecar:v1"
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "100m"
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.Kubernetes.Data/pods"
|
||||||
|
hostnetwork: false
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: "myregistry.azurecr.io/app:latest"
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "256Mi"
|
||||||
|
- name: sidecar
|
||||||
|
image: "myregistry.azurecr.io/sidecar:v1"
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: "100m"
|
||||||
|
round_trip: true
|
||||||
|
|
||||||
|
- note: "k8s pod denormalize restores casing"
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Kubernetes.Data/pods"
|
||||||
|
hostnetwork: true
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: "nginx"
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Kubernetes.Data/pods"
|
||||||
|
hostNetwork: true
|
||||||
|
containers:
|
||||||
|
- name: app
|
||||||
|
image: "nginx"
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
# ── Unknown resource type in known namespace ──
|
||||||
|
- note: "k8s unknown resource type passes through unchanged"
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Kubernetes.Data/services"
|
||||||
|
clusterIP: "10.0.0.1"
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.Kubernetes.Data/services"
|
||||||
|
clusterip: "10.0.0.1"
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetport: 8080
|
||||||
|
round_trip: true
|
||||||
|
# No aliases for this type, so casing is lost on round-trip.
|
||||||
|
expected_round_trip:
|
||||||
|
type: "Microsoft.Kubernetes.Data/services"
|
||||||
|
clusterip: "10.0.0.1"
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetport: 8080
|
||||||
|
|
||||||
|
# ── Empty containers array ──
|
||||||
|
- note: "k8s pod with empty containers array"
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Kubernetes.Data/pods"
|
||||||
|
hostNetwork: false
|
||||||
|
containers: []
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.Kubernetes.Data/pods"
|
||||||
|
hostnetwork: false
|
||||||
|
containers: []
|
||||||
|
round_trip: true
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Tests that exercise data-plane manifest loading and the data-plane
|
||||||
|
# normalization path (resource type contains ".Data/").
|
||||||
|
# This covers load_data_policy_manifest_json() and the data-plane
|
||||||
|
# normalization branch in normalizer.rs.
|
||||||
|
|
||||||
|
data_manifest_json: |
|
||||||
|
{
|
||||||
|
"dataNamespace": "Microsoft.KeyVault.Data",
|
||||||
|
"aliases": [],
|
||||||
|
"resourceTypeAliases": [
|
||||||
|
{
|
||||||
|
"resourceType": "vaults/certificates",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.KeyVault.Data/vaults/certificates/keySize",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "keySize",
|
||||||
|
"apiVersions": ["7.0"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.KeyVault.Data/vaults/certificates/attributes.expiresOn",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "attributes.expiresOn",
|
||||||
|
"apiVersions": ["7.0"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
cases:
|
||||||
|
- note: data-plane manifest normalize via registry API
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.KeyVault.Data/vaults/certificates"
|
||||||
|
keySize: 2048
|
||||||
|
attributes:
|
||||||
|
expiresOn: "2025-01-01T00:00:00Z"
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.KeyVault.Data/vaults/certificates"
|
||||||
|
keysize: 2048
|
||||||
|
attributes:
|
||||||
|
expireson: "2025-01-01T00:00:00Z"
|
||||||
|
round_trip: true
|
||||||
|
|
||||||
|
- note: data-plane manifest round-trip via registry API
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
type: "Microsoft.KeyVault.Data/vaults/certificates"
|
||||||
|
keySize: 2048
|
||||||
|
attributes:
|
||||||
|
expiresOn: "2025-01-01T00:00:00Z"
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
type: "Microsoft.KeyVault.Data/vaults/certificates"
|
||||||
|
keySize: 2048
|
||||||
|
attributes:
|
||||||
|
expiresOn: "2025-01-01T00:00:00Z"
|
||||||
319
tests/azure_policy/normalization/cases/denormalize_aliases.yaml
Normal file
319
tests/azure_policy/normalization/cases/denormalize_aliases.yaml
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Storage",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "storageAccounts",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||||
|
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/accessTier",
|
||||||
|
"defaultPath": "properties.accessTier",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/sku.name",
|
||||||
|
"defaultPath": "sku.name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "networkSecurityGroups",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.access",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
|
||||||
|
"defaultPath": "properties.securityRules[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceType": "virtualNetworks",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/virtualNetworks/subnets[*].addressPrefix",
|
||||||
|
"defaultPath": "properties.subnets[*].properties.addressPrefix",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/virtualNetworks/subnets[*].name",
|
||||||
|
"defaultPath": "properties.subnets[*].name",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/virtualNetworks/subnets[*].ipConfigurations[*].privateIPAddress",
|
||||||
|
"defaultPath": "properties.subnets[*].properties.ipConfigurations[*].properties.privateIPAddress",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/virtualNetworks/subnets[*].ipConfigurations[*].name",
|
||||||
|
"defaultPath": "properties.subnets[*].properties.ipConfigurations[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Web",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "sites",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Web/sites/isEnabled",
|
||||||
|
"defaultPath": "properties.isEnabled",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.enabled",
|
||||||
|
"apiVersions": ["2020-01-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "test",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "resource",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "test/resource/type",
|
||||||
|
"defaultPath": "properties.type",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resourceType": "versionedEnvelope",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "test/versionedEnvelope/items[*].status",
|
||||||
|
"defaultPath": "properties.items[*].properties.status",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.items[*].status",
|
||||||
|
"apiVersions": ["2025-01-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test/versionedEnvelope/items[*].name",
|
||||||
|
"defaultPath": "properties.items[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
cases:
|
||||||
|
- note: restores casing from aliases
|
||||||
|
input:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
accesstier: Hot
|
||||||
|
expected_denormalized:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
accessTier: Hot
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: versioned path - default
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Web/sites"
|
||||||
|
isenabled: true
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Web/sites"
|
||||||
|
properties:
|
||||||
|
isEnabled: true
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: versioned path - matching version
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Web/sites"
|
||||||
|
isenabled: true
|
||||||
|
api_version: "2020-01-01"
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Web/sites"
|
||||||
|
properties:
|
||||||
|
enabled: true
|
||||||
|
reverse_round_trip: true
|
||||||
|
expected_reverse_round_trip:
|
||||||
|
type: "Microsoft.Web/sites"
|
||||||
|
enabled: true
|
||||||
|
isenabled: true
|
||||||
|
|
||||||
|
- note: collision safe key
|
||||||
|
input:
|
||||||
|
type: "test/resource"
|
||||||
|
_p_type: SubType
|
||||||
|
expected_denormalized:
|
||||||
|
type: "test/resource"
|
||||||
|
properties:
|
||||||
|
type: SubType
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: sub-resource array
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
securityrules:
|
||||||
|
- name: rule1
|
||||||
|
protocol: Tcp
|
||||||
|
access: Allow
|
||||||
|
- name: rule2
|
||||||
|
protocol: "*"
|
||||||
|
access: Deny
|
||||||
|
expected_denormalized:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
access: Allow
|
||||||
|
- name: rule2
|
||||||
|
properties:
|
||||||
|
protocol: "*"
|
||||||
|
access: Deny
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: nested sub-resource arrays
|
||||||
|
input:
|
||||||
|
name: myVnet
|
||||||
|
type: "Microsoft.Network/virtualNetworks"
|
||||||
|
subnets:
|
||||||
|
- name: subnet1
|
||||||
|
addressprefix: "10.0.0.0/24"
|
||||||
|
ipconfigurations:
|
||||||
|
- name: ipconfig1
|
||||||
|
privateipaddress: "10.0.0.4"
|
||||||
|
expected_denormalized:
|
||||||
|
name: myVnet
|
||||||
|
type: "Microsoft.Network/virtualNetworks"
|
||||||
|
properties:
|
||||||
|
subnets:
|
||||||
|
- name: subnet1
|
||||||
|
properties:
|
||||||
|
addressPrefix: "10.0.0.0/24"
|
||||||
|
ipConfigurations:
|
||||||
|
- name: ipconfig1
|
||||||
|
properties:
|
||||||
|
privateIPAddress: "10.0.0.4"
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: root level alias (sku.name)
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
sku:
|
||||||
|
name: Standard_LRS
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
sku:
|
||||||
|
name: Standard_LRS
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: versioned envelope classification — default (status under properties)
|
||||||
|
resource_type: "test/versionedEnvelope"
|
||||||
|
sub_resource_arrays: ["items"]
|
||||||
|
input:
|
||||||
|
type: "test/versionedEnvelope"
|
||||||
|
items:
|
||||||
|
- name: item1
|
||||||
|
status: active
|
||||||
|
expected_denormalized:
|
||||||
|
type: "test/versionedEnvelope"
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
- name: item1
|
||||||
|
properties:
|
||||||
|
status: active
|
||||||
|
|
||||||
|
- note: versioned envelope classification — version promotes status to envelope
|
||||||
|
resource_type: "test/versionedEnvelope"
|
||||||
|
sub_resource_arrays: ["items"]
|
||||||
|
api_version: "2025-01-01"
|
||||||
|
input:
|
||||||
|
type: "test/versionedEnvelope"
|
||||||
|
items:
|
||||||
|
- name: item1
|
||||||
|
status: active
|
||||||
|
expected_denormalized:
|
||||||
|
type: "test/versionedEnvelope"
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
- name: item1
|
||||||
|
status: active
|
||||||
|
|
||||||
|
- note: versioned casing restoration from versioned alias paths
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Test",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "widgets",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/widgets/items[*].rating",
|
||||||
|
"defaultPath": "properties.items[*].properties.oldField",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.items[*].properties.RenamedField",
|
||||||
|
"apiVersions": ["2025-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/widgets/items[*].name",
|
||||||
|
"defaultPath": "properties.items[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
api_version: "2025-06-01"
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
items:
|
||||||
|
- name: w1
|
||||||
|
rating: 5
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
- name: w1
|
||||||
|
properties:
|
||||||
|
RenamedField: 5
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
cases:
|
||||||
|
- note: wraps properties
|
||||||
|
input:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
ishnsenabled: false
|
||||||
|
expected_denormalized:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
properties:
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
ishnsenabled: false
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: non-object returns clone
|
||||||
|
input: "just a string"
|
||||||
|
expected_denormalized: "just a string"
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: empty normalized
|
||||||
|
input: {}
|
||||||
|
expected_denormalized: {}
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: preserves root fields
|
||||||
|
input:
|
||||||
|
name: r
|
||||||
|
type: t
|
||||||
|
location: l
|
||||||
|
kind: k
|
||||||
|
id: "/sub/rg/r"
|
||||||
|
tags:
|
||||||
|
env: prod
|
||||||
|
identity:
|
||||||
|
type: SystemAssigned
|
||||||
|
principalid: pid-123
|
||||||
|
userassignedidentities:
|
||||||
|
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||||
|
sku:
|
||||||
|
name: Basic
|
||||||
|
plan:
|
||||||
|
name: p1
|
||||||
|
zones: ["1", "2"]
|
||||||
|
managedby: "/sub/other"
|
||||||
|
etag: "W/\"abc\""
|
||||||
|
apiversion: "2023-01-01"
|
||||||
|
fullname: parent/child
|
||||||
|
systemdata:
|
||||||
|
createdby: admin
|
||||||
|
extendedlocation:
|
||||||
|
name: edge1
|
||||||
|
type: EdgeZone
|
||||||
|
expected_denormalized:
|
||||||
|
name: r
|
||||||
|
type: t
|
||||||
|
location: l
|
||||||
|
kind: k
|
||||||
|
id: "/sub/rg/r"
|
||||||
|
tags:
|
||||||
|
env: prod
|
||||||
|
identity:
|
||||||
|
type: SystemAssigned
|
||||||
|
principalId: pid-123
|
||||||
|
userAssignedIdentities:
|
||||||
|
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||||
|
sku:
|
||||||
|
name: Basic
|
||||||
|
plan:
|
||||||
|
name: p1
|
||||||
|
zones: ["1", "2"]
|
||||||
|
managedBy: "/sub/other"
|
||||||
|
etag: "W/\"abc\""
|
||||||
|
apiVersion: "2023-01-01"
|
||||||
|
fullName: parent/child
|
||||||
|
systemData:
|
||||||
|
createdBy: admin
|
||||||
|
extendedLocation:
|
||||||
|
name: edge1
|
||||||
|
type: EdgeZone
|
||||||
|
reverse_round_trip: true
|
||||||
201
tests/azure_policy/normalization/cases/edge_cases.yaml
Normal file
201
tests/azure_policy/normalization/cases/edge_cases.yaml
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# Tests pinning behavior for ambiguous or malformed inputs that could
|
||||||
|
# be produced by external callers rather than the normalizer itself.
|
||||||
|
#
|
||||||
|
# The normalizer / denormalizer make assumptions about their input shape
|
||||||
|
# (e.g., keys are fully lowercased after normalization). These tests
|
||||||
|
# document what happens when those assumptions are violated, without
|
||||||
|
# asserting the behavior is "correct" per se — rather, they pin it so
|
||||||
|
# regressions are detected.
|
||||||
|
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Storage",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "storageAccounts",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||||
|
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/accessTier",
|
||||||
|
"defaultPath": "properties.accessTier",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "networkSecurityGroups",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.access",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
|
||||||
|
"defaultPath": "properties.securityRules[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Web",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "sites",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Web/sites/isEnabled",
|
||||||
|
"defaultPath": "properties.isEnabled",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.enabled",
|
||||||
|
"apiVersions": ["2020-01-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
cases:
|
||||||
|
# ── Normalize: ARM input with both root and properties having same field ──
|
||||||
|
|
||||||
|
- note: "normalize: both root 'name' and properties.name (root wins)"
|
||||||
|
input:
|
||||||
|
name: root-name
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties:
|
||||||
|
name: props-name
|
||||||
|
accessTier: Hot
|
||||||
|
expected_normalized:
|
||||||
|
name: root-name
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
accesstier: Hot
|
||||||
|
|
||||||
|
# ── Normalize: extra fields not in alias catalog or ROOT_FIELDS ──
|
||||||
|
|
||||||
|
- note: "normalize: extra non-aliased properties are kept (lowercased)"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
customUnknownField: 42
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
customunknownfield: 42
|
||||||
|
|
||||||
|
# ── Denormalize: input has mixed casing (externally constructed) ──
|
||||||
|
|
||||||
|
- note: "denormalize: mixed-case keys still resolve via aliases"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
SupportsHttpsTrafficOnly: true
|
||||||
|
expected_denormalized:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
|
||||||
|
# ── Denormalize: input has extra fields not in alias catalog ──
|
||||||
|
|
||||||
|
- note: "denormalize: unknown fields go under properties (control-plane)"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
unknownfield: 123
|
||||||
|
accesstier: Hot
|
||||||
|
expected_denormalized:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties:
|
||||||
|
unknownfield: 123
|
||||||
|
accessTier: Hot
|
||||||
|
|
||||||
|
# ── Denormalize with versioned path: both alias-named and ARM-named present ──
|
||||||
|
|
||||||
|
- note: "denormalize: both isenabled and enabled present, alias wins"
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Web/sites"
|
||||||
|
isenabled: true
|
||||||
|
enabled: false
|
||||||
|
api_version: "2020-01-01"
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Web/sites"
|
||||||
|
properties:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- note: "denormalize: both isenabled and enabled present, default path"
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Web/sites"
|
||||||
|
isenabled: true
|
||||||
|
enabled: false
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Web/sites"
|
||||||
|
properties:
|
||||||
|
isEnabled: true
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# ── Normalize: ARM resource with empty type ──
|
||||||
|
|
||||||
|
- note: "normalize: empty type field, no alias match"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
type: ""
|
||||||
|
properties:
|
||||||
|
foo: bar
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
type: ""
|
||||||
|
foo: bar
|
||||||
|
|
||||||
|
# ── Normalize: ARM resource with null properties ──
|
||||||
|
|
||||||
|
- note: "normalize: null properties value"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
properties: null
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
|
||||||
|
# ── Denormalize: sub-resource array with elements already containing properties ──
|
||||||
|
|
||||||
|
- note: "denormalize: elements already have properties wrapper (double-wrap)"
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
securityrules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
expected_denormalized:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "networkSecurityGroups",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
cases:
|
||||||
|
- note: normalize-and-wrap full pipeline
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
context:
|
||||||
|
resourceGroup:
|
||||||
|
name: rg1
|
||||||
|
parameters:
|
||||||
|
env: prod
|
||||||
|
expected_envelope:
|
||||||
|
resource:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
securityrules:
|
||||||
|
- name: rule1
|
||||||
|
protocol: Tcp
|
||||||
|
context:
|
||||||
|
resourceGroup:
|
||||||
|
name: rg1
|
||||||
|
parameters:
|
||||||
|
env: prod
|
||||||
233
tests/azure_policy/normalization/cases/malformed_input.yaml
Normal file
233
tests/azure_policy/normalization/cases/malformed_input.yaml
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
# Tests for malformed or unexpected *normalized* input fed to the
|
||||||
|
# denormalizer. These pin the current behavior when the denormalizer
|
||||||
|
# receives externally-produced JSON that violates the normalizer's
|
||||||
|
# implicit contract (e.g., keys not lowercased, wrong value types,
|
||||||
|
# missing type field, non-object input).
|
||||||
|
#
|
||||||
|
# These are NOT correctness assertions -- they document what the
|
||||||
|
# denormalizer actually produces so regressions are caught.
|
||||||
|
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Storage",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "storageAccounts",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||||
|
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/accessTier",
|
||||||
|
"defaultPath": "properties.accessTier",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "networkSecurityGroups",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
|
||||||
|
"defaultPath": "properties.securityRules[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
cases:
|
||||||
|
# ── Non-object input ──
|
||||||
|
# The denormalizer should return the value as-is when it's not an object.
|
||||||
|
|
||||||
|
- note: "denormalize: string input returned as-is"
|
||||||
|
input: "just a string"
|
||||||
|
expected_denormalized: "just a string"
|
||||||
|
|
||||||
|
- note: "denormalize: integer input returned as-is"
|
||||||
|
input: 42
|
||||||
|
expected_denormalized: 42
|
||||||
|
|
||||||
|
- note: "denormalize: array input returned as-is"
|
||||||
|
input: [1, 2, 3]
|
||||||
|
expected_denormalized: [1, 2, 3]
|
||||||
|
|
||||||
|
- note: "denormalize: null input returned as-is"
|
||||||
|
input: null
|
||||||
|
expected_denormalized: null
|
||||||
|
|
||||||
|
- note: "denormalize: boolean input returned as-is"
|
||||||
|
input: true
|
||||||
|
expected_denormalized: true
|
||||||
|
|
||||||
|
# ── Missing type field ──
|
||||||
|
# Without a type, no alias resolution occurs; fields are still placed
|
||||||
|
# under `properties` (control-plane default) but casing is not restored.
|
||||||
|
|
||||||
|
- note: "denormalize: no type field, fields go under properties"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
unknownfield: 42
|
||||||
|
expected_denormalized:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
unknownfield: 42
|
||||||
|
|
||||||
|
# ── Empty object ──
|
||||||
|
|
||||||
|
- note: "denormalize: empty object produces empty object"
|
||||||
|
input: {}
|
||||||
|
expected_denormalized: {}
|
||||||
|
|
||||||
|
# ── Object with only type field ──
|
||||||
|
|
||||||
|
- note: "denormalize: only type field, empty properties"
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
|
||||||
|
# ── Normalize: non-object input ──
|
||||||
|
|
||||||
|
- note: "normalize: string input returned as-is"
|
||||||
|
input: "just a string"
|
||||||
|
expected_normalized: "just a string"
|
||||||
|
|
||||||
|
- note: "normalize: integer input returned as-is"
|
||||||
|
input: 42
|
||||||
|
expected_normalized: 42
|
||||||
|
|
||||||
|
- note: "normalize: null input returned as-is"
|
||||||
|
input: null
|
||||||
|
expected_normalized: null
|
||||||
|
|
||||||
|
# ── Normalize: empty properties object ──
|
||||||
|
|
||||||
|
- note: "normalize: empty properties object"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties: {}
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
|
||||||
|
# ── Normalize: properties is a non-object value ──
|
||||||
|
|
||||||
|
- note: "normalize: properties is a string (treated as non-object, ignored)"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties: "not an object"
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
|
||||||
|
- note: "normalize: properties is an array (treated as non-object, ignored)"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties: [1, 2, 3]
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
|
||||||
|
# ── Denormalize: value types preserved through round trip ──
|
||||||
|
|
||||||
|
- note: "denormalize: numeric value zero"
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
accesstier: 0
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties:
|
||||||
|
accessTier: 0
|
||||||
|
|
||||||
|
- note: "denormalize: boolean false aliased field"
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
supportshttpstrafficonly: false
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: false
|
||||||
|
|
||||||
|
- note: "denormalize: null-valued aliased field"
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
supportshttpstrafficonly: null
|
||||||
|
expected_denormalized:
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: null
|
||||||
|
|
||||||
|
# ── Sub-resource array with non-array value ──
|
||||||
|
|
||||||
|
- note: "denormalize: expected sub-resource array is a string"
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
securityrules: "not an array"
|
||||||
|
expected_denormalized:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules: "not an array"
|
||||||
|
|
||||||
|
- note: "denormalize: expected sub-resource array is null"
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
securityrules: null
|
||||||
|
expected_denormalized:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules: null
|
||||||
|
|
||||||
|
# ── Sub-resource array with non-object elements ──
|
||||||
|
|
||||||
|
- note: "denormalize: sub-resource contains scalar elements"
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
securityrules:
|
||||||
|
- "just a string"
|
||||||
|
- 42
|
||||||
|
expected_denormalized:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- "just a string"
|
||||||
|
- 42
|
||||||
|
|
||||||
|
# ── Deeply nested null in normalize path ──
|
||||||
|
|
||||||
|
- note: "normalize: deeply nested null value preserved"
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: null
|
||||||
|
accessTier: null
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
supportshttpstrafficonly: null
|
||||||
|
accesstier: null
|
||||||
258
tests/azure_policy/normalization/cases/normalize_basic.yaml
Normal file
258
tests/azure_policy/normalization/cases/normalize_basic.yaml
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
cases:
|
||||||
|
- note: flattens root properties
|
||||||
|
input:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
isHnsEnabled: false
|
||||||
|
expected_normalized:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
ishnsenabled: false
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
properties:
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
ishnsenabled: false
|
||||||
|
|
||||||
|
- note: preserves root level precedence
|
||||||
|
input:
|
||||||
|
name: root-name
|
||||||
|
properties:
|
||||||
|
name: props-name
|
||||||
|
expected_normalized:
|
||||||
|
name: root-name
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: root-name
|
||||||
|
|
||||||
|
- note: leaves plain arrays alone
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
networkAcls:
|
||||||
|
ipRules:
|
||||||
|
- value: "10.0.0.1"
|
||||||
|
action: Allow
|
||||||
|
- value: "10.0.0.2"
|
||||||
|
action: Deny
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
networkacls:
|
||||||
|
iprules:
|
||||||
|
- value: "10.0.0.1"
|
||||||
|
action: Allow
|
||||||
|
- value: "10.0.0.2"
|
||||||
|
action: Deny
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
networkacls:
|
||||||
|
iprules:
|
||||||
|
- value: "10.0.0.1"
|
||||||
|
action: Allow
|
||||||
|
- value: "10.0.0.2"
|
||||||
|
action: Deny
|
||||||
|
|
||||||
|
- note: handles sku at root
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
sku:
|
||||||
|
name: Standard_LRS
|
||||||
|
tier: Standard
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
sku:
|
||||||
|
name: Standard_LRS
|
||||||
|
tier: Standard
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: test
|
||||||
|
sku:
|
||||||
|
name: Standard_LRS
|
||||||
|
tier: Standard
|
||||||
|
properties:
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
|
||||||
|
- note: non-object returns clone
|
||||||
|
input: "just a string"
|
||||||
|
expected_normalized: "just a string"
|
||||||
|
round_trip: true
|
||||||
|
|
||||||
|
- note: empty properties
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
properties: {}
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: test
|
||||||
|
|
||||||
|
- note: missing properties
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
location: eastus
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
location: eastus
|
||||||
|
round_trip: true
|
||||||
|
|
||||||
|
- note: preserves all root fields
|
||||||
|
input:
|
||||||
|
name: r
|
||||||
|
type: t
|
||||||
|
location: l
|
||||||
|
kind: k
|
||||||
|
id: "/sub/rg/r"
|
||||||
|
tags:
|
||||||
|
env: prod
|
||||||
|
identity:
|
||||||
|
type: SystemAssigned
|
||||||
|
principalId: pid-123
|
||||||
|
userAssignedIdentities:
|
||||||
|
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||||
|
sku:
|
||||||
|
name: Basic
|
||||||
|
plan:
|
||||||
|
name: p1
|
||||||
|
zones: ["1", "2"]
|
||||||
|
managedBy: "/sub/other"
|
||||||
|
etag: "W/\"abc\""
|
||||||
|
apiVersion: "2023-01-01"
|
||||||
|
fullName: parent/child
|
||||||
|
systemData:
|
||||||
|
createdBy: admin
|
||||||
|
extendedLocation:
|
||||||
|
name: edge1
|
||||||
|
type: EdgeZone
|
||||||
|
properties:
|
||||||
|
someProp: true
|
||||||
|
expected_normalized:
|
||||||
|
name: r
|
||||||
|
type: t
|
||||||
|
location: l
|
||||||
|
kind: k
|
||||||
|
id: "/sub/rg/r"
|
||||||
|
tags:
|
||||||
|
env: prod
|
||||||
|
identity:
|
||||||
|
type: SystemAssigned
|
||||||
|
principalid: pid-123
|
||||||
|
userassignedidentities:
|
||||||
|
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||||
|
sku:
|
||||||
|
name: Basic
|
||||||
|
plan:
|
||||||
|
name: p1
|
||||||
|
zones: ["1", "2"]
|
||||||
|
managedby: "/sub/other"
|
||||||
|
etag: "W/\"abc\""
|
||||||
|
apiversion: "2023-01-01"
|
||||||
|
fullname: parent/child
|
||||||
|
systemdata:
|
||||||
|
createdby: admin
|
||||||
|
extendedlocation:
|
||||||
|
name: edge1
|
||||||
|
type: EdgeZone
|
||||||
|
someprop: true
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: r
|
||||||
|
type: t
|
||||||
|
location: l
|
||||||
|
kind: k
|
||||||
|
id: "/sub/rg/r"
|
||||||
|
tags:
|
||||||
|
env: prod
|
||||||
|
identity:
|
||||||
|
type: SystemAssigned
|
||||||
|
principalId: pid-123
|
||||||
|
userAssignedIdentities:
|
||||||
|
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||||
|
sku:
|
||||||
|
name: Basic
|
||||||
|
plan:
|
||||||
|
name: p1
|
||||||
|
zones: ["1", "2"]
|
||||||
|
managedBy: "/sub/other"
|
||||||
|
etag: "W/\"abc\""
|
||||||
|
apiVersion: "2023-01-01"
|
||||||
|
fullName: parent/child
|
||||||
|
systemData:
|
||||||
|
createdBy: admin
|
||||||
|
extendedLocation:
|
||||||
|
name: edge1
|
||||||
|
type: EdgeZone
|
||||||
|
properties:
|
||||||
|
someprop: true
|
||||||
|
|
||||||
|
- note: primitive array pass through
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
allowedIPs: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
allowedips: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
allowedips: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
|
||||||
|
|
||||||
|
- note: deeply nested object no sub-resource
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
networkAcls:
|
||||||
|
defaultAction: Deny
|
||||||
|
virtualNetworkRules:
|
||||||
|
- id: "/vnet/subnet1"
|
||||||
|
action: Allow
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
networkacls:
|
||||||
|
defaultaction: Deny
|
||||||
|
virtualnetworkrules:
|
||||||
|
- id: "/vnet/subnet1"
|
||||||
|
action: Allow
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
networkacls:
|
||||||
|
defaultaction: Deny
|
||||||
|
virtualnetworkrules:
|
||||||
|
- id: "/vnet/subnet1"
|
||||||
|
action: Allow
|
||||||
|
|
||||||
|
- note: unknown root fields are dropped (not in ROOT_FIELDS)
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Foo/bars"
|
||||||
|
fooExtension:
|
||||||
|
barBaz: 1
|
||||||
|
properties:
|
||||||
|
enabled: true
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Foo/bars"
|
||||||
|
enabled: true
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: test
|
||||||
|
type: "Microsoft.Foo/bars"
|
||||||
|
properties:
|
||||||
|
enabled: true
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
cases:
|
||||||
|
- note: envelope defaults
|
||||||
|
input:
|
||||||
|
name: x
|
||||||
|
expected_envelope:
|
||||||
|
resource:
|
||||||
|
name: x
|
||||||
|
context: {}
|
||||||
|
parameters: {}
|
||||||
|
|
||||||
|
- note: envelope with context and parameters
|
||||||
|
input:
|
||||||
|
name: x
|
||||||
|
context:
|
||||||
|
resourceGroup:
|
||||||
|
name: rg1
|
||||||
|
parameters:
|
||||||
|
env: prod
|
||||||
|
expected_envelope:
|
||||||
|
resource:
|
||||||
|
name: x
|
||||||
|
context:
|
||||||
|
resourceGroup:
|
||||||
|
name: rg1
|
||||||
|
parameters:
|
||||||
|
env: prod
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
cases:
|
||||||
|
- note: flattens sub-resource arrays
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
access: Allow
|
||||||
|
- name: rule2
|
||||||
|
properties:
|
||||||
|
protocol: "*"
|
||||||
|
access: Deny
|
||||||
|
sub_resource_arrays: ["securityRules"]
|
||||||
|
resource_type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
expected_normalized:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
securityrules:
|
||||||
|
- name: rule1
|
||||||
|
protocol: Tcp
|
||||||
|
access: Allow
|
||||||
|
- name: rule2
|
||||||
|
protocol: "*"
|
||||||
|
access: Deny
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityrules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
access: Allow
|
||||||
|
- name: rule2
|
||||||
|
properties:
|
||||||
|
protocol: "*"
|
||||||
|
access: Deny
|
||||||
|
|
||||||
|
- note: nested sub-resource arrays
|
||||||
|
input:
|
||||||
|
name: myVnet
|
||||||
|
properties:
|
||||||
|
subnets:
|
||||||
|
- name: subnet1
|
||||||
|
properties:
|
||||||
|
addressPrefix: "10.0.0.0/24"
|
||||||
|
ipConfigurations:
|
||||||
|
- name: ipconfig1
|
||||||
|
properties:
|
||||||
|
privateIPAddress: "10.0.0.4"
|
||||||
|
sub_resource_arrays: ["subnets", "subnets.ipConfigurations"]
|
||||||
|
resource_type: "Microsoft.Network/virtualNetworks"
|
||||||
|
expected_normalized:
|
||||||
|
name: myVnet
|
||||||
|
subnets:
|
||||||
|
- name: subnet1
|
||||||
|
addressprefix: "10.0.0.0/24"
|
||||||
|
ipconfigurations:
|
||||||
|
- name: ipconfig1
|
||||||
|
privateipaddress: "10.0.0.4"
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myVnet
|
||||||
|
properties:
|
||||||
|
subnets:
|
||||||
|
- name: subnet1
|
||||||
|
properties:
|
||||||
|
addressprefix: "10.0.0.0/24"
|
||||||
|
ipconfigurations:
|
||||||
|
- name: ipconfig1
|
||||||
|
properties:
|
||||||
|
privateipaddress: "10.0.0.4"
|
||||||
|
|
||||||
|
- note: sub-resource element without properties
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
- name: plain-object
|
||||||
|
enabled: true
|
||||||
|
sub_resource_arrays: ["items"]
|
||||||
|
resource_type: test
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
items:
|
||||||
|
- name: plain-object
|
||||||
|
enabled: true
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
- name: plain-object
|
||||||
|
properties:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- note: case-insensitive sub-resource match
|
||||||
|
input:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: r1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
sub_resource_arrays: ["SecurityRules"]
|
||||||
|
resource_type: test
|
||||||
|
expected_normalized:
|
||||||
|
name: test
|
||||||
|
securityrules:
|
||||||
|
- name: r1
|
||||||
|
protocol: Tcp
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: test
|
||||||
|
properties:
|
||||||
|
securityrules:
|
||||||
|
- name: r1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
127
tests/azure_policy/normalization/cases/registry_api.yaml
Normal file
127
tests/azure_policy/normalization/cases/registry_api.yaml
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Tests that exercise the public AliasRegistry API (normalize, denormalize,
|
||||||
|
# normalize_and_wrap) rather than the low-level *_with_aliases functions.
|
||||||
|
# This ensures the registry lookup path and public entry points are covered.
|
||||||
|
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Storage",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "storageAccounts",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||||
|
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/isHnsEnabled",
|
||||||
|
"defaultPath": "properties.isHnsEnabled",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "networkSecurityGroups",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
|
||||||
|
"defaultPath": "properties.securityRules[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
cases:
|
||||||
|
- note: registry API normalize
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
isHnsEnabled: false
|
||||||
|
expected_normalized:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
ishnsenabled: false
|
||||||
|
round_trip: true
|
||||||
|
|
||||||
|
- note: registry API denormalize
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
ishnsenabled: false
|
||||||
|
expected_denormalized:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
isHnsEnabled: false
|
||||||
|
reverse_round_trip: true
|
||||||
|
|
||||||
|
- note: registry API normalize_and_wrap (envelope)
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
context:
|
||||||
|
resourceGroup:
|
||||||
|
name: rg1
|
||||||
|
expected_envelope:
|
||||||
|
resource:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
securityrules:
|
||||||
|
- name: rule1
|
||||||
|
protocol: Tcp
|
||||||
|
context:
|
||||||
|
resourceGroup:
|
||||||
|
name: rg1
|
||||||
|
parameters: {}
|
||||||
|
|
||||||
|
- note: registry API round-trip sub-resource
|
||||||
|
use_registry_api: true
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
449
tests/azure_policy/normalization/cases/round_trip.yaml
Normal file
449
tests/azure_policy/normalization/cases/round_trip.yaml
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Storage",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "storageAccounts",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||||
|
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Storage/storageAccounts/isHnsEnabled",
|
||||||
|
"defaultPath": "properties.isHnsEnabled",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "networkSecurityGroups",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.protocol",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
|
||||||
|
"defaultPath": "properties.securityRules[*].properties.access",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
|
||||||
|
"defaultPath": "properties.securityRules[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
cases:
|
||||||
|
- note: round-trip simple resource
|
||||||
|
input:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
sku:
|
||||||
|
name: Standard_LRS
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
isHnsEnabled: false
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
sku:
|
||||||
|
name: Standard_LRS
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
isHnsEnabled: false
|
||||||
|
|
||||||
|
- note: round-trip sub-resource
|
||||||
|
input:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
access: Allow
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myNsg
|
||||||
|
type: "Microsoft.Network/networkSecurityGroups"
|
||||||
|
properties:
|
||||||
|
securityRules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
protocol: Tcp
|
||||||
|
access: Allow
|
||||||
|
|
||||||
|
- note: round-trip versioned array alias (element field remap)
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "firewallPolicies",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/firewallPolicies/rules[*].priority",
|
||||||
|
"defaultPath": "properties.rules[*].properties.priority",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.rules[*].properties.prio",
|
||||||
|
"apiVersions": ["2021-01-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/firewallPolicies/rules[*].name",
|
||||||
|
"defaultPath": "properties.rules[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
input:
|
||||||
|
name: myPolicy
|
||||||
|
type: "Microsoft.Network/firewallPolicies"
|
||||||
|
properties:
|
||||||
|
rules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
prio: 100
|
||||||
|
api_version: "2021-01-01"
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myPolicy
|
||||||
|
type: "Microsoft.Network/firewallPolicies"
|
||||||
|
properties:
|
||||||
|
rules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
prio: 100
|
||||||
|
|
||||||
|
# Regression: default and versioned paths both produce exactly one element
|
||||||
|
# remap (same count), but the source field differs. A length-only comparison
|
||||||
|
# would silently reuse the default aggregate, producing incorrect results for
|
||||||
|
# the versioned API version.
|
||||||
|
- note: round-trip versioned remap with same count but different source field
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "firewallPolicies",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/firewallPolicies/rules[*].priority",
|
||||||
|
"defaultPath": "properties.rules[*].properties.oldA",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"path": "properties.rules[*].properties.oldB",
|
||||||
|
"apiVersions": ["2023-06-01"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/firewallPolicies/rules[*].name",
|
||||||
|
"defaultPath": "properties.rules[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
input:
|
||||||
|
name: myPolicy
|
||||||
|
type: "Microsoft.Network/firewallPolicies"
|
||||||
|
properties:
|
||||||
|
rules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
oldB: 100
|
||||||
|
api_version: "2023-06-01"
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myPolicy
|
||||||
|
type: "Microsoft.Network/firewallPolicies"
|
||||||
|
properties:
|
||||||
|
rules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
oldB: 100
|
||||||
|
|
||||||
|
- note: round-trip systemData and extendedLocation
|
||||||
|
input:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
identity:
|
||||||
|
type: SystemAssigned
|
||||||
|
principalId: pid-123
|
||||||
|
userAssignedIdentities:
|
||||||
|
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||||
|
systemData:
|
||||||
|
createdBy: user@example.com
|
||||||
|
createdByType: User
|
||||||
|
createdAt: "2023-01-01T00:00:00Z"
|
||||||
|
extendedLocation:
|
||||||
|
name: edge-site-1
|
||||||
|
type: EdgeZone
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
expected_normalized:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
identity:
|
||||||
|
type: SystemAssigned
|
||||||
|
principalid: pid-123
|
||||||
|
userassignedidentities:
|
||||||
|
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||||
|
systemdata:
|
||||||
|
createdby: user@example.com
|
||||||
|
createdbytype: User
|
||||||
|
createdat: "2023-01-01T00:00:00Z"
|
||||||
|
extendedlocation:
|
||||||
|
name: edge-site-1
|
||||||
|
type: EdgeZone
|
||||||
|
supportshttpstrafficonly: true
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myStorage
|
||||||
|
type: "Microsoft.Storage/storageAccounts"
|
||||||
|
location: westus2
|
||||||
|
identity:
|
||||||
|
type: SystemAssigned
|
||||||
|
principalId: pid-123
|
||||||
|
userAssignedIdentities:
|
||||||
|
/subscriptions/Sub/resourceGroups/Rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/Uai1: {}
|
||||||
|
systemData:
|
||||||
|
createdBy: user@example.com
|
||||||
|
createdByType: User
|
||||||
|
createdAt: "2023-01-01T00:00:00Z"
|
||||||
|
extendedLocation:
|
||||||
|
name: edge-site-1
|
||||||
|
type: EdgeZone
|
||||||
|
properties:
|
||||||
|
supportsHttpsTrafficOnly: true
|
||||||
|
|
||||||
|
# Regression: element remap must remove the stale ARM source field from the
|
||||||
|
# normalized output. Without cleanup, both the alias short name AND the
|
||||||
|
# original ARM leaf key survive; casing restoration during denormalization
|
||||||
|
# then produces a duplicate key (e.g. both "prio" and "priority").
|
||||||
|
- note: element remap removes stale ARM source field
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Network",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "firewallPolicies",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/firewallPolicies/rules[*].priority",
|
||||||
|
"defaultPath": "properties.rules[*].properties.prio",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Network/firewallPolicies/rules[*].name",
|
||||||
|
"defaultPath": "properties.rules[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
input:
|
||||||
|
name: myPolicy
|
||||||
|
type: "Microsoft.Network/firewallPolicies"
|
||||||
|
properties:
|
||||||
|
rules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
prio: 42
|
||||||
|
# Normalize: "prio" (ARM leaf) → "priority" (alias short name).
|
||||||
|
# The stale "prio" key must be removed from the normalized element.
|
||||||
|
expected_normalized:
|
||||||
|
name: myPolicy
|
||||||
|
type: "Microsoft.Network/firewallPolicies"
|
||||||
|
rules:
|
||||||
|
- name: rule1
|
||||||
|
priority: 42
|
||||||
|
# Round-trip: normalize → denormalize should restore ARM structure.
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
name: myPolicy
|
||||||
|
type: "Microsoft.Network/firewallPolicies"
|
||||||
|
properties:
|
||||||
|
rules:
|
||||||
|
- name: rule1
|
||||||
|
properties:
|
||||||
|
prio: 42
|
||||||
|
|
||||||
|
# Regression: same stale-field-cleanup scenario but with a dotted ARM leaf
|
||||||
|
# path (e.g. "config.value" rather than a single segment "prio").
|
||||||
|
# The remove_element_field helper must navigate to the parent object and
|
||||||
|
# remove the leaf key via remove_at_dotted_path.
|
||||||
|
- note: element remap removes stale dotted ARM source field
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Test",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "widgets",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/widgets/items[*].rating",
|
||||||
|
"defaultPath": "properties.items[*].properties.config.score",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/widgets/items[*].name",
|
||||||
|
"defaultPath": "properties.items[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
- name: w1
|
||||||
|
properties:
|
||||||
|
config:
|
||||||
|
score: 5
|
||||||
|
# Normalize: "config.score" (dotted ARM leaf) → "rating" (alias short name).
|
||||||
|
# The stale "config.score" path must be removed; only "rating" should remain.
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
items:
|
||||||
|
- name: w1
|
||||||
|
config: {}
|
||||||
|
rating: 5
|
||||||
|
# Round-trip: normalize → denormalize should restore ARM structure.
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
- name: w1
|
||||||
|
properties:
|
||||||
|
config:
|
||||||
|
score: 5
|
||||||
|
|
||||||
|
# Regression: array base rename must move (not clone) the value so that
|
||||||
|
# the stale ARM base key does not survive in the normalized output.
|
||||||
|
# Without the removal, denormalization produces a duplicate key.
|
||||||
|
- note: array base rename removes stale ARM base key
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Test",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "widgets",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/widgets/items[*].label",
|
||||||
|
"defaultPath": "properties.entries[*].label",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
properties:
|
||||||
|
entries:
|
||||||
|
- label: hello
|
||||||
|
# Normalize: ARM base "entries" → alias base "items".
|
||||||
|
# The stale "entries" key must be removed; only "items" should remain.
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
items:
|
||||||
|
- label: hello
|
||||||
|
# Round-trip: normalize → denormalize should restore ARM structure.
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
properties:
|
||||||
|
entries:
|
||||||
|
- label: hello
|
||||||
|
|
||||||
|
# Regression: denormalize reverse element remap with a dotted ARM target
|
||||||
|
# must preserve restored casing (e.g. "Config.Score") rather than
|
||||||
|
# re-lowercasing it.
|
||||||
|
- note: round-trip dotted element remap preserves ARM casing
|
||||||
|
aliases_json: |
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "Microsoft.Test",
|
||||||
|
"resourceTypes": [
|
||||||
|
{
|
||||||
|
"resourceType": "widgets",
|
||||||
|
"aliases": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/widgets/items[*].rating",
|
||||||
|
"defaultPath": "properties.items[*].properties.Config.Score",
|
||||||
|
"paths": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.Test/widgets/items[*].name",
|
||||||
|
"defaultPath": "properties.items[*].name",
|
||||||
|
"paths": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
input:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
- name: w1
|
||||||
|
properties:
|
||||||
|
Config:
|
||||||
|
Score: 9
|
||||||
|
expected_normalized:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
items:
|
||||||
|
- name: w1
|
||||||
|
config: {}
|
||||||
|
rating: 9
|
||||||
|
round_trip: true
|
||||||
|
expected_round_trip:
|
||||||
|
type: "Microsoft.Test/widgets"
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
- name: w1
|
||||||
|
properties:
|
||||||
|
Config:
|
||||||
|
Score: 9
|
||||||
322
tests/azure_policy/normalization/mod.rs
Normal file
322
tests/azure_policy/normalization/mod.rs
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
//! YAML-driven normalization / denormalization tests.
|
||||||
|
//!
|
||||||
|
//! Each YAML file contains a `cases` array. Every case specifies an `input`
|
||||||
|
//! JSON value plus optional `aliases` and `api_version`. The runner then
|
||||||
|
//! checks whichever of the following fields are present:
|
||||||
|
//!
|
||||||
|
//! * `expected_normalized` – result of normalizing `input`
|
||||||
|
//! * `expected_denormalized` – result of denormalizing `input`
|
||||||
|
//! * `round_trip` – normalize then denormalize; compare with `expected_round_trip`
|
||||||
|
//! * `reverse_round_trip` – denormalize then normalize; compare with
|
||||||
|
//! `expected_reverse_round_trip`
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use test_generator::test_resources;
|
||||||
|
|
||||||
|
use regorus::languages::azure_policy::aliases::normalizer;
|
||||||
|
use regorus::languages::azure_policy::aliases::types::ResolvedAliases;
|
||||||
|
use regorus::languages::azure_policy::aliases::{denormalizer, AliasRegistry};
|
||||||
|
use regorus::Value;
|
||||||
|
|
||||||
|
// ── YAML schema ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct YamlTest {
|
||||||
|
#[serde(default)]
|
||||||
|
aliases_json: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
aliases_file: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
data_manifest_json: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
data_manifest_file: Option<String>,
|
||||||
|
cases: Vec<TestCase>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct TestCase {
|
||||||
|
note: String,
|
||||||
|
input: serde_json::Value,
|
||||||
|
#[serde(default)]
|
||||||
|
api_version: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
context: Option<serde_json::Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
parameters: Option<serde_json::Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
expected_normalized: Option<serde_json::Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
expected_denormalized: Option<serde_json::Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
expected_envelope: Option<serde_json::Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
round_trip: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
expected_round_trip: Option<serde_json::Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
reverse_round_trip: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
expected_reverse_round_trip: Option<serde_json::Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
aliases_json: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
sub_resource_arrays: Option<Vec<String>>,
|
||||||
|
#[serde(default)]
|
||||||
|
resource_type: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
use_registry_api: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Convert a `serde_json::Value` to `regorus::Value`.
|
||||||
|
fn to_regorus(v: &serde_json::Value) -> Value {
|
||||||
|
Value::from(v.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_registry(yaml_file: &str, test: &YamlTest) -> Result<Option<AliasRegistry>> {
|
||||||
|
let mut reg = AliasRegistry::new();
|
||||||
|
let mut loaded = false;
|
||||||
|
|
||||||
|
if let Some(ref inline) = test.aliases_json {
|
||||||
|
reg.load_from_json(inline)?;
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
if let Some(ref relpath) = test.aliases_file {
|
||||||
|
let base = Path::new(yaml_file).parent().unwrap_or(Path::new("."));
|
||||||
|
let path = base.join(relpath);
|
||||||
|
let json = std::fs::read_to_string(&path)?;
|
||||||
|
reg.load_from_json(&json)?;
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
if let Some(ref inline) = test.data_manifest_json {
|
||||||
|
reg.load_data_policy_manifest_json(inline)?;
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
if let Some(ref relpath) = test.data_manifest_file {
|
||||||
|
let base = Path::new(yaml_file).parent().unwrap_or(Path::new("."));
|
||||||
|
let path = base.join(relpath);
|
||||||
|
let json = std::fs::read_to_string(&path)?;
|
||||||
|
reg.load_data_policy_manifest_json(&json)?;
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(if loaded { Some(reg) } else { None })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn case_override_registry(case: &TestCase) -> Result<Option<AliasRegistry>> {
|
||||||
|
if let Some(ref inline) = case.aliases_json {
|
||||||
|
let mut reg = AliasRegistry::new();
|
||||||
|
reg.load_from_json(inline)?;
|
||||||
|
return Ok(Some(reg));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_aliases(
|
||||||
|
registry: Option<&AliasRegistry>,
|
||||||
|
case: &TestCase,
|
||||||
|
input: &serde_json::Value,
|
||||||
|
) -> Option<ResolvedAliases> {
|
||||||
|
let resource_type = case
|
||||||
|
.resource_type
|
||||||
|
.clone()
|
||||||
|
.or_else(|| input.get("type").and_then(|v| v.as_str()).map(String::from));
|
||||||
|
|
||||||
|
if let (Some(reg), Some(rt)) = (registry, resource_type.as_deref()) {
|
||||||
|
if let Some(resolved) = reg.get(rt) {
|
||||||
|
let mut r = resolved.clone();
|
||||||
|
if let Some(ref subs) = case.sub_resource_arrays {
|
||||||
|
r.sub_resource_arrays = subs.iter().map(|s| s.to_ascii_lowercase()).collect();
|
||||||
|
}
|
||||||
|
return Some(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref subs) = case.sub_resource_arrays {
|
||||||
|
return Some(ResolvedAliases {
|
||||||
|
resource_type: resource_type.unwrap_or_default(),
|
||||||
|
entries: Default::default(),
|
||||||
|
sub_resource_arrays: subs.iter().map(|s| s.to_ascii_lowercase()).collect(),
|
||||||
|
default_aggregates: Default::default(),
|
||||||
|
versioned_aggregates: Default::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pretty_regorus(v: &Value) -> String {
|
||||||
|
v.to_json_str().unwrap_or_else(|_| format!("{v:?}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Runner ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn run_yaml_test(file: &str) -> Result<()> {
|
||||||
|
let yaml_str = std::fs::read_to_string(file)?;
|
||||||
|
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
||||||
|
let file_registry = load_registry(file, &test)?;
|
||||||
|
|
||||||
|
for case in &test.cases {
|
||||||
|
print!(" case: {} … ", case.note);
|
||||||
|
|
||||||
|
let case_override = case_override_registry(case)?;
|
||||||
|
let registry = case_override.as_ref().or(file_registry.as_ref());
|
||||||
|
let resolved = resolve_aliases(registry, case, &case.input);
|
||||||
|
let api_ver = case.api_version.as_deref();
|
||||||
|
let input = to_regorus(&case.input);
|
||||||
|
|
||||||
|
// ── normalize ────────────────────────────────────────────────
|
||||||
|
if let Some(ref expected) = case.expected_normalized {
|
||||||
|
let expected = to_regorus(expected);
|
||||||
|
let actual = if case.use_registry_api {
|
||||||
|
normalizer::normalize(&input, registry, api_ver)
|
||||||
|
} else {
|
||||||
|
normalizer::normalize_with_aliases(&input, resolved.as_ref(), api_ver)
|
||||||
|
};
|
||||||
|
if actual != expected {
|
||||||
|
bail!(
|
||||||
|
"normalize mismatch in '{}':\nexpected:\n{}\nactual:\n{}",
|
||||||
|
case.note,
|
||||||
|
pretty_regorus(&expected),
|
||||||
|
pretty_regorus(&actual),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── denormalize ──────────────────────────────────────────────
|
||||||
|
if let Some(ref expected) = case.expected_denormalized {
|
||||||
|
let expected = to_regorus(expected);
|
||||||
|
let actual = if case.use_registry_api {
|
||||||
|
denormalizer::denormalize(&input, registry, api_ver)
|
||||||
|
} else {
|
||||||
|
denormalizer::denormalize_with_aliases(&input, resolved.as_ref(), api_ver)
|
||||||
|
};
|
||||||
|
if actual != expected {
|
||||||
|
bail!(
|
||||||
|
"denormalize mismatch in '{}':\nexpected:\n{}\nactual:\n{}",
|
||||||
|
case.note,
|
||||||
|
pretty_regorus(&expected),
|
||||||
|
pretty_regorus(&actual),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── envelope ─────────────────────────────────────────────────
|
||||||
|
if let Some(ref expected) = case.expected_envelope {
|
||||||
|
let expected = to_regorus(expected);
|
||||||
|
let actual = if case.use_registry_api {
|
||||||
|
if let Some(reg) = registry {
|
||||||
|
reg.normalize_and_wrap(
|
||||||
|
&input,
|
||||||
|
api_ver,
|
||||||
|
case.context.as_ref().map(to_regorus),
|
||||||
|
case.parameters.as_ref().map(to_regorus),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let norm = normalizer::normalize(&input, None, api_ver);
|
||||||
|
normalizer::build_input_envelope(
|
||||||
|
norm,
|
||||||
|
case.context.as_ref().map(to_regorus),
|
||||||
|
case.parameters.as_ref().map(to_regorus),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let norm = normalizer::normalize_with_aliases(&input, resolved.as_ref(), api_ver);
|
||||||
|
normalizer::build_input_envelope(
|
||||||
|
norm,
|
||||||
|
case.context.as_ref().map(to_regorus),
|
||||||
|
case.parameters.as_ref().map(to_regorus),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if actual != expected {
|
||||||
|
bail!(
|
||||||
|
"envelope mismatch in '{}':\nexpected:\n{}\nactual:\n{}",
|
||||||
|
case.note,
|
||||||
|
pretty_regorus(&expected),
|
||||||
|
pretty_regorus(&actual),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── round-trip ───────────────────────────────────────────────
|
||||||
|
if case.round_trip {
|
||||||
|
let (normalized, denormalized) = if case.use_registry_api {
|
||||||
|
let n = normalizer::normalize(&input, registry, api_ver);
|
||||||
|
let d = denormalizer::denormalize(&n, registry, api_ver);
|
||||||
|
(n, d)
|
||||||
|
} else {
|
||||||
|
let n = normalizer::normalize_with_aliases(&input, resolved.as_ref(), api_ver);
|
||||||
|
let d = denormalizer::denormalize_with_aliases(&n, resolved.as_ref(), api_ver);
|
||||||
|
(n, d)
|
||||||
|
};
|
||||||
|
let _ = normalized;
|
||||||
|
if let Some(ref expected) = case.expected_round_trip {
|
||||||
|
let expected = to_regorus(expected);
|
||||||
|
if denormalized != expected {
|
||||||
|
bail!(
|
||||||
|
"round-trip mismatch in '{}':\nexpected:\n{}\nactual:\n{}",
|
||||||
|
case.note,
|
||||||
|
pretty_regorus(&expected),
|
||||||
|
pretty_regorus(&denormalized),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if denormalized != input {
|
||||||
|
bail!(
|
||||||
|
"round-trip mismatch in '{}' (expected original input):\ninput:\n{}\nresult:\n{}",
|
||||||
|
case.note,
|
||||||
|
pretty_regorus(&input),
|
||||||
|
pretty_regorus(&denormalized),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── reverse round-trip ─────────────────────────────────────
|
||||||
|
if case.reverse_round_trip {
|
||||||
|
let (denormalized, renormalized) = if case.use_registry_api {
|
||||||
|
let d = denormalizer::denormalize(&input, registry, api_ver);
|
||||||
|
let n = normalizer::normalize(&d, registry, api_ver);
|
||||||
|
(d, n)
|
||||||
|
} else {
|
||||||
|
let d = denormalizer::denormalize_with_aliases(&input, resolved.as_ref(), api_ver);
|
||||||
|
let n = normalizer::normalize_with_aliases(&d, resolved.as_ref(), api_ver);
|
||||||
|
(d, n)
|
||||||
|
};
|
||||||
|
let _ = denormalized;
|
||||||
|
if let Some(ref expected) = case.expected_reverse_round_trip {
|
||||||
|
let expected = to_regorus(expected);
|
||||||
|
if renormalized != expected {
|
||||||
|
bail!(
|
||||||
|
"reverse round-trip mismatch in '{}':\nexpected:\n{}\nactual:\n{}",
|
||||||
|
case.note,
|
||||||
|
pretty_regorus(&expected),
|
||||||
|
pretty_regorus(&renormalized),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if renormalized != input {
|
||||||
|
bail!(
|
||||||
|
"reverse round-trip mismatch in '{}' (expected original input):\ninput:\n{}\nresult:\n{}",
|
||||||
|
case.note,
|
||||||
|
pretty_regorus(&input),
|
||||||
|
pretty_regorus(&renormalized),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(" {} cases passed in {file}", test.cases.len());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_resources("tests/azure_policy/normalization/cases/**/*.yaml")]
|
||||||
|
fn run(path: &str) {
|
||||||
|
run_yaml_test(path).unwrap()
|
||||||
|
}
|
||||||
@@ -12,5 +12,8 @@ mod lexer;
|
|||||||
mod parser;
|
mod parser;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
|
#[cfg(feature = "azure_policy")]
|
||||||
|
mod azure_policy;
|
||||||
|
|
||||||
#[cfg(feature = "rvm")]
|
#[cfg(feature = "rvm")]
|
||||||
mod rvm;
|
mod rvm;
|
||||||
|
|||||||
Reference in New Issue
Block a user