mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
8 Commits
copilot/fi
...
regorus-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acf7f7a25e | ||
|
|
86b4a279fa | ||
|
|
5467cd9e69 | ||
|
|
dae3052781 | ||
|
|
3111bf58f2 | ||
|
|
be3fde7706 | ||
|
|
d2c483e93e | ||
|
|
093e50f0a1 |
2
.github/copilot-setup-steps.yml
vendored
2
.github/copilot-setup-steps.yml
vendored
@@ -8,3 +8,5 @@ steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0 # full history needed for git diff against main
|
||||
- run: git fetch origin main:refs/remotes/origin/main
|
||||
name: Ensure origin/main ref is available for diff computation
|
||||
|
||||
28
.github/skills/code-review/SKILL.md
vendored
28
.github/skills/code-review/SKILL.md
vendored
@@ -25,15 +25,21 @@ Key constraints (details in copilot-instructions.md):
|
||||
## Step 1: Get the Diff
|
||||
|
||||
```bash
|
||||
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||
|| git merge-base origin/main HEAD 2>/dev/null)
|
||||
if [ -z "$BASE" ]; then
|
||||
echo "ERROR: Cannot find upstream/main or origin/main. Cannot determine review scope."
|
||||
exit 1
|
||||
# Primary: use gh pr diff (works in cloud agent + any PR context).
|
||||
# Fallback: git merge-base for local non-PR usage.
|
||||
if gh pr diff --name-only >/dev/null 2>&1; then
|
||||
echo "---STAT---"
|
||||
gh pr diff --name-only
|
||||
echo "---DIFF---"
|
||||
gh pr diff
|
||||
else
|
||||
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||
|| git merge-base origin/main HEAD 2>/dev/null \
|
||||
|| git merge-base main HEAD 2>/dev/null)
|
||||
echo "Reviewing changes since: $BASE"
|
||||
git diff "$BASE"..HEAD --stat
|
||||
git diff "$BASE"..HEAD
|
||||
fi
|
||||
echo "Reviewing changes since: $BASE"
|
||||
git diff "$BASE"..HEAD --stat
|
||||
git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
||||
```
|
||||
|
||||
If the diff is empty, stop and report: "No changes found to review."
|
||||
@@ -196,3 +202,9 @@ one pass. If any were skipped, note them and briefly assess.
|
||||
### Summary
|
||||
|
||||
X findings (N critical, N high, N medium, N low). One sentence overall assessment.
|
||||
|
||||
### Output
|
||||
|
||||
After generating the report above, write the COMPLETE report to `/tmp/code-review-report.md`
|
||||
using the `create` tool or shell. This ensures the full report is preserved even if
|
||||
display output is truncated.
|
||||
|
||||
59
.github/skills/deep-review/SKILL.md
vendored
59
.github/skills/deep-review/SKILL.md
vendored
@@ -40,22 +40,25 @@ Use `read_agent` with `wait: true` to wait for each background agent.
|
||||
## Step 1: Get the Diff and Build Inventory
|
||||
|
||||
```bash
|
||||
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||
|| git merge-base origin/main HEAD 2>/dev/null)
|
||||
if [ -z "$BASE" ]; then
|
||||
echo "ERROR: Cannot find upstream/main or origin/main."
|
||||
exit 1
|
||||
# Primary: use gh pr diff (works in cloud agent + any PR context).
|
||||
# Fallback: git merge-base for local non-PR usage.
|
||||
if gh pr diff --name-only >/dev/null 2>&1; then
|
||||
echo "---STAT---"
|
||||
gh pr diff --name-only
|
||||
echo "---DIFF---"
|
||||
gh pr diff
|
||||
else
|
||||
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||
|| git merge-base origin/main HEAD 2>/dev/null \
|
||||
|| git merge-base main HEAD 2>/dev/null)
|
||||
echo "Reviewing changes since: $BASE"
|
||||
git diff "$BASE"..HEAD --stat
|
||||
git diff "$BASE"..HEAD
|
||||
fi
|
||||
echo "Reviewing changes since: $BASE"
|
||||
git diff "$BASE"..HEAD --stat
|
||||
git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/' | head -2000
|
||||
```
|
||||
|
||||
If the diff is empty, stop and report: "No changes found to review."
|
||||
|
||||
**Scope rule:** Focus on code files (`*.rs`, `*.toml`, examples). Do NOT pass
|
||||
docs/config diffs to agents.
|
||||
|
||||
**Build a risk-classified inventory.** List every changed function, struct,
|
||||
impl, trait, pub item, and significant code block. Number them and tag with
|
||||
risk predicates:
|
||||
@@ -106,8 +109,10 @@ Use `model: "gpt-5.4"` in the task tool call (provides model diversity).
|
||||
> Get the diff:
|
||||
> ```
|
||||
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||
> || git merge-base origin/main HEAD 2>/dev/null)
|
||||
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
||||
> || git merge-base origin/main HEAD 2>/dev/null \
|
||||
> || git merge-base main HEAD 2>/dev/null)
|
||||
> # If no merge-base, use: gh pr diff
|
||||
> git diff "$BASE"..HEAD # or: gh pr diff
|
||||
> ```
|
||||
>
|
||||
> Key regorus constraints:
|
||||
@@ -161,8 +166,10 @@ Use `model: "claude-opus-4.6"` in the task tool call.
|
||||
> Get the diff AND read full source files for context:
|
||||
> ```
|
||||
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||
> || git merge-base origin/main HEAD 2>/dev/null)
|
||||
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
||||
> || git merge-base origin/main HEAD 2>/dev/null \
|
||||
> || git merge-base main HEAD 2>/dev/null)
|
||||
> # If no merge-base, use: gh pr diff
|
||||
> git diff "$BASE"..HEAD # or: gh pr diff
|
||||
> ```
|
||||
> Then use `view` to read the full source files that were changed.
|
||||
>
|
||||
@@ -219,8 +226,10 @@ Use the default model (no `model` parameter).
|
||||
> Get the diff:
|
||||
> ```
|
||||
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||
> || git merge-base origin/main HEAD 2>/dev/null)
|
||||
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
||||
> || git merge-base origin/main HEAD 2>/dev/null \
|
||||
> || git merge-base main HEAD 2>/dev/null)
|
||||
> # If no merge-base, use: gh pr diff
|
||||
> git diff "$BASE"..HEAD # or: gh pr diff
|
||||
> ```
|
||||
> Use `view` to read surrounding context.
|
||||
>
|
||||
@@ -439,8 +448,10 @@ Launch **1 general-purpose agent in background mode**.
|
||||
> Get the diff:
|
||||
> ```
|
||||
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||
> || git merge-base origin/main HEAD 2>/dev/null)
|
||||
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
||||
> || git merge-base origin/main HEAD 2>/dev/null \
|
||||
> || git merge-base main HEAD 2>/dev/null)
|
||||
> # If no merge-base, use: gh pr diff
|
||||
> git diff "$BASE"..HEAD # or: gh pr diff
|
||||
> ```
|
||||
> Use `view` to read full source files.
|
||||
>
|
||||
@@ -481,8 +492,8 @@ Launch **1 general-purpose agent in background mode**.
|
||||
|
||||
## Step 5: Synthesize and Report
|
||||
|
||||
**IMPORTANT:** This is the primary output. Everything above was preparation.
|
||||
Keep the report COMPACT — one finding per block, no filler prose.
|
||||
**CRITICAL:** Write the report to `/tmp/deep-review-report.md` FIRST, then display it.
|
||||
Use a shell command to write the file before any other output in this step.
|
||||
|
||||
Apply verdicts from the adversarial verifier:
|
||||
- **CONFIRMED**: keep at stated severity
|
||||
@@ -522,3 +533,9 @@ would catch it. If not, name the minimal test that should exist.
|
||||
X findings (N critical, N high, N medium, N low). Y "likely" findings.
|
||||
Z dropped (one-line reasons).
|
||||
Risk assessment in one sentence.
|
||||
|
||||
---
|
||||
|
||||
**Remember:** The report above MUST be written to `/tmp/deep-review-report.md` at the
|
||||
START of Step 5 (before displaying it). Use shell: `cat > /tmp/deep-review-report.md << 'REPORT_EOF'`
|
||||
... report content ... `REPORT_EOF`
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.10.1](https://github.com/microsoft/regorus/compare/regorus-v0.10.0...regorus-v0.10.1) - 2026-05-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- *(ffi)* eliminate aliasing UB + add Azure Policy JSON compilation FFI ([#727](https://github.com/microsoft/regorus/pull/727))
|
||||
- *(interpreter,rvm)* correct partial object rule iteration and classification ([#718](https://github.com/microsoft/regorus/pull/718))
|
||||
- *(copilot)* robust diff computation for cloud agent environments ([#709](https://github.com/microsoft/regorus/pull/709))
|
||||
|
||||
### Other
|
||||
|
||||
- *(azure_policy)* reduce AliasRegistry allocations via Rc sharing ([#725](https://github.com/microsoft/regorus/pull/725))
|
||||
- *(normalizer)* use Rc<str> interning to reduce alias resolution allocations ([#726](https://github.com/microsoft/regorus/pull/726))
|
||||
- *(deps)* bump the rust-dependencies group across 5 directories with 2 updates ([#724](https://github.com/microsoft/regorus/pull/724))
|
||||
- *(deps)* bump the rust-dependencies group across 5 directories with 4 updates ([#717](https://github.com/microsoft/regorus/pull/717))
|
||||
|
||||
## [0.10.0] - 2026-05-05
|
||||
|
||||
### Added
|
||||
|
||||
75
Cargo.lock
generated
75
Cargo.lock
generated
@@ -180,9 +180,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
version = "1.2.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -474,9 +474,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
|
||||
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
@@ -650,9 +650,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
dependencies = [
|
||||
"foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -832,7 +835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -866,9 +869,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.97"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -878,9 +881,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
|
||||
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytecount",
|
||||
@@ -947,9 +950,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.16.4"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
|
||||
checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -957,6 +960,12 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
@@ -1344,14 +1353,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
|
||||
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"fluent-uri",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.1",
|
||||
"itoa",
|
||||
"micromap",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"serde_json",
|
||||
@@ -1388,7 +1399,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "regorus"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -1399,7 +1410,7 @@ dependencies = [
|
||||
"dashmap",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"hashbrown 0.16.1",
|
||||
"hashbrown 0.17.1",
|
||||
"icu_casemap",
|
||||
"indexmap",
|
||||
"ipnet",
|
||||
@@ -1430,7 +1441,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regorus-mimalloc"
|
||||
version = "2.2.6"
|
||||
version = "2.2.7"
|
||||
dependencies = [
|
||||
"regorus-mimalloc-sys",
|
||||
]
|
||||
@@ -1545,9 +1556,9 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@@ -1830,9 +1841,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1843,9 +1854,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
dependencies = [
|
||||
"quote 1.0.45",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1853,9 +1864,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2 1.0.106",
|
||||
@@ -1866,9 +1877,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1909,9 +1920,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.97"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602"
|
||||
checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -2182,9 +2193,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -8,7 +8,7 @@ members = [
|
||||
[package]
|
||||
name = "regorus"
|
||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
edition = "2021"
|
||||
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
@@ -99,7 +99,7 @@ rand = ["dep:rand"]
|
||||
anyhow = { version = "1.0.102", default-features = false }
|
||||
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
|
||||
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
|
||||
hashbrown = { version = "0.16", default-features = false, features = ["default-hasher"], optional = true }
|
||||
hashbrown = { version = "0.17", default-features = false, features = ["default-hasher"], optional = true }
|
||||
lazy_static = { version = "1.4.0", default-features = false }
|
||||
thiserror = { version = "2.0", default-features = false }
|
||||
|
||||
@@ -114,7 +114,7 @@ regex = {version = "1.12.3", optional = true, default-features = false }
|
||||
semver = {version = "1.0.28", optional = true, default-features = false }
|
||||
url = { version = "2.5.4", optional = true }
|
||||
uuid = { version = "1.22.0", default-features = false, features = ["v4", "fast-rng"], optional = true }
|
||||
jsonschema = { version = "0.45.1", default-features = false, optional = true }
|
||||
jsonschema = { version = "0.46.5", default-features = false, optional = true }
|
||||
chrono = { version = "0.4.44", optional = true }
|
||||
chrono-tz = { version = "0.10.1", optional = true }
|
||||
ipnet = { version = "2.12.0", optional = true, default-features = false }
|
||||
@@ -127,8 +127,8 @@ rand = { version = "0.10.0", default-features = false, features = ["thread_rng"]
|
||||
# Causes the project to link with the Spectre-mitigated CRT and libs.
|
||||
msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true }
|
||||
dashmap = { version = "6.1", default-features = false, optional = true }
|
||||
lru = { version = "0.16", default-features = false, optional = true }
|
||||
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.6", optional = true }
|
||||
lru = { version = "0.18", default-features = false, optional = true }
|
||||
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.7", optional = true }
|
||||
|
||||
# rvm related deps
|
||||
indexmap = { version = "2.13.1", default-features = false, features = ["serde"], optional = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<RegorusPackageVersion>0.10.0</RegorusPackageVersion>
|
||||
<RegorusPackageVersion>0.10.1</RegorusPackageVersion>
|
||||
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -150,3 +150,76 @@ const string ContextJson = """
|
||||
var allowed = RbacEngine.EvaluateCondition(Condition, ContextJson);
|
||||
Console.WriteLine($"RBAC condition allowed: {allowed}");
|
||||
```
|
||||
|
||||
## Azure Policy JSON Evaluation
|
||||
|
||||
Compile and evaluate Azure Policy JSON `policyRule` definitions directly — no Rego translation required.
|
||||
The `AzurePolicyCompiler` compiles JSON policy rules into RVM programs that can be executed with the `Rvm` engine.
|
||||
|
||||
```csharp
|
||||
using Regorus;
|
||||
|
||||
// 1. Load alias definitions for the resource provider
|
||||
const string AliasesJson = """
|
||||
[{
|
||||
"namespace": "Microsoft.Storage",
|
||||
"resourceTypes": [{
|
||||
"resourceType": "storageAccounts",
|
||||
"aliases": [{
|
||||
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||
"paths": []
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
""";
|
||||
|
||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
||||
|
||||
// 2. Compile a JSON policy rule (the native Azure Policy language)
|
||||
const string PolicyRule = """
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
|
||||
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}
|
||||
""";
|
||||
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, PolicyRule);
|
||||
|
||||
// 3. Normalize an ARM resource and evaluate
|
||||
var armResource = """
|
||||
{
|
||||
"type": "Microsoft.Storage/storageAccounts",
|
||||
"name": "mystorage",
|
||||
"properties": { "supportsHttpsTrafficOnly": false }
|
||||
}
|
||||
""";
|
||||
var envelope = registry.NormalizeAndWrap(armResource);
|
||||
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(envelope!);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
// result: {"effect": "deny"} for non-compliant, "<undefined>" for compliant
|
||||
Console.WriteLine($"Policy result: {result}");
|
||||
```
|
||||
|
||||
**Context-dependent policies:** If your policy uses context functions like
|
||||
`subscription()`, `resourceGroup()`, or `requestContext()`, you must also set
|
||||
the VM context separately:
|
||||
|
||||
```csharp
|
||||
// The context JSON from NormalizeAndWrap is in the input envelope,
|
||||
// but must also be provided to the VM's ambient context:
|
||||
vm.SetContextJson(contextJson);
|
||||
```
|
||||
|
||||
You can also compile full policy definitions (with parameters) using
|
||||
`AzurePolicyCompiler.CompilePolicyDefinition()`. See
|
||||
`bindings/csharp/Regorus.Tests/AzurePolicyCompilerTests.cs` for comprehensive examples.
|
||||
|
||||
@@ -43,31 +43,28 @@ public class AliasRegistryTests
|
||||
[TestMethod]
|
||||
public void Create_and_dispose_succeeds()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
using var registry = AliasRegistry.Empty();
|
||||
Assert.AreEqual(0, registry.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LoadJson_populates_registry()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
||||
Assert.AreEqual(1, registry.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void LoadManifest_populates_registry()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadManifest(ManifestJson);
|
||||
using var registry = AliasRegistry.FromManifest(ManifestJson);
|
||||
Assert.AreEqual(1, registry.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NormalizeAndWrap_produces_envelope()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
||||
|
||||
var resource = @"{
|
||||
""name"": ""acct1"",
|
||||
@@ -93,8 +90,7 @@ public class AliasRegistryTests
|
||||
[TestMethod]
|
||||
public void NormalizeAndWrap_with_context_and_parameters()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
||||
|
||||
var resource = @"{
|
||||
""name"": ""acct1"",
|
||||
@@ -115,8 +111,7 @@ public class AliasRegistryTests
|
||||
[TestMethod]
|
||||
public void Denormalize_restores_properties()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
||||
|
||||
var normalized = @"{
|
||||
""name"": ""acct1"",
|
||||
@@ -137,8 +132,7 @@ public class AliasRegistryTests
|
||||
[TestMethod]
|
||||
public void Round_trip_normalize_then_denormalize()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(AliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
||||
|
||||
var resource = @"{
|
||||
""name"": ""acct1"",
|
||||
@@ -166,8 +160,7 @@ public class AliasRegistryTests
|
||||
[TestMethod]
|
||||
public void DataPlane_manifest_normalize()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadManifest(ManifestJson);
|
||||
using var registry = AliasRegistry.FromManifest(ManifestJson);
|
||||
|
||||
var resource = @"{
|
||||
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
|
||||
@@ -185,7 +178,7 @@ public class AliasRegistryTests
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void LoadJson_invalid_throws()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson("not valid json");
|
||||
using var builder = new AliasRegistryBuilder();
|
||||
builder.LoadJson("not valid json");
|
||||
}
|
||||
}
|
||||
|
||||
436
bindings/csharp/Regorus.Tests/AzurePolicyCompilerTests.cs
Normal file
436
bindings/csharp/Regorus.Tests/AzurePolicyCompilerTests.cs
Normal file
@@ -0,0 +1,436 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Regorus;
|
||||
|
||||
namespace Regorus.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="AzurePolicyCompiler"/> — compiling Azure Policy JSON
|
||||
/// policyRule and policyDefinition into RVM programs and evaluating them.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class AzurePolicyCompilerTests
|
||||
{
|
||||
// -----------------------------------------------------------------------
|
||||
// Test data
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private const string StorageAliasesJson = @"[{
|
||||
""namespace"": ""Microsoft.Storage"",
|
||||
""resourceTypes"": [{
|
||||
""resourceType"": ""storageAccounts"",
|
||||
""capabilities"": ""SupportsTags, SupportsLocation"",
|
||||
""aliases"": [
|
||||
{
|
||||
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
|
||||
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
|
||||
""paths"": []
|
||||
},
|
||||
{
|
||||
""name"": ""Microsoft.Storage/storageAccounts/minimumTlsVersion"",
|
||||
""defaultPath"": ""properties.minimumTlsVersion"",
|
||||
""paths"": []
|
||||
}
|
||||
]
|
||||
}]
|
||||
}]";
|
||||
|
||||
/// <summary>Simple policy rule that checks the resource type.</summary>
|
||||
private const string SimpleAuditRule = @"{
|
||||
""if"": {
|
||||
""field"": ""type"",
|
||||
""equals"": ""Microsoft.Storage/storageAccounts""
|
||||
},
|
||||
""then"": { ""effect"": ""audit"" }
|
||||
}";
|
||||
|
||||
/// <summary>Policy rule that uses an alias to check HTTPS-only.</summary>
|
||||
private const string HttpsDenyRule = @"{
|
||||
""if"": {
|
||||
""allOf"": [
|
||||
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
|
||||
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
|
||||
]
|
||||
},
|
||||
""then"": { ""effect"": ""deny"" }
|
||||
}";
|
||||
|
||||
/// <summary>Full policy definition with parameters.</summary>
|
||||
private const string PolicyDefinitionWithParams = @"{
|
||||
""displayName"": ""Require HTTPS for storage accounts"",
|
||||
""policyType"": ""Custom"",
|
||||
""mode"": ""Indexed"",
|
||||
""parameters"": {
|
||||
""effect"": {
|
||||
""type"": ""String"",
|
||||
""defaultValue"": ""deny""
|
||||
}
|
||||
},
|
||||
""policyRule"": {
|
||||
""if"": {
|
||||
""allOf"": [
|
||||
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
|
||||
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
|
||||
]
|
||||
},
|
||||
""then"": { ""effect"": ""[parameters('effect')]"" }
|
||||
}
|
||||
}";
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Wrap a normalized resource JSON and parameters into the input envelope
|
||||
/// expected by compiled Azure Policy RVM programs.
|
||||
/// </summary>
|
||||
private static string WrapInput(string resourceJson, string parametersJson = "{}")
|
||||
{
|
||||
return $@"{{""resource"": {resourceJson}, ""parameters"": {parametersJson}}}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile a policy rule, load it into an RVM, set input, and execute.
|
||||
/// Returns the result string from <c>ExecuteEntryPoint("main")</c>.
|
||||
/// </summary>
|
||||
private static string? CompileAndEval(
|
||||
AliasRegistry? registry,
|
||||
string policyRuleJson,
|
||||
string inputJson)
|
||||
{
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, policyRuleJson);
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(inputJson);
|
||||
return vm.ExecuteEntryPoint("main");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// CompilePolicyRule tests
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[TestMethod]
|
||||
public void CompilePolicyRule_no_aliases_succeeds()
|
||||
{
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
|
||||
Assert.IsNotNull(program);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CompilePolicyRule_with_aliases_succeeds()
|
||||
{
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
||||
Assert.IsNotNull(program);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void CompilePolicyRule_null_json_throws()
|
||||
{
|
||||
AzurePolicyCompiler.CompilePolicyRule(null, null!);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void CompilePolicyRule_invalid_json_throws()
|
||||
{
|
||||
AzurePolicyCompiler.CompilePolicyRule(null, "not valid json");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// CompilePolicyDefinition tests
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[TestMethod]
|
||||
public void CompilePolicyDefinition_no_aliases_succeeds()
|
||||
{
|
||||
using var program = AzurePolicyCompiler.CompilePolicyDefinition(null, PolicyDefinitionWithParams);
|
||||
Assert.IsNotNull(program);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CompilePolicyDefinition_with_aliases_succeeds()
|
||||
{
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
using var program = AzurePolicyCompiler.CompilePolicyDefinition(registry, PolicyDefinitionWithParams);
|
||||
Assert.IsNotNull(program);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void CompilePolicyDefinition_null_json_throws()
|
||||
{
|
||||
AzurePolicyCompiler.CompilePolicyDefinition(null, null!);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void CompilePolicyDefinition_invalid_json_throws()
|
||||
{
|
||||
AzurePolicyCompiler.CompilePolicyDefinition(null, @"{""not"": ""a definition""}");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// End-to-end evaluation tests
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[TestMethod]
|
||||
public void Eval_simple_rule_matching_resource_returns_effect()
|
||||
{
|
||||
var input = WrapInput(
|
||||
@"{""type"": ""microsoft.storage/storageaccounts""}");
|
||||
|
||||
var result = CompileAndEval(null, SimpleAuditRule, input);
|
||||
Assert.IsNotNull(result, "expected a result for matching resource");
|
||||
|
||||
var doc = JsonNode.Parse(result!)!;
|
||||
Assert.AreEqual("audit", doc["effect"]?.GetValue<string>(),
|
||||
$"expected 'audit' effect, got: {result}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Eval_simple_rule_non_matching_resource_returns_undefined()
|
||||
{
|
||||
var input = WrapInput(
|
||||
@"{""type"": ""microsoft.compute/virtualmachines""}");
|
||||
|
||||
var result = CompileAndEval(null, SimpleAuditRule, input);
|
||||
Assert.IsNotNull(result);
|
||||
StringAssert.Contains(result!, "undefined",
|
||||
"expected undefined for non-matching resource type");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Eval_alias_rule_non_compliant_returns_deny()
|
||||
{
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
// Non-compliant: HTTPS not enabled (normalized/lowercased form)
|
||||
var input = WrapInput(
|
||||
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": false}");
|
||||
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(input);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
var doc = JsonNode.Parse(result!)!;
|
||||
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
|
||||
$"expected 'deny' for non-compliant resource, got: {result}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Eval_alias_rule_compliant_returns_undefined()
|
||||
{
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
// Compliant: HTTPS enabled
|
||||
var input = WrapInput(
|
||||
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": true}");
|
||||
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(input);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
Assert.IsNotNull(result);
|
||||
StringAssert.Contains(result!, "undefined",
|
||||
"expected undefined for compliant resource");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Eval_definition_with_default_parameters()
|
||||
{
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
using var program = AzurePolicyCompiler.CompilePolicyDefinition(
|
||||
registry, PolicyDefinitionWithParams);
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
|
||||
// Non-compliant resource
|
||||
var input = WrapInput(
|
||||
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": false}");
|
||||
vm.SetInputJson(input);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
var doc = JsonNode.Parse(result!)!;
|
||||
// Default parameter value is "deny"
|
||||
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
|
||||
$"expected default 'deny' effect, got: {result}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Eval_with_normalized_arm_resource_end_to_end()
|
||||
{
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
// Simulate the full production flow:
|
||||
// 1. Start with an ARM resource
|
||||
var armResource = @"{
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
""name"": ""mystorage"",
|
||||
""location"": ""eastus"",
|
||||
""properties"": {
|
||||
""supportsHttpsTrafficOnly"": false,
|
||||
""minimumTlsVersion"": ""TLS1_0""
|
||||
}
|
||||
}";
|
||||
|
||||
// 2. Normalize via AliasRegistry
|
||||
var normalizedEnvelope = registry.NormalizeAndWrap(
|
||||
armResource,
|
||||
apiVersion: null,
|
||||
contextJson: "{}",
|
||||
parametersJson: "{}");
|
||||
Assert.IsNotNull(normalizedEnvelope);
|
||||
|
||||
// 3. Compile the policy rule
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
||||
|
||||
// 4. Execute
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(normalizedEnvelope!);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
var doc = JsonNode.Parse(result!)!;
|
||||
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
|
||||
$"expected 'deny' for non-HTTPS storage account, got: {result}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Eval_normalized_compliant_resource_end_to_end()
|
||||
{
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
var armResource = @"{
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
""name"": ""secureastorage"",
|
||||
""location"": ""westus"",
|
||||
""properties"": {
|
||||
""supportsHttpsTrafficOnly"": true,
|
||||
""minimumTlsVersion"": ""TLS1_2""
|
||||
}
|
||||
}";
|
||||
|
||||
var normalizedEnvelope = registry.NormalizeAndWrap(
|
||||
armResource,
|
||||
apiVersion: null,
|
||||
contextJson: "{}",
|
||||
parametersJson: "{}");
|
||||
Assert.IsNotNull(normalizedEnvelope);
|
||||
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(normalizedEnvelope!);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
Assert.IsNotNull(result);
|
||||
StringAssert.Contains(result!, "undefined",
|
||||
"expected undefined for compliant HTTPS storage account");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Program_can_be_serialized_and_reloaded()
|
||||
{
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
|
||||
|
||||
// Serialize to binary
|
||||
var binary = program.SerializeBinary();
|
||||
Assert.IsTrue(binary.Length > 0, "serialized program should not be empty");
|
||||
|
||||
// Deserialize and run
|
||||
using var restored = Program.DeserializeBinary(binary, out var isPartial);
|
||||
Assert.IsFalse(isPartial, "program should not be partial");
|
||||
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(restored);
|
||||
var input = WrapInput(@"{""type"": ""microsoft.storage/storageaccounts""}");
|
||||
vm.SetInputJson(input);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
Assert.IsNotNull(result);
|
||||
var doc = JsonNode.Parse(result!)!;
|
||||
Assert.AreEqual("audit", doc["effect"]?.GetValue<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Program_generates_listing()
|
||||
{
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
|
||||
var listing = program.GenerateListing();
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(listing),
|
||||
"generated listing should not be empty");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Context-dependent policy tests
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/// Policy rule that uses subscription() context function.
|
||||
private const string ContextPolicyRule = @"{
|
||||
""if"": {
|
||||
""allOf"": [
|
||||
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
|
||||
{ ""value"": ""[subscription().subscriptionId]"", ""equals"": ""sub-123"" }
|
||||
]
|
||||
},
|
||||
""then"": { ""effect"": ""deny"" }
|
||||
}";
|
||||
|
||||
[TestMethod]
|
||||
public void Eval_context_policy_with_set_context_returns_effect()
|
||||
{
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, ContextPolicyRule);
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
|
||||
vm.SetContextJson(@"{""subscription"": {""subscriptionId"": ""sub-123""}}");
|
||||
|
||||
var input = WrapInput(
|
||||
@"{""type"": ""microsoft.storage/storageaccounts""}");
|
||||
vm.SetInputJson(input);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
Assert.IsNotNull(result);
|
||||
var doc = JsonNode.Parse(result!)!;
|
||||
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
|
||||
$"expected 'deny' with matching context, got: {result}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Eval_context_policy_without_context_returns_undefined()
|
||||
{
|
||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, ContextPolicyRule);
|
||||
using var vm = new Rvm();
|
||||
vm.LoadProgram(program);
|
||||
|
||||
// No context set — subscription() will be undefined
|
||||
var input = WrapInput(
|
||||
@"{""type"": ""microsoft.storage/storageaccounts""}");
|
||||
vm.SetInputJson(input);
|
||||
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
Assert.IsNotNull(result);
|
||||
StringAssert.Contains(result!, "undefined",
|
||||
"expected undefined without context set");
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,7 @@ public class AzurePolicyTests
|
||||
[TestMethod]
|
||||
public void AliasRegistry_NormalizeAndWrap_produces_input_envelope()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(StorageAliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
var result = registry.NormalizeAndWrap(
|
||||
StorageResourceJson,
|
||||
@@ -84,8 +83,7 @@ public class AzurePolicyTests
|
||||
[TestMethod]
|
||||
public void AliasRegistry_NormalizeAndWrap_flattens_properties()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(StorageAliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
var result = registry.NormalizeAndWrap(StorageResourceJson);
|
||||
Assert.IsNotNull(result);
|
||||
@@ -107,8 +105,7 @@ public class AzurePolicyTests
|
||||
[TestMethod]
|
||||
public void AliasRegistry_NormalizeAndWrap_preserves_type_field()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(StorageAliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
var result = registry.NormalizeAndWrap(StorageResourceJson);
|
||||
var doc = JsonNode.Parse(result!);
|
||||
@@ -125,8 +122,7 @@ public class AzurePolicyTests
|
||||
[TestMethod]
|
||||
public void AliasRegistry_NormalizeAndWrap_includes_parameters()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(StorageAliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
var parametersJson = @"{ ""effect"": ""Deny"" }";
|
||||
var result = registry.NormalizeAndWrap(
|
||||
@@ -143,8 +139,7 @@ public class AzurePolicyTests
|
||||
[TestMethod]
|
||||
public void AliasRegistry_Denormalize_roundtrips_correctly()
|
||||
{
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(StorageAliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
||||
|
||||
// Normalize the ARM resource.
|
||||
var envelope = registry.NormalizeAndWrap(StorageResourceJson);
|
||||
@@ -177,8 +172,7 @@ public class AzurePolicyTests
|
||||
}
|
||||
|
||||
var aliasesJson = File.ReadAllText(aliasesPath);
|
||||
using var registry = new AliasRegistry();
|
||||
registry.LoadJson(aliasesJson);
|
||||
using var registry = AliasRegistry.FromJson(aliasesJson);
|
||||
|
||||
// The test_aliases.json file contains multiple providers.
|
||||
Assert.IsTrue(registry.Length > 0,
|
||||
|
||||
@@ -8,51 +8,43 @@ using Regorus.Internal;
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages Azure Policy alias definitions used for resource normalization
|
||||
/// Immutable Azure Policy alias registry 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))
|
||||
internal AliasRegistry(RegorusAliasRegistryHandle handle)
|
||||
: base(handle, nameof(AliasRegistry))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
|
||||
/// Create an empty immutable alias registry.
|
||||
/// </summary>
|
||||
/// <param name="json">JSON array of ProviderAliases (e.g. from Get-AzPolicyAlias or ResourceTypesAndAliases.json)</param>
|
||||
public void LoadJson(string json)
|
||||
public static AliasRegistry Empty()
|
||||
{
|
||||
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||
{
|
||||
UseHandle(regPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_alias_registry_load_json(
|
||||
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
using var builder = new AliasRegistryBuilder();
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a data-plane policy manifest from a JSON string.
|
||||
/// Create an immutable alias registry from control-plane alias JSON.
|
||||
/// </summary>
|
||||
/// <param name="json">JSON object containing a DataPolicyManifest</param>
|
||||
public void LoadManifest(string json)
|
||||
public static AliasRegistry FromJson(string json)
|
||||
{
|
||||
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||
{
|
||||
UseHandle(regPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_alias_registry_load_manifest(
|
||||
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
using var builder = new AliasRegistryBuilder();
|
||||
builder.LoadJson(json);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an immutable alias registry from a data-plane manifest JSON document.
|
||||
/// </summary>
|
||||
public static AliasRegistry FromManifest(string json)
|
||||
{
|
||||
using var builder = new AliasRegistryBuilder();
|
||||
builder.LoadManifest(json);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,11 +66,6 @@ namespace Regorus
|
||||
/// 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 =>
|
||||
@@ -96,27 +83,22 @@ namespace Regorus
|
||||
(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));
|
||||
}));
|
||||
}
|
||||
|
||||
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 =>
|
||||
@@ -131,23 +113,16 @@ namespace Regorus
|
||||
(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);
|
||||
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
|
||||
UseHandle(regPtr =>
|
||||
{
|
||||
return ResultHelpers.GetStringResult(
|
||||
API.regorus_alias_registry_denormalize(
|
||||
(RegorusAliasRegistry*)regPtr,
|
||||
(byte*)normPtr, (byte*)apiPtr));
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
bindings/csharp/Regorus/AliasRegistryBuilder.cs
Normal file
69
bindings/csharp/Regorus/AliasRegistryBuilder.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Mutable, single-threaded builder for <see cref="AliasRegistry"/>.
|
||||
/// Load alias data, then call <see cref="Build"/> to freeze the registry.
|
||||
/// </summary>
|
||||
public unsafe sealed class AliasRegistryBuilder : SafeHandleWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an empty alias registry builder.
|
||||
/// </summary>
|
||||
public AliasRegistryBuilder()
|
||||
: base(RegorusAliasRegistryBuilderHandle.Create(), nameof(AliasRegistryBuilder))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
|
||||
/// </summary>
|
||||
public void LoadJson(string json)
|
||||
{
|
||||
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||
{
|
||||
UseHandle(builderPtr =>
|
||||
{
|
||||
ResultHelpers.GetStringResult(API.regorus_alias_registry_builder_load_json(
|
||||
(RegorusAliasRegistryBuilder*)builderPtr,
|
||||
(byte*)jsonPtr));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a data-plane policy manifest from a JSON string.
|
||||
/// </summary>
|
||||
public void LoadManifest(string json)
|
||||
{
|
||||
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||
{
|
||||
UseHandle(builderPtr =>
|
||||
{
|
||||
ResultHelpers.GetStringResult(API.regorus_alias_registry_builder_load_manifest(
|
||||
(RegorusAliasRegistryBuilder*)builderPtr,
|
||||
(byte*)jsonPtr));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Freeze the builder into an immutable, thread-safe alias registry.
|
||||
/// </summary>
|
||||
public AliasRegistry Build()
|
||||
{
|
||||
return UseHandle(builderPtr =>
|
||||
{
|
||||
var registryPtr = ResultHelpers.GetPointerResult(
|
||||
API.regorus_alias_registry_builder_build((RegorusAliasRegistryBuilder*)builderPtr));
|
||||
return new AliasRegistry(RegorusAliasRegistryHandle.FromPointer(registryPtr));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
183
bindings/csharp/Regorus/AzurePolicyCompiler.cs
Normal file
183
bindings/csharp/Regorus/AzurePolicyCompiler.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides static methods for compiling Azure Policy JSON definitions
|
||||
/// into RVM programs that can be executed by <see cref="Rvm"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class bridges the gap between Azure Policy JSON (the native
|
||||
/// Azure policy language with <c>policyRule</c>, <c>field</c>,
|
||||
/// <c>equals</c>, etc.) and Regorus's RVM execution engine.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Typical workflow:</b>
|
||||
/// </para>
|
||||
/// <list type="number">
|
||||
/// <item>Load alias definitions with <see cref="AliasRegistryBuilder"/> and freeze them into an <see cref="AliasRegistry"/>.</item>
|
||||
/// <item>Normalize the ARM resource via <see cref="AliasRegistry.NormalizeAndWrap"/>.</item>
|
||||
/// <item>Compile the JSON policyRule with <see cref="CompilePolicyRule"/> or the
|
||||
/// full definition with <see cref="CompilePolicyDefinition"/>.</item>
|
||||
/// <item>Execute the resulting <see cref="Program"/> in an <see cref="Rvm"/>
|
||||
/// instance with the normalized input.</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Context-dependent policies:</b> Policies that use context functions
|
||||
/// such as <c>subscription()</c>, <c>resourceGroup()</c>, or
|
||||
/// <c>requestContext()</c> require the VM context to be set separately via
|
||||
/// <see cref="Rvm.SetContextJson"/> before execution. The context JSON
|
||||
/// returned by <see cref="AliasRegistry.NormalizeAndWrap"/> is passed as
|
||||
/// <c>input.context</c> but is <b>not</b> automatically wired into the VM's
|
||||
/// ambient context — the caller must do both:
|
||||
/// <c>vm.SetInputJson(envelope)</c> and <c>vm.SetContextJson(contextJson)</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static unsafe class AzurePolicyCompiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Compile an Azure Policy JSON policy rule into an RVM <see cref="Program"/>.
|
||||
/// </summary>
|
||||
/// <param name="aliasRegistry">
|
||||
/// Alias registry for resolving fully-qualified alias names in field
|
||||
/// references. Pass <c>null</c> if no alias resolution is needed.
|
||||
/// <para>
|
||||
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
|
||||
/// property paths and will silently produce incorrect evaluation results for
|
||||
/// policies that use aliases. Modify/Append effect policies will also skip
|
||||
/// the compile-time modifiability validation. Only pass <c>null</c> when the
|
||||
/// policy is known to contain no alias references (e.g. simple type/location
|
||||
/// checks or unit-test scenarios).
|
||||
/// </para>
|
||||
/// </param>
|
||||
/// <param name="policyRuleJson">
|
||||
/// JSON string containing the policyRule object, e.g.
|
||||
/// <c>{ "if": { "field": "type", "equals": "..." }, "then": { "effect": "deny" } }</c>
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A compiled <see cref="Program"/> ready to be loaded into an
|
||||
/// <see cref="Rvm"/> instance.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown when <paramref name="policyRuleJson"/> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <exception cref="Exception">
|
||||
/// Thrown when parsing or compilation fails.
|
||||
/// </exception>
|
||||
public static Program CompilePolicyRule(AliasRegistry? aliasRegistry, string policyRuleJson)
|
||||
{
|
||||
if (policyRuleJson is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policyRuleJson));
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(policyRuleJson, rulePtr =>
|
||||
{
|
||||
if (aliasRegistry is null)
|
||||
{
|
||||
var result = API.regorus_compile_azure_policy_rule(
|
||||
null, (byte*)rulePtr);
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return aliasRegistry.UseHandleForInterop(regPtr =>
|
||||
{
|
||||
var result = API.regorus_compile_azure_policy_rule(
|
||||
(RegorusAliasRegistry*)regPtr, (byte*)rulePtr);
|
||||
return GetProgramResult(result);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile a full Azure Policy definition JSON into an RVM <see cref="Program"/>.
|
||||
/// </summary>
|
||||
/// <param name="aliasRegistry">
|
||||
/// Alias registry for resolving fully-qualified alias names in field
|
||||
/// references. Pass <c>null</c> if no alias resolution is needed.
|
||||
/// <para>
|
||||
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
|
||||
/// property paths and will silently produce incorrect evaluation results for
|
||||
/// policies that use aliases. Modify/Append effect policies will also skip
|
||||
/// the compile-time modifiability validation. Only pass <c>null</c> when the
|
||||
/// policy is known to contain no alias references (e.g. simple type/location
|
||||
/// checks or unit-test scenarios).
|
||||
/// </para>
|
||||
/// </param>
|
||||
/// <param name="policyDefinitionJson">
|
||||
/// JSON string containing the full policy definition, which includes
|
||||
/// <c>policyRule</c>, <c>parameters</c>, <c>displayName</c>, etc.
|
||||
/// Accepted in both wrapped and unwrapped forms.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A compiled <see cref="Program"/> ready to be loaded into an
|
||||
/// <see cref="Rvm"/> instance.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown when <paramref name="policyDefinitionJson"/> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <exception cref="Exception">
|
||||
/// Thrown when parsing or compilation fails.
|
||||
/// </exception>
|
||||
public static Program CompilePolicyDefinition(AliasRegistry? aliasRegistry, string policyDefinitionJson)
|
||||
{
|
||||
if (policyDefinitionJson is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(policyDefinitionJson));
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(policyDefinitionJson, defnPtr =>
|
||||
{
|
||||
if (aliasRegistry is null)
|
||||
{
|
||||
var result = API.regorus_compile_azure_policy_definition(
|
||||
null, (byte*)defnPtr);
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return aliasRegistry.UseHandleForInterop(regPtr =>
|
||||
{
|
||||
var result = API.regorus_compile_azure_policy_definition(
|
||||
(RegorusAliasRegistry*)regPtr, (byte*)defnPtr);
|
||||
return GetProgramResult(result);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Program GetProgramResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
||||
{
|
||||
throw new Exception("Expected program pointer but got different data type");
|
||||
}
|
||||
|
||||
var handle = RegorusProgramHandle.FromPointer((IntPtr)result.pointer_value);
|
||||
return new Program(handle);
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +178,14 @@ namespace Regorus.Internal
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_input(RegorusRvm* vm, byte* input_json);
|
||||
|
||||
/// <summary>
|
||||
/// Set the context document for the RVM.
|
||||
/// The context provides host-supplied ambient data (e.g. resourceGroup(), subscription())
|
||||
/// that Azure Policy functions can access.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_rvm_set_context(RegorusRvm* vm, byte* context_json);
|
||||
|
||||
/// <summary>
|
||||
/// Execute the program.
|
||||
/// </summary>
|
||||
@@ -490,6 +498,20 @@ namespace Regorus.Internal
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_compile_policy_for_target", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_compile_policy_for_target(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len);
|
||||
|
||||
/// <summary>
|
||||
/// Compile an Azure Policy JSON policy rule into an RVM program.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_compile_azure_policy_rule", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_compile_azure_policy_rule(
|
||||
RegorusAliasRegistry* registry, byte* policy_rule_json);
|
||||
|
||||
/// <summary>
|
||||
/// Compile a full Azure Policy definition JSON into an RVM program.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_compile_azure_policy_definition", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_compile_azure_policy_definition(
|
||||
RegorusAliasRegistry* registry, byte* policy_definition_json);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Compiled Policy Methods
|
||||
@@ -673,10 +695,34 @@ namespace Regorus.Internal
|
||||
#region Alias Registry Methods
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty AliasRegistry.
|
||||
/// Create a new alias registry builder.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusAliasRegistry* regorus_alias_registry_new();
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusAliasRegistryBuilder* regorus_alias_registry_builder_new();
|
||||
|
||||
/// <summary>
|
||||
/// Drop an alias registry builder.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern void regorus_alias_registry_builder_drop(RegorusAliasRegistryBuilder* builder);
|
||||
|
||||
/// <summary>
|
||||
/// Load control-plane alias data into the builder.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_alias_registry_builder_load_json(RegorusAliasRegistryBuilder* builder, byte* json);
|
||||
|
||||
/// <summary>
|
||||
/// Load a data-plane policy manifest into the builder.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_alias_registry_builder_load_manifest(RegorusAliasRegistryBuilder* builder, byte* json);
|
||||
|
||||
/// <summary>
|
||||
/// Freeze a builder into an immutable alias registry.
|
||||
/// </summary>
|
||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_build", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
internal static extern RegorusResult regorus_alias_registry_builder_build(RegorusAliasRegistryBuilder* builder);
|
||||
|
||||
/// <summary>
|
||||
/// Drop an AliasRegistry.
|
||||
@@ -684,18 +730,6 @@ namespace Regorus.Internal
|
||||
[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>
|
||||
@@ -923,6 +957,14 @@ namespace Regorus.Internal
|
||||
public byte* content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for AliasRegistryBuilder.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal unsafe partial struct RegorusAliasRegistryBuilder
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for AliasRegistry.
|
||||
/// </summary>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public unsafe sealed class Program : SafeHandleWrapper
|
||||
{
|
||||
private Program(RegorusProgramHandle handle)
|
||||
internal Program(RegorusProgramHandle handle)
|
||||
: base(handle, nameof(Program))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -69,5 +69,29 @@ namespace Regorus.Internal
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static IntPtr GetPointerResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
||||
{
|
||||
throw new InvalidOperationException("Expected pointer result.");
|
||||
}
|
||||
|
||||
return (IntPtr)result.pointer_value;
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,24 @@ namespace Regorus
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the context document for the VM.
|
||||
/// The context provides host-supplied ambient data (e.g. resourceGroup(),
|
||||
/// subscription()) that Azure Policy functions can access via LoadContext
|
||||
/// instructions.
|
||||
/// </summary>
|
||||
public void SetContextJson(string contextJson)
|
||||
{
|
||||
Utf8Marshaller.WithUtf8(contextJson, contextPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_set_context((RegorusRvm*)vmPtr, (byte*)contextPtr));
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the execution mode (0 = run-to-completion, 1 = suspendable).
|
||||
/// </summary>
|
||||
|
||||
@@ -184,28 +184,48 @@ namespace Regorus
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RegorusAliasRegistryBuilderHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private RegorusAliasRegistryBuilderHandle() : base(ownsHandle: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static RegorusAliasRegistryBuilderHandle Create()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var raw = Internal.API.regorus_alias_registry_builder_new();
|
||||
if (raw is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create Regorus alias registry builder.");
|
||||
}
|
||||
|
||||
var handle = new RegorusAliasRegistryBuilderHandle();
|
||||
handle.SetHandle((IntPtr)raw);
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Internal.API.regorus_alias_registry_builder_drop((Internal.RegorusAliasRegistryBuilder*)handle);
|
||||
}
|
||||
SetHandle(IntPtr.Zero);
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -232,6 +232,9 @@ allow if {
|
||||
|
||||
Console.WriteLine("\n8. RVM host await (suspend/resume):");
|
||||
DemonstrateRvmHostAwait();
|
||||
|
||||
Console.WriteLine("\n9. Azure Policy JSON compilation:");
|
||||
DemonstrateAzurePolicyJsonCompilation();
|
||||
}
|
||||
|
||||
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
|
||||
@@ -492,4 +495,80 @@ allow if {
|
||||
var resumed = vm.Resume("{\"tier\":\"gold\"}");
|
||||
Console.WriteLine($"HostAwait resumed result: {resumed}");
|
||||
}
|
||||
|
||||
// Azure Policy JSON constants
|
||||
private const string STORAGE_ALIASES_JSON = @"[{
|
||||
""namespace"": ""Microsoft.Storage"",
|
||||
""resourceTypes"": [{
|
||||
""resourceType"": ""storageAccounts"",
|
||||
""capabilities"": ""SupportsTags, SupportsLocation"",
|
||||
""aliases"": [
|
||||
{
|
||||
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
|
||||
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
|
||||
""paths"": []
|
||||
}
|
||||
]
|
||||
}]
|
||||
}]";
|
||||
|
||||
private const string HTTPS_DENY_RULE = @"{
|
||||
""if"": {
|
||||
""allOf"": [
|
||||
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
|
||||
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
|
||||
]
|
||||
},
|
||||
""then"": { ""effect"": ""deny"" }
|
||||
}";
|
||||
|
||||
static void DemonstrateAzurePolicyJsonCompilation()
|
||||
{
|
||||
// 1. Set up alias registry
|
||||
using var registry = Regorus.AliasRegistry.FromJson(STORAGE_ALIASES_JSON);
|
||||
Console.WriteLine("Loaded storage account aliases");
|
||||
|
||||
// 2. Compile the JSON policy rule directly (no Rego needed)
|
||||
using var program = Regorus.AzurePolicyCompiler.CompilePolicyRule(registry, HTTPS_DENY_RULE);
|
||||
Console.WriteLine("Compiled Azure Policy JSON rule to RVM program");
|
||||
|
||||
// 3. Normalize an ARM resource
|
||||
var armResource = @"{
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
""name"": ""insecurestorage"",
|
||||
""location"": ""eastus"",
|
||||
""properties"": { ""supportsHttpsTrafficOnly"": false }
|
||||
}";
|
||||
var envelope = registry.NormalizeAndWrap(armResource, apiVersion: null, contextJson: "{}", parametersJson: "{}");
|
||||
Console.WriteLine($"Normalized ARM resource to evaluation envelope");
|
||||
|
||||
// 4. Execute in the RVM
|
||||
// Note: For policies using context functions (subscription(), resourceGroup()),
|
||||
// call vm.SetContextJson(contextJson) before execution. The context from
|
||||
// NormalizeAndWrap is in the envelope but must also be set on the VM separately.
|
||||
using var vm = new Regorus.Rvm();
|
||||
vm.LoadProgram(program);
|
||||
vm.SetInputJson(envelope!);
|
||||
// vm.SetContextJson(contextJson); // ← required for context-dependent policies
|
||||
var result = vm.ExecuteEntryPoint("main");
|
||||
Console.WriteLine($"Evaluation result (non-compliant): {result}");
|
||||
|
||||
// 5. Test with a compliant resource
|
||||
var compliantResource = @"{
|
||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
||||
""name"": ""securestorage"",
|
||||
""location"": ""eastus"",
|
||||
""properties"": { ""supportsHttpsTrafficOnly"": true }
|
||||
}";
|
||||
var compliantEnvelope = registry.NormalizeAndWrap(compliantResource, apiVersion: null, contextJson: "{}", parametersJson: "{}");
|
||||
using var vm2 = new Regorus.Rvm();
|
||||
vm2.LoadProgram(program);
|
||||
vm2.SetInputJson(compliantEnvelope!);
|
||||
var compliantResult = vm2.ExecuteEntryPoint("main");
|
||||
Console.WriteLine($"Evaluation result (compliant): {compliantResult}");
|
||||
|
||||
// 6. Demonstrate program serialization
|
||||
var binary = program.SerializeBinary();
|
||||
Console.WriteLine($"Serialized program size: {binary.Length} bytes");
|
||||
}
|
||||
}
|
||||
|
||||
69
bindings/ffi/Cargo.lock
generated
69
bindings/ffi/Cargo.lock
generated
@@ -172,9 +172,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
version = "1.2.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -353,9 +353,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
|
||||
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
@@ -508,9 +508,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
dependencies = [
|
||||
"foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -684,7 +687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -709,9 +712,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.97"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -721,9 +724,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
|
||||
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytecount",
|
||||
@@ -793,9 +796,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.16.4"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
|
||||
checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -803,6 +806,12 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
|
||||
|
||||
[[package]]
|
||||
name = "msvc_spectre_libs"
|
||||
version = "0.1.3"
|
||||
@@ -1073,14 +1082,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
|
||||
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"fluent-uri",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.1",
|
||||
"itoa",
|
||||
"micromap",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"serde_json",
|
||||
@@ -1117,7 +1128,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "regorus"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1125,7 +1136,7 @@ dependencies = [
|
||||
"dashmap",
|
||||
"data-encoding",
|
||||
"globset",
|
||||
"hashbrown 0.16.1",
|
||||
"hashbrown 0.17.1",
|
||||
"icu_casemap",
|
||||
"indexmap",
|
||||
"ipnet",
|
||||
@@ -1152,7 +1163,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regorus-ffi"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cbindgen",
|
||||
@@ -1163,7 +1174,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regorus-mimalloc"
|
||||
version = "2.2.6"
|
||||
version = "2.2.7"
|
||||
dependencies = [
|
||||
"regorus-mimalloc-sys",
|
||||
]
|
||||
@@ -1285,9 +1296,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@@ -1524,9 +1535,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1537,9 +1548,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1547,9 +1558,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1560,9 +1571,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[package]
|
||||
name = "regorus-ffi"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
edition = "2021"
|
||||
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
||||
|
||||
|
||||
@@ -5,66 +5,108 @@
|
||||
|
||||
#![cfg(feature = "azure_policy")]
|
||||
|
||||
use crate::common::{from_c_str, to_ref, RegorusResult, RegorusStatus};
|
||||
use crate::common::{from_c_str, to_ref, to_shared_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 alloc::sync::Arc;
|
||||
use anyhow::{anyhow, Result};
|
||||
use core::ffi::{c_char, c_void};
|
||||
use core::{mem, ptr};
|
||||
|
||||
use regorus::languages::azure_policy::aliases::AliasRegistry;
|
||||
|
||||
/// Opaque wrapper for `AliasRegistry`.
|
||||
pub struct RegorusAliasRegistry {
|
||||
registry: AliasRegistry,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Create a new, empty `AliasRegistry`.
|
||||
/// Mutable builder for `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))
|
||||
/// This handle is intentionally single-threaded and must not be used
|
||||
/// concurrently. Callers should finish loading alias data and then freeze it
|
||||
/// into a `RegorusAliasRegistry` via `regorus_alias_registry_builder_build`.
|
||||
pub struct RegorusAliasRegistryBuilder {
|
||||
registry: AliasRegistry,
|
||||
built: bool,
|
||||
}
|
||||
|
||||
/// Drop a `RegorusAliasRegistry`.
|
||||
impl RegorusAliasRegistryBuilder {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
registry: AliasRegistry::new(),
|
||||
built: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn registry_mut(&mut self) -> Result<&mut AliasRegistry> {
|
||||
if self.built {
|
||||
return Err(anyhow!("alias registry builder has already been built"));
|
||||
}
|
||||
Ok(&mut self.registry)
|
||||
}
|
||||
|
||||
fn build(&mut self) -> Result<RegorusAliasRegistry> {
|
||||
if self.built {
|
||||
return Err(anyhow!("alias registry builder has already been built"));
|
||||
}
|
||||
|
||||
self.built = true;
|
||||
Ok(RegorusAliasRegistry {
|
||||
registry: Arc::new(mem::replace(&mut self.registry, AliasRegistry::new())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Frozen, immutable alias registry.
|
||||
pub struct RegorusAliasRegistry {
|
||||
registry: Arc<AliasRegistry>,
|
||||
}
|
||||
|
||||
impl RegorusAliasRegistry {
|
||||
/// Return a shared reference to the inner registry for use by the compiler.
|
||||
pub(crate) fn inner(&self) -> Arc<AliasRegistry> {
|
||||
Arc::clone(&self.registry)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Builder lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Create a new, empty `AliasRegistry` builder.
|
||||
///
|
||||
/// The caller must eventually call `regorus_alias_registry_builder_drop`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_drop(registry: *mut RegorusAliasRegistry) {
|
||||
if let Ok(r) = to_ref(registry) {
|
||||
pub extern "C" fn regorus_alias_registry_builder_new() -> *mut RegorusAliasRegistryBuilder {
|
||||
Box::into_raw(Box::new(RegorusAliasRegistryBuilder::new()))
|
||||
}
|
||||
|
||||
/// Drop a `RegorusAliasRegistryBuilder`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_builder_drop(builder: *mut RegorusAliasRegistryBuilder) {
|
||||
if let Ok(builder) = to_ref(builder) {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(ptr::from_mut(r));
|
||||
let _ = Box::from_raw(ptr::from_mut(builder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Loading
|
||||
// Builder loading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Load control-plane alias data (array of `ProviderAliases`) into the registry.
|
||||
/// Load control-plane alias data (array of `ProviderAliases`) into the builder.
|
||||
///
|
||||
/// `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,
|
||||
pub extern "C" fn regorus_alias_registry_builder_load_json(
|
||||
builder: *mut RegorusAliasRegistryBuilder,
|
||||
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)?;
|
||||
to_ref(builder)?.registry_mut()?.load_from_json(&json_str)?;
|
||||
Ok(())
|
||||
}();
|
||||
|
||||
@@ -78,20 +120,20 @@ pub extern "C" fn regorus_alias_registry_load_json(
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a data-plane policy manifest into the registry.
|
||||
/// Load a data-plane policy manifest into the builder.
|
||||
///
|
||||
/// `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,
|
||||
pub extern "C" fn regorus_alias_registry_builder_load_manifest(
|
||||
builder: *mut RegorusAliasRegistryBuilder,
|
||||
json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<()> {
|
||||
let json_str = from_c_str(json)?;
|
||||
to_ref(registry)?
|
||||
.registry
|
||||
to_ref(builder)?
|
||||
.registry_mut()?
|
||||
.load_data_policy_manifest_json(&json_str)?;
|
||||
Ok(())
|
||||
}();
|
||||
@@ -106,16 +148,52 @@ pub extern "C" fn regorus_alias_registry_load_manifest(
|
||||
})
|
||||
}
|
||||
|
||||
/// Freeze a builder into an immutable `RegorusAliasRegistry`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_builder_build(
|
||||
builder: *mut RegorusAliasRegistryBuilder,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusAliasRegistry> {
|
||||
let registry = to_ref(builder)?.build()?;
|
||||
Ok(Box::into_raw(Box::new(registry)))
|
||||
}();
|
||||
|
||||
match output {
|
||||
Ok(registry) => RegorusResult::ok_pointer(registry as *mut c_void),
|
||||
Err(e) => {
|
||||
RegorusResult::err_with_message(RegorusStatus::InvalidArgument, format!("{e}"))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Queries
|
||||
// Frozen registry lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Drop a `RegorusAliasRegistry`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_alias_registry_drop(registry: *mut RegorusAliasRegistry) {
|
||||
if let Ok(registry) = to_ref(registry) {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(ptr::from_mut(registry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frozen registry 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 {
|
||||
pub extern "C" fn regorus_alias_registry_len(
|
||||
registry: *const RegorusAliasRegistry,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<i64> {
|
||||
let len = to_ref(registry)?.registry.len();
|
||||
let len = to_shared_ref(registry)?.registry.len();
|
||||
Ok(len as i64)
|
||||
}();
|
||||
|
||||
@@ -134,15 +212,9 @@ pub extern "C" fn regorus_alias_registry_len(registry: *mut RegorusAliasRegistry
|
||||
///
|
||||
/// 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,
|
||||
registry: *const RegorusAliasRegistry,
|
||||
resource_json: *const c_char,
|
||||
api_version: *const c_char,
|
||||
context_json: *const c_char,
|
||||
@@ -168,7 +240,7 @@ pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
|
||||
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(
|
||||
let wrapped = to_shared_ref(registry)?.registry.normalize_and_wrap(
|
||||
&resource,
|
||||
api_ver.as_deref(),
|
||||
Some(context),
|
||||
@@ -185,14 +257,9 @@ pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
|
||||
}
|
||||
|
||||
/// 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,
|
||||
registry: *const RegorusAliasRegistry,
|
||||
normalized_json: *const c_char,
|
||||
api_version: *const c_char,
|
||||
) -> RegorusResult {
|
||||
@@ -212,7 +279,7 @@ pub extern "C" fn regorus_alias_registry_denormalize(
|
||||
|
||||
let normalized = regorus::Value::from_json_str(&normalized_str)?;
|
||||
|
||||
let result = to_ref(registry)?
|
||||
let result = to_shared_ref(registry)?
|
||||
.registry
|
||||
.denormalize(&normalized, api_ver.as_deref());
|
||||
result.to_json_str()
|
||||
@@ -232,12 +299,10 @@ mod tests {
|
||||
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");
|
||||
@@ -248,12 +313,51 @@ mod tests {
|
||||
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
|
||||
}
|
||||
|
||||
fn assert_ok_pointer(r: &RegorusResult) -> *mut c_void {
|
||||
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
||||
assert!(matches!(
|
||||
r.data_type,
|
||||
crate::common::RegorusDataType::Pointer
|
||||
));
|
||||
assert!(!r.pointer_value.is_null());
|
||||
r.pointer_value
|
||||
}
|
||||
|
||||
fn build_registry_with_json(json: &str) -> *mut RegorusAliasRegistry {
|
||||
let builder = regorus_alias_registry_builder_new();
|
||||
let json = c(json);
|
||||
|
||||
let r = regorus_alias_registry_builder_load_json(builder, json.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let r = regorus_alias_registry_builder_build(builder);
|
||||
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
||||
regorus_result_drop(r);
|
||||
regorus_alias_registry_builder_drop(builder);
|
||||
registry
|
||||
}
|
||||
|
||||
fn build_registry_with_manifest(json: &str) -> *mut RegorusAliasRegistry {
|
||||
let builder = regorus_alias_registry_builder_new();
|
||||
let json = c(json);
|
||||
|
||||
let r = regorus_alias_registry_builder_load_manifest(builder, json.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let r = regorus_alias_registry_builder_build(builder);
|
||||
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
||||
regorus_result_drop(r);
|
||||
regorus_alias_registry_builder_drop(builder);
|
||||
registry
|
||||
}
|
||||
|
||||
const ALIASES: &str = r#"[{
|
||||
"namespace": "Microsoft.Storage",
|
||||
"resourceTypes": [{
|
||||
@@ -279,20 +383,21 @@ mod tests {
|
||||
}"#;
|
||||
|
||||
#[test]
|
||||
fn lifecycle_new_and_drop() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
assert!(!reg.is_null());
|
||||
regorus_alias_registry_drop(reg);
|
||||
fn lifecycle_builder_build_and_drop() {
|
||||
let builder = regorus_alias_registry_builder_new();
|
||||
assert!(!builder.is_null());
|
||||
|
||||
let r = regorus_alias_registry_builder_build(builder);
|
||||
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_alias_registry_builder_drop(builder);
|
||||
regorus_alias_registry_drop(registry);
|
||||
}
|
||||
|
||||
#[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 reg = build_registry_with_json(ALIASES);
|
||||
|
||||
let r = regorus_alias_registry_len(reg);
|
||||
assert_eq!(assert_ok_int(&r), 1);
|
||||
@@ -303,12 +408,7 @@ mod tests {
|
||||
|
||||
#[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 reg = build_registry_with_manifest(MANIFEST);
|
||||
|
||||
let r = regorus_alias_registry_len(reg);
|
||||
assert_eq!(assert_ok_int(&r), 1);
|
||||
@@ -319,23 +419,39 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn load_invalid_json_returns_error() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
let builder = regorus_alias_registry_builder_new();
|
||||
let bad = c("not valid json");
|
||||
|
||||
let r = regorus_alias_registry_load_json(reg, bad.as_ptr());
|
||||
let r = regorus_alias_registry_builder_load_json(builder, bad.as_ptr());
|
||||
assert_ne!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
regorus_alias_registry_builder_drop(builder);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builder_cannot_be_reused_after_build() {
|
||||
let builder = regorus_alias_registry_builder_new();
|
||||
let r = regorus_alias_registry_builder_build(builder);
|
||||
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
||||
regorus_result_drop(r);
|
||||
|
||||
let aliases = c(ALIASES);
|
||||
let r = regorus_alias_registry_builder_load_json(builder, aliases.as_ptr());
|
||||
assert_ne!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let r = regorus_alias_registry_builder_build(builder);
|
||||
assert_ne!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_alias_registry_builder_drop(builder);
|
||||
regorus_alias_registry_drop(registry);
|
||||
}
|
||||
|
||||
#[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 reg = build_registry_with_json(ALIASES);
|
||||
|
||||
let resource = c(r#"{
|
||||
"name": "acct1",
|
||||
@@ -346,7 +462,6 @@ mod tests {
|
||||
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(),
|
||||
@@ -357,7 +472,6 @@ mod tests {
|
||||
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!(
|
||||
@@ -373,16 +487,13 @@ mod tests {
|
||||
"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);
|
||||
|
||||
@@ -392,7 +503,6 @@ mod tests {
|
||||
|
||||
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"
|
||||
@@ -403,11 +513,7 @@ mod tests {
|
||||
|
||||
#[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 reg = build_registry_with_json(ALIASES);
|
||||
|
||||
let bad = c("not json");
|
||||
let api = c("2023-01-01");
|
||||
@@ -420,11 +526,7 @@ mod tests {
|
||||
|
||||
#[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 reg = build_registry_with_manifest(MANIFEST);
|
||||
|
||||
let resource = c(r#"{
|
||||
"type": "Microsoft.KeyVault.Data/vaults/certificates",
|
||||
@@ -453,7 +555,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn empty_registry_normalize() {
|
||||
let reg = regorus_alias_registry_new();
|
||||
let builder = regorus_alias_registry_builder_new();
|
||||
let r = regorus_alias_registry_builder_build(builder);
|
||||
let reg = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
||||
regorus_result_drop(r);
|
||||
regorus_alias_registry_builder_drop(builder);
|
||||
|
||||
let resource = c(r#"{"name": "test", "type": "Unknown/type", "properties": {"foo": 1}}"#);
|
||||
let api = c("");
|
||||
let ctx = c("{}");
|
||||
@@ -470,7 +577,6 @@ mod tests {
|
||||
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");
|
||||
|
||||
|
||||
@@ -236,6 +236,10 @@ pub(crate) fn to_ref<'a, T>(t: *mut T) -> Result<&'a mut T> {
|
||||
unsafe { t.as_mut().ok_or_else(|| anyhow!("null pointer")) }
|
||||
}
|
||||
|
||||
pub(crate) fn to_shared_ref<'a, T>(t: *const T) -> Result<&'a T> {
|
||||
unsafe { t.as_ref().ok_or_else(|| anyhow!("null pointer")) }
|
||||
}
|
||||
|
||||
pub(crate) fn to_regorus_result(r: Result<()>) -> RegorusResult {
|
||||
match r {
|
||||
Ok(()) => RegorusResult::ok_void(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
|
||||
use crate::common::{from_c_str, to_shared_ref, RegorusResult, RegorusStatus};
|
||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||
use crate::panic_guard::with_unwind_guard;
|
||||
use alloc::boxed::Box;
|
||||
@@ -208,6 +208,220 @@ fn convert_c_modules_to_rust(
|
||||
Ok(policy_modules)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Azure Policy JSON compilation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Compile an Azure Policy JSON policy rule into an RVM program.
|
||||
///
|
||||
/// Parses the JSON `policyRule` (the `{ "if": ..., "then": ... }` object),
|
||||
/// resolves aliases using the provided registry, and compiles the result
|
||||
/// into an RVM [`Program`] that can be loaded into a [`RegorusRvm`].
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `registry` - Alias registry handle, or null.
|
||||
/// * `policy_rule_json` - JSON string containing the policyRule object
|
||||
///
|
||||
/// # Null registry behavior
|
||||
///
|
||||
/// When `registry` is null, compilation proceeds **without alias resolution**.
|
||||
/// Field references that correspond to Azure resource provider aliases
|
||||
/// (e.g. `Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly`) will
|
||||
/// be compiled as raw property paths rather than being resolved to their
|
||||
/// short forms. This means:
|
||||
///
|
||||
/// - Policies that rely on aliases will **silently produce incorrect
|
||||
/// evaluation results** because the field paths won't match the
|
||||
/// normalized resource structure.
|
||||
/// - **Modify / Append** effect policies will **skip the modifiability
|
||||
/// validation** that normally rejects writes to non-modifiable aliases
|
||||
/// at compile time.
|
||||
///
|
||||
/// Pass null only when the policy is known to contain no alias references
|
||||
/// (e.g. simple `type` / `location` checks, or in unit-test scenarios).
|
||||
///
|
||||
/// # Returns
|
||||
/// Returns a `RegorusResult` containing a `RegorusProgram` pointer on success.
|
||||
///
|
||||
/// # Safety
|
||||
/// `policy_rule_json` must be a valid null-terminated UTF-8 string.
|
||||
/// If `registry` is non-null it must be a valid `RegorusAliasRegistry` pointer.
|
||||
/// The caller must eventually call `regorus_program_drop` on the returned handle.
|
||||
#[cfg(all(feature = "azure_policy", feature = "rvm"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_compile_azure_policy_rule(
|
||||
registry: *const crate::alias_registry::RegorusAliasRegistry,
|
||||
policy_rule_json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
use crate::alias_registry::RegorusAliasRegistry;
|
||||
use crate::rvm::RegorusProgram;
|
||||
use alloc::sync::Arc;
|
||||
use regorus::languages::azure_policy::{compiler, parser};
|
||||
use regorus::Rc;
|
||||
use regorus::Source;
|
||||
|
||||
with_unwind_guard(|| {
|
||||
let result = || -> Result<RegorusProgram, (RegorusStatus, alloc::string::String)> {
|
||||
let json_str = from_c_str(policy_rule_json).map_err(|e| {
|
||||
(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid policy rule JSON string: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let source = Source::from_contents("policy_rule".into(), json_str).map_err(|e| {
|
||||
(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to create source: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let ast = parser::parse_policy_rule(&source).map_err(|e| {
|
||||
(
|
||||
RegorusStatus::InvalidPolicy,
|
||||
format!("Failed to parse policy rule: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let program = if registry.is_null() {
|
||||
compiler::compile_policy_rule(&ast)
|
||||
} else {
|
||||
let reg: &RegorusAliasRegistry = to_shared_ref(registry).map_err(|e| {
|
||||
(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid alias registry: {e}"),
|
||||
)
|
||||
})?;
|
||||
compiler::compile_policy_rule_with_aliases(&ast, reg.inner())
|
||||
};
|
||||
|
||||
program
|
||||
.map(|p| RegorusProgram {
|
||||
program: Arc::new(Rc::try_unwrap(p).unwrap_or_else(|rc| (*rc).clone())),
|
||||
})
|
||||
.map_err(|e| {
|
||||
(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile policy rule: {e}"),
|
||||
)
|
||||
})
|
||||
}();
|
||||
|
||||
match result {
|
||||
Ok(program) => {
|
||||
RegorusResult::ok_pointer(Box::into_raw(Box::new(program)) as *mut c_void)
|
||||
}
|
||||
Err((status, msg)) => RegorusResult::err_with_message(status, msg),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile a full Azure Policy definition JSON into an RVM program.
|
||||
///
|
||||
/// Parses the JSON policy definition (which includes `policyRule`, `parameters`,
|
||||
/// `displayName`, etc.), resolves aliases using the provided registry, and
|
||||
/// compiles the result into an RVM [`Program`].
|
||||
///
|
||||
/// The definition JSON may be in either wrapped or unwrapped form:
|
||||
/// - **Wrapped**: `{ "properties": { "policyRule": ..., "parameters": ... }, "id": ... }`
|
||||
/// - **Unwrapped**: `{ "policyRule": ..., "parameters": ..., "displayName": ... }`
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `registry` - Alias registry handle, or null.
|
||||
/// * `policy_definition_json` - JSON string containing the full policy definition
|
||||
///
|
||||
/// # Null registry behavior
|
||||
///
|
||||
/// When `registry` is null, compilation proceeds **without alias resolution**.
|
||||
/// Field references that correspond to Azure resource provider aliases will
|
||||
/// be compiled as raw property paths rather than being resolved. This means:
|
||||
///
|
||||
/// - Policies that rely on aliases will **silently produce incorrect
|
||||
/// evaluation results**.
|
||||
/// - **Modify / Append** effect policies will **skip the modifiability
|
||||
/// validation** that normally rejects writes to non-modifiable aliases
|
||||
/// at compile time.
|
||||
///
|
||||
/// Pass null only when the policy is known to contain no alias references
|
||||
/// (e.g. simple `type` / `location` checks, or in unit-test scenarios).
|
||||
///
|
||||
/// # Returns
|
||||
/// Returns a `RegorusResult` containing a `RegorusProgram` pointer on success.
|
||||
///
|
||||
/// # Safety
|
||||
/// `policy_definition_json` must be a valid null-terminated UTF-8 string.
|
||||
/// If `registry` is non-null it must be a valid `RegorusAliasRegistry` pointer.
|
||||
/// The caller must eventually call `regorus_program_drop` on the returned handle.
|
||||
#[cfg(all(feature = "azure_policy", feature = "rvm"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_compile_azure_policy_definition(
|
||||
registry: *const crate::alias_registry::RegorusAliasRegistry,
|
||||
policy_definition_json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
use crate::alias_registry::RegorusAliasRegistry;
|
||||
use crate::rvm::RegorusProgram;
|
||||
use alloc::sync::Arc;
|
||||
use regorus::languages::azure_policy::{compiler, parser};
|
||||
use regorus::Rc;
|
||||
use regorus::Source;
|
||||
|
||||
with_unwind_guard(|| {
|
||||
let result = || -> Result<RegorusProgram, (RegorusStatus, alloc::string::String)> {
|
||||
let json_str = from_c_str(policy_definition_json).map_err(|e| {
|
||||
(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Invalid policy definition JSON string: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let source =
|
||||
Source::from_contents("policy_definition".into(), json_str).map_err(|e| {
|
||||
(
|
||||
RegorusStatus::InvalidDataFormat,
|
||||
format!("Failed to create source: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let defn = parser::parse_policy_definition(&source).map_err(|e| {
|
||||
(
|
||||
RegorusStatus::InvalidPolicy,
|
||||
format!("Failed to parse policy definition: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let program = if registry.is_null() {
|
||||
compiler::compile_policy_definition(&defn)
|
||||
} else {
|
||||
let reg: &RegorusAliasRegistry = to_shared_ref(registry).map_err(|e| {
|
||||
(
|
||||
RegorusStatus::InvalidArgument,
|
||||
format!("Invalid alias registry: {e}"),
|
||||
)
|
||||
})?;
|
||||
compiler::compile_policy_definition_with_aliases(&defn, reg.inner())
|
||||
};
|
||||
|
||||
program
|
||||
.map(|p| RegorusProgram {
|
||||
program: Arc::new(Rc::try_unwrap(p).unwrap_or_else(|rc| (*rc).clone())),
|
||||
})
|
||||
.map_err(|e| {
|
||||
(
|
||||
RegorusStatus::CompilationFailed,
|
||||
format!("Failed to compile policy definition: {e}"),
|
||||
)
|
||||
})
|
||||
}();
|
||||
|
||||
match result {
|
||||
Ok(program) => {
|
||||
RegorusResult::ok_pointer(Box::into_raw(Box::new(program)) as *mut c_void)
|
||||
}
|
||||
Err((status, msg)) => RegorusResult::err_with_message(status, msg),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn report_module_error(index: usize, kind: &str, err: &anyhow::Error) {
|
||||
eprintln!("Invalid {} at index {}: {}", kind, index, err);
|
||||
@@ -215,3 +429,402 @@ fn report_module_error(index: usize, kind: &str, err: &anyhow::Error) {
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn report_module_error(_index: usize, _kind: &str, _err: &anyhow::Error) {}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::common::regorus_result_drop;
|
||||
use core::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
|
||||
fn c(s: &str) -> CString {
|
||||
CString::new(s).expect("CString::new failed")
|
||||
}
|
||||
|
||||
fn assert_ok_pointer(r: &RegorusResult) -> *mut c_void {
|
||||
assert_eq!(
|
||||
r.status,
|
||||
RegorusStatus::Ok,
|
||||
"expected Ok, got {:?}",
|
||||
r.status
|
||||
);
|
||||
assert!(!r.pointer_value.is_null(), "expected non-null pointer");
|
||||
r.pointer_value
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "azure_policy", feature = "rvm"))]
|
||||
mod azure_policy_json {
|
||||
use super::*;
|
||||
use crate::alias_registry::regorus_alias_registry_drop;
|
||||
use crate::rvm::{
|
||||
regorus_program_drop, regorus_rvm_drop, regorus_rvm_execute_entry_point_by_name,
|
||||
regorus_rvm_load_program, regorus_rvm_new, regorus_rvm_set_context,
|
||||
regorus_rvm_set_input, RegorusProgram,
|
||||
};
|
||||
|
||||
const ALIASES: &str = r#"[{
|
||||
"namespace": "Microsoft.Storage",
|
||||
"resourceTypes": [{
|
||||
"resourceType": "storageAccounts",
|
||||
"aliases": [{
|
||||
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
||||
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
||||
"paths": []
|
||||
}, {
|
||||
"name": "Microsoft.Storage/storageAccounts/minimumTlsVersion",
|
||||
"defaultPath": "properties.minimumTlsVersion",
|
||||
"paths": []
|
||||
}]
|
||||
}]
|
||||
}]"#;
|
||||
|
||||
const SIMPLE_POLICY_RULE: &str = r#"{
|
||||
"if": {
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Storage/storageAccounts"
|
||||
},
|
||||
"then": { "effect": "audit" }
|
||||
}"#;
|
||||
|
||||
const ALIAS_POLICY_RULE: &str = r#"{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
|
||||
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}"#;
|
||||
|
||||
const POLICY_DEFINITION: &str = r#"{
|
||||
"displayName": "Require HTTPS for storage accounts",
|
||||
"policyType": "Custom",
|
||||
"mode": "Indexed",
|
||||
"parameters": {
|
||||
"effect": {
|
||||
"type": "String",
|
||||
"defaultValue": "deny"
|
||||
}
|
||||
},
|
||||
"policyRule": {
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
|
||||
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "[parameters('effect')]" }
|
||||
}
|
||||
}"#;
|
||||
|
||||
/// Wrap a normalized resource JSON into the input envelope expected by
|
||||
/// the compiled Azure Policy RVM program.
|
||||
fn wrap_input(resource_json: &str, parameters_json: &str) -> String {
|
||||
format!(r#"{{"resource": {resource_json}, "parameters": {parameters_json}}}"#)
|
||||
}
|
||||
|
||||
fn build_registry_with_json(
|
||||
json: &str,
|
||||
) -> *mut crate::alias_registry::RegorusAliasRegistry {
|
||||
let builder = crate::alias_registry::regorus_alias_registry_builder_new();
|
||||
let json_c = c(json);
|
||||
let r = crate::alias_registry::regorus_alias_registry_builder_load_json(
|
||||
builder,
|
||||
json_c.as_ptr(),
|
||||
);
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let r = crate::alias_registry::regorus_alias_registry_builder_build(builder);
|
||||
let registry =
|
||||
assert_ok_pointer(&r) as *mut crate::alias_registry::RegorusAliasRegistry;
|
||||
regorus_result_drop(r);
|
||||
crate::alias_registry::regorus_alias_registry_builder_drop(builder);
|
||||
registry
|
||||
}
|
||||
|
||||
/// Helper: compile a policy rule, execute it with input, and return the
|
||||
/// result string.
|
||||
unsafe fn compile_and_eval_rule(
|
||||
registry: *const crate::alias_registry::RegorusAliasRegistry,
|
||||
policy_rule: &str,
|
||||
input_json: &str,
|
||||
) -> String {
|
||||
let rule_c = c(policy_rule);
|
||||
let r = regorus_compile_azure_policy_rule(registry, rule_c.as_ptr());
|
||||
let program_ptr = assert_ok_pointer(&r) as *mut RegorusProgram;
|
||||
regorus_result_drop(r);
|
||||
|
||||
let vm = regorus_rvm_new();
|
||||
assert!(!vm.is_null());
|
||||
|
||||
let r = regorus_rvm_load_program(vm, program_ptr);
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let input_c = c(input_json);
|
||||
let r = regorus_rvm_set_input(vm, input_c.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let entry = c("main");
|
||||
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok, "execute failed");
|
||||
let output = CStr::from_ptr(r.output)
|
||||
.to_str()
|
||||
.expect("invalid UTF-8")
|
||||
.to_string();
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_rvm_drop(vm);
|
||||
regorus_program_drop(program_ptr);
|
||||
output
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_simple_rule_no_aliases() {
|
||||
let rule_c = c(SIMPLE_POLICY_RULE);
|
||||
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), rule_c.as_ptr());
|
||||
let ptr = assert_ok_pointer(&r);
|
||||
regorus_result_drop(r);
|
||||
regorus_program_drop(ptr as *mut RegorusProgram);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_rule_with_aliases() {
|
||||
let reg = build_registry_with_json(ALIASES);
|
||||
|
||||
let rule_c = c(ALIAS_POLICY_RULE);
|
||||
let r = regorus_compile_azure_policy_rule(reg, rule_c.as_ptr());
|
||||
let ptr = assert_ok_pointer(&r);
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_program_drop(ptr as *mut RegorusProgram);
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_and_eval_simple_rule_matching() {
|
||||
let input = wrap_input(r#"{"type":"microsoft.storage/storageaccounts"}"#, "{}");
|
||||
let result =
|
||||
unsafe { compile_and_eval_rule(core::ptr::null_mut(), SIMPLE_POLICY_RULE, &input) };
|
||||
let parsed: serde_json::Value =
|
||||
serde_json::from_str(&result).expect("result should be valid JSON");
|
||||
assert_eq!(
|
||||
parsed["effect"], "audit",
|
||||
"expected audit effect, got: {result}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_and_eval_simple_rule_not_matching() {
|
||||
let input = wrap_input(r#"{"type":"microsoft.compute/virtualmachines"}"#, "{}");
|
||||
let result =
|
||||
unsafe { compile_and_eval_rule(core::ptr::null_mut(), SIMPLE_POLICY_RULE, &input) };
|
||||
// When the "if" condition doesn't match, the result should be undefined
|
||||
assert!(
|
||||
result.contains("undefined"),
|
||||
"expected undefined for non-matching input, got: {result}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_and_eval_alias_rule_deny() {
|
||||
let reg = build_registry_with_json(ALIASES);
|
||||
|
||||
// Non-compliant resource: HTTPS not enabled (normalized form)
|
||||
let input = wrap_input(
|
||||
r#"{"type": "microsoft.storage/storageaccounts", "supportshttpstrafficonly": false}"#,
|
||||
"{}",
|
||||
);
|
||||
let result = unsafe { compile_and_eval_rule(reg, ALIAS_POLICY_RULE, &input) };
|
||||
let parsed: serde_json::Value = serde_json::from_str(&result).expect("valid JSON");
|
||||
assert_eq!(parsed["effect"], "deny", "expected deny, got: {result}");
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_and_eval_alias_rule_compliant() {
|
||||
let reg = build_registry_with_json(ALIASES);
|
||||
|
||||
// Compliant resource: HTTPS enabled (normalized form)
|
||||
let input = wrap_input(
|
||||
r#"{"type": "microsoft.storage/storageaccounts", "supportshttpstrafficonly": true}"#,
|
||||
"{}",
|
||||
);
|
||||
let result = unsafe { compile_and_eval_rule(reg, ALIAS_POLICY_RULE, &input) };
|
||||
assert!(
|
||||
result.contains("undefined"),
|
||||
"expected undefined for compliant resource, got: {result}"
|
||||
);
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_definition_no_aliases() {
|
||||
let defn_c = c(POLICY_DEFINITION);
|
||||
let r = regorus_compile_azure_policy_definition(core::ptr::null_mut(), defn_c.as_ptr());
|
||||
let ptr = assert_ok_pointer(&r);
|
||||
regorus_result_drop(r);
|
||||
regorus_program_drop(ptr as *mut RegorusProgram);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_definition_with_aliases_and_eval() {
|
||||
let reg = build_registry_with_json(ALIASES);
|
||||
|
||||
let defn_c = c(POLICY_DEFINITION);
|
||||
let r = regorus_compile_azure_policy_definition(reg, defn_c.as_ptr());
|
||||
let program_ptr = assert_ok_pointer(&r) as *mut RegorusProgram;
|
||||
regorus_result_drop(r);
|
||||
|
||||
// Evaluate with a non-compliant resource (normalized form, wrapped in envelope)
|
||||
unsafe {
|
||||
let vm = regorus_rvm_new();
|
||||
let r = regorus_rvm_load_program(vm, program_ptr);
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let input_json = wrap_input(
|
||||
r#"{"type": "microsoft.storage/storageaccounts", "supportshttpstrafficonly": false}"#,
|
||||
"{}",
|
||||
);
|
||||
let input = c(&input_json);
|
||||
let r = regorus_rvm_set_input(vm, input.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let entry = c("main");
|
||||
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
let result = CStr::from_ptr(r.output)
|
||||
.to_str()
|
||||
.expect("UTF-8")
|
||||
.to_string();
|
||||
regorus_result_drop(r);
|
||||
|
||||
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
|
||||
// The default parameter value is "deny"
|
||||
assert_eq!(parsed["effect"], "deny", "got: {result}");
|
||||
|
||||
regorus_rvm_drop(vm);
|
||||
regorus_program_drop(program_ptr);
|
||||
}
|
||||
|
||||
regorus_alias_registry_drop(reg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_json_returns_error() {
|
||||
let bad = c("not valid json");
|
||||
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), bad.as_ptr());
|
||||
assert_ne!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_definition_returns_error() {
|
||||
let bad = c(r#"{"not": "a policy definition"}"#);
|
||||
let r = regorus_compile_azure_policy_definition(core::ptr::null_mut(), bad.as_ptr());
|
||||
assert_ne!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
}
|
||||
|
||||
/// Policy rule that uses a context function (subscription()).
|
||||
const CONTEXT_POLICY_RULE: &str = r#"{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
|
||||
{ "value": "[subscription().subscriptionId]", "equals": "sub-123" }
|
||||
]
|
||||
},
|
||||
"then": { "effect": "deny" }
|
||||
}"#;
|
||||
|
||||
#[test]
|
||||
fn context_policy_evaluates_with_set_context() {
|
||||
let rule_c = c(CONTEXT_POLICY_RULE);
|
||||
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), rule_c.as_ptr());
|
||||
let program = assert_ok_pointer(&r) as *mut RegorusProgram;
|
||||
regorus_result_drop(r);
|
||||
|
||||
let vm = regorus_rvm_new();
|
||||
assert!(!vm.is_null());
|
||||
|
||||
let r = regorus_rvm_load_program(vm, program);
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
// Set the context with subscription info
|
||||
let context = c(r#"{"subscription": {"subscriptionId": "sub-123"}}"#);
|
||||
let r = regorus_rvm_set_context(vm, context.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
// Set matching input
|
||||
let input = c(&wrap_input(
|
||||
r#"{"type": "microsoft.storage/storageaccounts"}"#,
|
||||
"{}",
|
||||
));
|
||||
let r = regorus_rvm_set_input(vm, input.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let entry = c("main");
|
||||
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
let output = unsafe { CStr::from_ptr(r.output) }.to_str().unwrap();
|
||||
assert!(
|
||||
output.contains("deny"),
|
||||
"expected deny effect with matching context, got: {output}"
|
||||
);
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_rvm_drop(vm);
|
||||
regorus_program_drop(program);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_policy_undefined_without_context() {
|
||||
let rule_c = c(CONTEXT_POLICY_RULE);
|
||||
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), rule_c.as_ptr());
|
||||
let program = assert_ok_pointer(&r) as *mut RegorusProgram;
|
||||
regorus_result_drop(r);
|
||||
|
||||
let vm = regorus_rvm_new();
|
||||
assert!(!vm.is_null());
|
||||
|
||||
let r = regorus_rvm_load_program(vm, program);
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
// No context set — subscription() will be undefined
|
||||
let input = c(&wrap_input(
|
||||
r#"{"type": "microsoft.storage/storageaccounts"}"#,
|
||||
"{}",
|
||||
));
|
||||
let r = regorus_rvm_set_input(vm, input.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
regorus_result_drop(r);
|
||||
|
||||
let entry = c("main");
|
||||
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
|
||||
assert_eq!(r.status, RegorusStatus::Ok);
|
||||
let output = unsafe { CStr::from_ptr(r.output) }.to_str().unwrap();
|
||||
assert!(
|
||||
output.contains("undefined"),
|
||||
"expected undefined without context, got: {output}"
|
||||
);
|
||||
regorus_result_drop(r);
|
||||
|
||||
regorus_rvm_drop(vm);
|
||||
regorus_program_drop(program);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ pub extern "C" fn regorus_compiled_policy_eval_with_input(
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let input_value = regorus::Value::from_json_str(&from_c_str(input)?)?;
|
||||
let result = to_ref(compiled_policy)?
|
||||
let result = to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?
|
||||
.compiled_policy
|
||||
.eval_with_input(input_value)?;
|
||||
result.to_json_str()
|
||||
@@ -65,7 +65,9 @@ pub extern "C" fn regorus_compiled_policy_get_policy_info(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let info = to_ref(compiled_policy)?.compiled_policy.get_policy_info()?;
|
||||
let info = to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?
|
||||
.compiled_policy
|
||||
.get_policy_info()?;
|
||||
serde_json::to_string(&info)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to serialize policy info: {}", e))
|
||||
}();
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::common::{
|
||||
from_c_str, to_ref, to_regorus_result, to_regorus_string_result, RegorusResult, RegorusStatus,
|
||||
from_c_str, to_ref, to_regorus_result, to_regorus_string_result, to_shared_ref, RegorusResult,
|
||||
RegorusStatus,
|
||||
};
|
||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||
use crate::limits::RegorusExecutionTimerConfig;
|
||||
@@ -193,7 +194,7 @@ pub extern "C" fn regorus_engine_new() -> *mut RegorusEngine {
|
||||
///
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_clone(engine: *mut RegorusEngine) -> *mut RegorusEngine {
|
||||
match to_ref(engine) {
|
||||
match to_shared_ref(engine as *const RegorusEngine) {
|
||||
Ok(e) => Box::into_raw(Box::new(e.clone())),
|
||||
_ => ptr::null_mut(),
|
||||
}
|
||||
@@ -223,7 +224,7 @@ pub extern "C" fn regorus_engine_add_policy(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy(from_c_str(path)?, from_c_str(rego)?)
|
||||
}())
|
||||
@@ -238,7 +239,7 @@ pub extern "C" fn regorus_engine_add_policy_from_file(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_policy_from_file(from_c_str(path)?)
|
||||
}())
|
||||
@@ -256,7 +257,7 @@ pub extern "C" fn regorus_engine_add_data_json(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
|
||||
}())
|
||||
@@ -270,7 +271,7 @@ pub extern "C" fn regorus_engine_add_data_json(
|
||||
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_packages()?).map_err(anyhow::Error::msg)
|
||||
}())
|
||||
@@ -284,7 +285,7 @@ pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> Reg
|
||||
pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_policies_as_json()
|
||||
}())
|
||||
@@ -299,7 +300,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.add_data(regorus::Value::from_json_file(from_c_str(path)?)?)
|
||||
}())
|
||||
@@ -313,7 +314,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
|
||||
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_data();
|
||||
Ok(())
|
||||
@@ -332,7 +333,7 @@ pub extern "C" fn regorus_engine_set_input_json(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
|
||||
Ok(())
|
||||
@@ -348,7 +349,7 @@ pub extern "C" fn regorus_engine_set_input_from_json_file(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_input(regorus::Value::from_json_file(from_c_str(path)?)?);
|
||||
Ok(())
|
||||
@@ -367,7 +368,7 @@ pub extern "C" fn regorus_engine_eval_query(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let results = guard.eval_query(from_c_str(query)?, false)?;
|
||||
Ok(serde_json::to_string_pretty(&results)?)
|
||||
@@ -390,7 +391,7 @@ pub extern "C" fn regorus_engine_eval_rule(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.eval_rule(from_c_str(rule)?)?.to_json_str()
|
||||
}();
|
||||
@@ -413,7 +414,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_enable_coverage(enable);
|
||||
Ok(())
|
||||
@@ -429,7 +430,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
|
||||
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let guard = engine.try_read()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.get_coverage_report()?)?)
|
||||
}();
|
||||
@@ -451,7 +452,7 @@ pub extern "C" fn regorus_engine_set_strict_builtin_errors(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_strict_builtin_errors(strict);
|
||||
Ok(())
|
||||
@@ -465,18 +466,20 @@ pub extern "C" fn regorus_engine_set_execution_timer_config(
|
||||
engine: *mut RegorusEngine,
|
||||
config: *const RegorusExecutionTimerConfig,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let config = unsafe {
|
||||
config
|
||||
.as_ref()
|
||||
.copied()
|
||||
.ok_or_else(|| anyhow!("execution timer config pointer is null"))?
|
||||
};
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_execution_timer_config(config.to_execution_timer_config()?);
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let config = unsafe {
|
||||
config
|
||||
.as_ref()
|
||||
.copied()
|
||||
.ok_or_else(|| anyhow!("execution timer config pointer is null"))?
|
||||
};
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_execution_timer_config(config.to_execution_timer_config()?);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -484,12 +487,14 @@ pub extern "C" fn regorus_engine_set_execution_timer_config(
|
||||
pub extern "C" fn regorus_engine_clear_execution_timer_config(
|
||||
engine: *mut RegorusEngine,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_execution_timer_config();
|
||||
Ok(())
|
||||
}())
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_execution_timer_config();
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the policy length limits used when loading policies.
|
||||
@@ -500,7 +505,7 @@ pub extern "C" fn regorus_engine_set_policy_length_config(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_policy_length_config(config.to_policy_length_config()?);
|
||||
Ok(())
|
||||
@@ -515,7 +520,7 @@ pub extern "C" fn regorus_engine_clear_policy_length_config(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_policy_length_config();
|
||||
Ok(())
|
||||
@@ -533,7 +538,7 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_coverage_report()?.to_string_pretty()
|
||||
}();
|
||||
@@ -552,7 +557,7 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
|
||||
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.clear_coverage_data();
|
||||
Ok(())
|
||||
@@ -571,7 +576,7 @@ pub extern "C" fn regorus_engine_set_gather_prints(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_gather_prints(enable);
|
||||
Ok(())
|
||||
@@ -586,7 +591,7 @@ pub extern "C" fn regorus_engine_set_gather_prints(
|
||||
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
Ok(serde_json::to_string_pretty(&guard.take_prints()?)?)
|
||||
}();
|
||||
@@ -605,7 +610,7 @@ pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> Rego
|
||||
pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let guard = engine.try_read()?;
|
||||
guard.get_ast_as_json()
|
||||
}();
|
||||
@@ -626,7 +631,7 @@ pub extern "C" fn regorus_engine_get_policy_package_names(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_package_names()?)
|
||||
.map_err(anyhow::Error::msg)
|
||||
@@ -648,7 +653,7 @@ pub extern "C" fn regorus_engine_get_policy_parameters(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let guard = engine.try_read()?;
|
||||
serde_json::to_string_pretty(&guard.get_policy_parameters()?)
|
||||
.map_err(anyhow::Error::msg)
|
||||
@@ -670,7 +675,7 @@ pub extern "C" fn regorus_engine_set_rego_v0(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<()> {
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
guard.set_rego_v0(enable);
|
||||
Ok(())
|
||||
@@ -692,7 +697,7 @@ pub extern "C" fn regorus_engine_set_rego_v0(
|
||||
#[cfg(feature = "azure_policy")]
|
||||
pub extern "C" fn regorus_engine_compile_for_target(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let engine = match to_ref(engine) {
|
||||
let engine = match to_shared_ref(engine as *const RegorusEngine) {
|
||||
Ok(engine) => engine,
|
||||
Err(e) => {
|
||||
return RegorusResult::err_with_message(
|
||||
@@ -741,7 +746,7 @@ pub extern "C" fn regorus_engine_compile_with_entrypoint(
|
||||
let result = || -> Result<RegorusCompiledPolicy> {
|
||||
let rule_str = from_c_str(rule)?;
|
||||
let rule_rc: regorus::Rc<str> = rule_str.into();
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||
Ok(RegorusCompiledPolicy { compiled_policy })
|
||||
@@ -800,7 +805,7 @@ pub extern "C" fn regorus_engine_compile_program_with_entrypoints(
|
||||
.ok_or_else(|| anyhow!("entry_points must contain at least one entry"))?;
|
||||
let rule_rc: regorus::Rc<str> = (*rule).into();
|
||||
|
||||
let engine = to_ref(engine)?;
|
||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
||||
let mut guard = engine.try_write()?;
|
||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::common::{
|
||||
from_c_str, to_ref, to_regorus_result, RegorusBuffer, RegorusResult, RegorusStatus,
|
||||
from_c_str, to_ref, to_regorus_result, to_shared_ref, RegorusBuffer, RegorusResult,
|
||||
RegorusStatus,
|
||||
};
|
||||
use crate::compile::RegorusPolicyModule;
|
||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||
@@ -106,7 +107,8 @@ pub extern "C" fn regorus_program_compile_from_policy(
|
||||
|
||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
let compiled_policy = &to_ref(compiled_policy)?.compiled_policy;
|
||||
let compiled_policy =
|
||||
&to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?.compiled_policy;
|
||||
let program = Compiler::compile_from_policy(compiled_policy, &entry_points_ref)?;
|
||||
Ok(Box::into_raw(Box::new(RegorusProgram { program })))
|
||||
}();
|
||||
@@ -187,7 +189,7 @@ pub extern "C" fn regorus_program_new() -> *mut RegorusProgram {
|
||||
pub extern "C" fn regorus_program_serialize_binary(program: *mut RegorusProgram) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusBuffer> {
|
||||
let program = &to_ref(program)?.program;
|
||||
let program = &to_shared_ref(program as *const RegorusProgram)?.program;
|
||||
let bytes = program.serialize_binary().map_err(|e| anyhow!(e))?;
|
||||
Ok(RegorusBuffer::from_vec(bytes))
|
||||
}();
|
||||
@@ -211,7 +213,10 @@ pub extern "C" fn regorus_program_deserialize_binary(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<(*mut RegorusProgram, bool)> {
|
||||
if data.is_null() && len > 0 {
|
||||
if data.is_null() {
|
||||
if len > 0 {
|
||||
return Err(anyhow!("null data pointer with non-zero length"));
|
||||
}
|
||||
return Err(anyhow!("null data pointer"));
|
||||
}
|
||||
let data = unsafe { core::slice::from_raw_parts(data, len) };
|
||||
@@ -249,7 +254,7 @@ pub extern "C" fn regorus_program_deserialize_binary(
|
||||
pub extern "C" fn regorus_program_generate_listing(program: *mut RegorusProgram) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let program = &to_ref(program)?.program;
|
||||
let program = &to_shared_ref(program as *const RegorusProgram)?.program;
|
||||
Ok(generate_assembly_listing(
|
||||
program,
|
||||
&AssemblyListingConfig::default(),
|
||||
@@ -270,7 +275,7 @@ pub extern "C" fn regorus_program_generate_tabular_listing(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let program = &to_ref(program)?.program;
|
||||
let program = &to_shared_ref(program as *const RegorusProgram)?.program;
|
||||
Ok(generate_tabular_assembly_listing(
|
||||
program,
|
||||
&AssemblyListingConfig::default(),
|
||||
@@ -297,7 +302,9 @@ pub extern "C" fn regorus_rvm_new_with_policy(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<*mut RegorusRvm> {
|
||||
let policy = to_ref(compiled_policy)?.compiled_policy.clone();
|
||||
let policy = to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?
|
||||
.compiled_policy
|
||||
.clone();
|
||||
Ok(Box::into_raw(Box::new(RegorusRvm::new(
|
||||
RegoVM::new_with_policy(policy),
|
||||
))))
|
||||
@@ -318,9 +325,11 @@ pub extern "C" fn regorus_rvm_load_program(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let program = to_ref(program)?.program.clone();
|
||||
let program = to_shared_ref(program as *const RegorusProgram)?
|
||||
.program
|
||||
.clone();
|
||||
guard.load_program(program);
|
||||
Ok(())
|
||||
}())
|
||||
@@ -332,7 +341,7 @@ pub extern "C" fn regorus_rvm_load_program(
|
||||
pub extern "C" fn regorus_rvm_set_data(vm: *mut RegorusRvm, data: *const c_char) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let data_value = Value::from_json_str(&from_c_str(data)?)?;
|
||||
guard.set_data(data_value)?;
|
||||
@@ -349,7 +358,7 @@ pub extern "C" fn regorus_rvm_set_input(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let input_value = Value::from_json_str(&from_c_str(input)?)?;
|
||||
guard.set_input(input_value);
|
||||
@@ -358,6 +367,33 @@ pub extern "C" fn regorus_rvm_set_input(
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the VM context document from JSON.
|
||||
///
|
||||
/// The context provides host-supplied ambient data (e.g. `resourceGroup()`,
|
||||
/// `subscription()`) that Azure Policy functions can access via `LoadContext`
|
||||
/// instructions. This must be called before `regorus_rvm_execute` when
|
||||
/// evaluating policies that reference context functions.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `vm` must be a valid pointer to a `RegorusRvm` created by `regorus_rvm_new`.
|
||||
/// - `context_json` must be a valid null-terminated UTF-8 string.
|
||||
#[cfg(feature = "azure_policy")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_context(
|
||||
vm: *mut RegorusRvm,
|
||||
context_json: *const c_char,
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let context_value = Value::from_json_str(&from_c_str(context_json)?)?;
|
||||
guard.set_context(context_value);
|
||||
Ok(())
|
||||
}())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the maximum number of instructions that can execute.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_rvm_set_max_instructions(
|
||||
@@ -366,7 +402,7 @@ pub extern "C" fn regorus_rvm_set_max_instructions(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
guard.set_max_instructions(max_instructions);
|
||||
Ok(())
|
||||
@@ -382,7 +418,7 @@ pub extern "C" fn regorus_rvm_set_strict_builtin_errors(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
guard.set_strict_builtin_errors(strict);
|
||||
Ok(())
|
||||
@@ -395,7 +431,7 @@ pub extern "C" fn regorus_rvm_set_strict_builtin_errors(
|
||||
pub extern "C" fn regorus_rvm_set_execution_mode(vm: *mut RegorusRvm, mode: u8) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let mode = match mode {
|
||||
0 => ExecutionMode::RunToCompletion,
|
||||
@@ -413,7 +449,7 @@ pub extern "C" fn regorus_rvm_set_execution_mode(vm: *mut RegorusRvm, mode: u8)
|
||||
pub extern "C" fn regorus_rvm_set_step_mode(vm: *mut RegorusRvm, enabled: bool) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
guard.set_step_mode(enabled);
|
||||
Ok(())
|
||||
@@ -430,7 +466,7 @@ pub extern "C" fn regorus_rvm_set_execution_timer_config(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
if has_config {
|
||||
guard.set_execution_timer_config(Some(config.to_execution_timer_config()?));
|
||||
@@ -447,7 +483,7 @@ pub extern "C" fn regorus_rvm_set_execution_timer_config(
|
||||
pub extern "C" fn regorus_rvm_execute(vm: *mut RegorusRvm) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let result = guard.execute()?;
|
||||
result.to_json_str()
|
||||
@@ -468,7 +504,7 @@ pub extern "C" fn regorus_rvm_execute_entry_point_by_name(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let name = from_c_str(entry_point)?;
|
||||
let result = guard.execute_entry_point_by_name(&name)?;
|
||||
@@ -490,7 +526,7 @@ pub extern "C" fn regorus_rvm_execute_entry_point_by_index(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let result = guard.execute_entry_point_by_index(index)?;
|
||||
result.to_json_str()
|
||||
@@ -512,7 +548,7 @@ pub extern "C" fn regorus_rvm_resume(
|
||||
) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let mut guard = vm.try_write()?;
|
||||
let value = if has_value {
|
||||
Some(Value::from_json_str(&from_c_str(resume_value_json)?)?)
|
||||
@@ -535,7 +571,7 @@ pub extern "C" fn regorus_rvm_resume(
|
||||
pub extern "C" fn regorus_rvm_get_execution_state(vm: *mut RegorusRvm) -> RegorusResult {
|
||||
with_unwind_guard(|| {
|
||||
let output = || -> Result<String> {
|
||||
let vm = to_ref(vm)?;
|
||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
||||
let guard = vm.try_read()?;
|
||||
let state: ExecutionState = guard.execution_state().clone();
|
||||
Ok(format!("{:?}", state))
|
||||
|
||||
68
bindings/java/Cargo.lock
generated
68
bindings/java/Cargo.lock
generated
@@ -109,9 +109,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
version = "1.2.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -237,9 +237,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
|
||||
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
@@ -380,9 +380,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -530,7 +530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -598,9 +598,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.97"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -610,9 +610,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
|
||||
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytecount",
|
||||
@@ -676,9 +676,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.16.4"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
|
||||
checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -686,6 +686,12 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
|
||||
|
||||
[[package]]
|
||||
name = "msvc_spectre_libs"
|
||||
version = "0.1.3"
|
||||
@@ -948,14 +954,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
|
||||
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"fluent-uri",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.1",
|
||||
"itoa",
|
||||
"micromap",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"serde_json",
|
||||
@@ -992,7 +1000,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "regorus"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1024,7 +1032,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regorus-java"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"jni",
|
||||
@@ -1034,7 +1042,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regorus-mimalloc"
|
||||
version = "2.2.6"
|
||||
version = "2.2.7"
|
||||
dependencies = [
|
||||
"regorus-mimalloc-sys",
|
||||
]
|
||||
@@ -1168,9 +1176,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@@ -1352,9 +1360,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1365,9 +1373,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1375,9 +1383,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1388,9 +1396,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1651,9 +1659,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[package]
|
||||
name = "regorus-java"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/java"
|
||||
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>com.microsoft.regorus</groupId>
|
||||
<artifactId>regorus-java</artifactId>
|
||||
<version>0.10.0</version>
|
||||
<version>0.10.1</version>
|
||||
|
||||
<name>Regorus Java</name>
|
||||
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>
|
||||
|
||||
@@ -462,7 +462,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromModul
|
||||
}
|
||||
|
||||
let mut modules = Vec::with_capacity(ids.len());
|
||||
for (id, content) in ids.into_iter().zip(contents.into_iter()) {
|
||||
for (id, content) in ids.into_iter().zip(contents) {
|
||||
modules.push(PolicyModule {
|
||||
id: Rc::from(id.as_str()),
|
||||
content: Rc::from(content.as_str()),
|
||||
|
||||
68
bindings/python/Cargo.lock
generated
68
bindings/python/Cargo.lock
generated
@@ -103,9 +103,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
version = "1.2.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -221,9 +221,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
|
||||
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
@@ -364,9 +364,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -514,7 +514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -533,9 +533,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.97"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -545,9 +545,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
|
||||
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytecount",
|
||||
@@ -611,9 +611,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.16.4"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
|
||||
checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -621,6 +621,12 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
|
||||
|
||||
[[package]]
|
||||
name = "msvc_spectre_libs"
|
||||
version = "0.1.3"
|
||||
@@ -957,14 +963,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
|
||||
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"fluent-uri",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.1",
|
||||
"itoa",
|
||||
"micromap",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"serde_json",
|
||||
@@ -1001,7 +1009,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "regorus"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1033,7 +1041,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regorus-mimalloc"
|
||||
version = "2.2.6"
|
||||
version = "2.2.7"
|
||||
dependencies = [
|
||||
"regorus-mimalloc-sys",
|
||||
]
|
||||
@@ -1047,7 +1055,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regoruspy"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ordered-float",
|
||||
@@ -1144,9 +1152,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@@ -1324,9 +1332,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1337,9 +1345,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1347,9 +1355,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1360,9 +1368,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1605,9 +1613,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[package]
|
||||
name = "regoruspy"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/python"
|
||||
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
|
||||
76
bindings/ruby/Cargo.lock
generated
76
bindings/ruby/Cargo.lock
generated
@@ -121,9 +121,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
version = "1.2.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -244,9 +244,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
|
||||
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
@@ -393,9 +393,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -543,7 +543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -571,9 +571,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.97"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -583,9 +583,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
|
||||
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytecount",
|
||||
@@ -659,9 +659,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.16.4"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
|
||||
checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "magnus"
|
||||
@@ -692,6 +692,12 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
@@ -929,18 +935,18 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rb-sys"
|
||||
version = "0.9.127"
|
||||
version = "0.9.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7d7c9560fe42dcffa576941394075f18a17dce89fcf718a2fa90b7dc2134d12"
|
||||
checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
|
||||
dependencies = [
|
||||
"rb-sys-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rb-sys-build"
|
||||
version = "0.9.127"
|
||||
version = "0.9.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1688e8f32967ba48c89e4dfa283b57f901075f542fc7ee9c3d7c5f9091ca1d9"
|
||||
checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"lazy_static",
|
||||
@@ -988,14 +994,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
|
||||
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"fluent-uri",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.1",
|
||||
"itoa",
|
||||
"micromap",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"serde_json",
|
||||
@@ -1032,7 +1040,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "regorus"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1063,7 +1071,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regorus-mimalloc"
|
||||
version = "2.2.6"
|
||||
version = "2.2.7"
|
||||
dependencies = [
|
||||
"regorus-mimalloc-sys",
|
||||
]
|
||||
@@ -1077,7 +1085,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regorusrb"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"magnus",
|
||||
"regorus",
|
||||
@@ -1203,9 +1211,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@@ -1383,9 +1391,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1396,9 +1404,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1406,9 +1414,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1419,9 +1427,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1664,9 +1672,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorusrb"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
edition = "2024"
|
||||
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Regorus
|
||||
VERSION = "0.10.0"
|
||||
VERSION = "0.10.1"
|
||||
end
|
||||
|
||||
82
bindings/wasm/Cargo.lock
generated
82
bindings/wasm/Cargo.lock
generated
@@ -120,9 +120,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
version = "1.2.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@@ -238,9 +238,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
|
||||
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
@@ -396,9 +396,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -546,7 +546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -565,9 +565,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.97"
|
||||
version = "0.3.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
|
||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -577,9 +577,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
|
||||
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytecount",
|
||||
@@ -649,9 +649,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.16.4"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
|
||||
checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -659,6 +659,12 @@ version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "micromap"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
|
||||
|
||||
[[package]]
|
||||
name = "minicov"
|
||||
version = "0.3.8"
|
||||
@@ -947,14 +953,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.45.1"
|
||||
version = "0.46.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
|
||||
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"fluent-uri",
|
||||
"getrandom 0.3.4",
|
||||
"hashbrown 0.16.1",
|
||||
"itoa",
|
||||
"micromap",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"serde_json",
|
||||
@@ -991,7 +999,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "regorus"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1022,7 +1030,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regorusjs"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"getrandom 0.3.4",
|
||||
@@ -1144,9 +1152,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@@ -1336,9 +1344,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -1349,9 +1357,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.70"
|
||||
version = "0.4.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084"
|
||||
checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -1359,9 +1367,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -1369,9 +1377,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -1382,18 +1390,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.70"
|
||||
version = "0.3.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29826f9d9ecaa314c480d376b276d1c790e6cb6a4681fab8532da69cbabf977d"
|
||||
checksum = "af5ec93229ad9ccd0a545a516dec76dc276613f278f6a91aa6b463d5b33d42d0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cast",
|
||||
@@ -1413,9 +1421,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.70"
|
||||
version = "0.3.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c610311887f9e6599a546d278d12d69dfd3a3e92639b2129e4b11ad6cf1961d6"
|
||||
checksum = "3c81b9fef827e575e0e54431736d1baa0d700315d8c62cfef1f61fa3aad0cbeb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1424,9 +1432,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-shared"
|
||||
version = "0.2.120"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60238e5b4b1b295701d6f9a66d2a126fe19990348f5fb9dae3b623a370119d94"
|
||||
checksum = "4f4d8ae7ad5440360e9799dfd42857d126454a88441ddf72d288ef83fa47f527"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
@@ -1684,9 +1692,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[package]
|
||||
name = "regorusjs"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/wasm"
|
||||
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
@@ -55,7 +55,7 @@ getrandom03 = { package = "getrandom", version = "0.3.1", features = ["std", "wa
|
||||
getrandom = { version = "0.4.2", features = ["wasm_js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.67"
|
||||
wasm-bindgen-test = "0.3.71"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }
|
||||
|
||||
@@ -23,8 +23,7 @@ use regorus::languages::azure_policy::aliases::AliasRegistry;
|
||||
use regorus::languages::azure_policy::compiler;
|
||||
use regorus::languages::azure_policy::parser;
|
||||
use regorus::rvm::RegoVM;
|
||||
use regorus::Source;
|
||||
use regorus::Value;
|
||||
use regorus::{Rc, Source, Value};
|
||||
|
||||
/// Evaluate an Azure Policy definition against a resource.
|
||||
///
|
||||
@@ -60,11 +59,8 @@ pub fn azure_policy_eval(
|
||||
println!("Parsed policy definition from {policy_definition}");
|
||||
|
||||
// 3. Compile to RVM bytecode.
|
||||
let program = compiler::compile_policy_definition_with_aliases(
|
||||
&defn,
|
||||
registry.alias_map(),
|
||||
registry.alias_modifiable_map(),
|
||||
)?;
|
||||
let registry = Rc::new(registry);
|
||||
let program = compiler::compile_policy_definition_with_aliases(&defn, Rc::clone(®istry))?;
|
||||
println!("Compiled policy to RVM bytecode");
|
||||
|
||||
// 4. Build normalized input.
|
||||
@@ -138,7 +134,7 @@ pub fn azure_policy_aliases(aliases: String, resource_type: Option<String>) -> R
|
||||
if let Some(ref rt) = resource_type {
|
||||
let rt_lower = rt.to_lowercase();
|
||||
let mut found = false;
|
||||
for (alias_name, _) in registry.alias_map() {
|
||||
for alias_name in registry.alias_map().keys() {
|
||||
if alias_name.to_lowercase().starts_with(&rt_lower) {
|
||||
println!(" {alias_name}");
|
||||
found = true;
|
||||
@@ -148,7 +144,7 @@ pub fn azure_policy_aliases(aliases: String, resource_type: Option<String>) -> R
|
||||
bail!("no aliases found for resource type '{rt}'");
|
||||
}
|
||||
} else {
|
||||
for (alias_name, _) in registry.alias_map() {
|
||||
for alias_name in registry.alias_map().keys() {
|
||||
println!(" {alias_name}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "regorus-mimalloc"
|
||||
description = "Vendored mimalloc allocator for regorus"
|
||||
edition = "2021"
|
||||
version = "2.2.6"
|
||||
version = "2.2.7"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ fn order_element_pairs<T: VariableBindingContext>(
|
||||
|
||||
if ready {
|
||||
let (value_expr, plan, _deps, binds) = remaining.remove(idx);
|
||||
scheduled.extend(binds.into_iter());
|
||||
scheduled.extend(binds);
|
||||
ordered.push((value_expr, plan));
|
||||
progress = true;
|
||||
break;
|
||||
|
||||
@@ -1782,6 +1782,7 @@ impl Interpreter {
|
||||
|
||||
let mut comps = self.eval_rule_ref(&rule_ref)?;
|
||||
if let Some(ke) = &key_expr {
|
||||
is_const_rule = is_const_rule && Self::is_simple_literal(ke)?;
|
||||
comps.push(self.eval_expr(ke)?);
|
||||
}
|
||||
let output = if let Some(oe) = &output_expr {
|
||||
|
||||
@@ -172,11 +172,31 @@ impl AliasRegistry {
|
||||
let prefix = alloc::format!("{}/", fq_type);
|
||||
|
||||
for alias in aliases {
|
||||
// Skip aliases without a default_path — the normalizer's
|
||||
// resolve_resource_type also skips these, so inserting them into
|
||||
// compiler maps would cause a divergence where the compiler
|
||||
// resolves the alias but normalized input never contains the field.
|
||||
if alias.default_path.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Derive the short name by stripping the resource type prefix.
|
||||
let raw_short = if alias.name.len() > prefix.len()
|
||||
&& alias.name[..prefix.len()].eq_ignore_ascii_case(&prefix)
|
||||
&& alias
|
||||
.name
|
||||
.get(..prefix.len())
|
||||
.is_some_and(|s| s.eq_ignore_ascii_case(&prefix))
|
||||
{
|
||||
alias.name[prefix.len()..].to_string()
|
||||
// Both slice boundaries are valid: prefix is ASCII
|
||||
// (resource type + '/'), so if `..prefix.len()` succeeded
|
||||
// above, `prefix.len()..` is guaranteed to be on a char
|
||||
// boundary too. The `unwrap_or` is a defensive fallback
|
||||
// that can never trigger for well-formed Azure alias names.
|
||||
alias
|
||||
.name
|
||||
.get(prefix.len()..)
|
||||
.unwrap_or(&alias.name)
|
||||
.to_string()
|
||||
} else if let Some(rest) = alias
|
||||
.name
|
||||
.rfind('/')
|
||||
@@ -260,20 +280,19 @@ impl AliasRegistry {
|
||||
.map(String::as_str)
|
||||
}
|
||||
|
||||
/// Return a clone of the alias-to-short-name map for use by the compiler.
|
||||
/// Return a reference to the alias-to-short-name map.
|
||||
///
|
||||
/// The compiler stores this map internally so it can resolve fully-qualified
|
||||
/// alias names without holding a reference to the registry.
|
||||
pub fn alias_map(&self) -> BTreeMap<String, String> {
|
||||
self.alias_to_short.clone()
|
||||
/// Keys are lowercase fully-qualified alias names; values are short names.
|
||||
pub const fn alias_map(&self) -> &BTreeMap<String, String> {
|
||||
&self.alias_to_short
|
||||
}
|
||||
|
||||
/// Return a clone of the alias-to-modifiable map for use by the compiler.
|
||||
/// Return a reference to the alias-to-modifiable map.
|
||||
///
|
||||
/// Maps lowercase fully-qualified alias names to `true` when the alias
|
||||
/// has `defaultMetadata.attributes = "Modifiable"`.
|
||||
pub fn alias_modifiable_map(&self) -> BTreeMap<String, bool> {
|
||||
self.alias_modifiable.clone()
|
||||
/// Keys are lowercase fully-qualified alias names; values are `true` when
|
||||
/// the alias has `defaultMetadata.attributes = "Modifiable"`.
|
||||
pub const fn alias_modifiable_map(&self) -> &BTreeMap<String, bool> {
|
||||
&self.alias_modifiable
|
||||
}
|
||||
|
||||
/// Normalize a raw ARM resource and wrap it in the input envelope.
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
//! 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::Rc;
|
||||
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,
|
||||
collision_safe_key, is_root_field_collision, obj_contains, obj_insert, obj_insert_rc,
|
||||
obj_remove, set_nested_lowercased, ObjMap,
|
||||
};
|
||||
use super::super::types::ResolvedAliases;
|
||||
use super::element_remap::apply_element_remap_precomputed;
|
||||
@@ -48,12 +47,14 @@ pub fn apply_alias_entries(
|
||||
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)
|
||||
if is_root_field_collision(&entry.short_name, &entry.default_path) {
|
||||
let target = collision_safe_key(&entry.short_name);
|
||||
set_nested_lowercased(result, &target, value);
|
||||
} else if entry.short_name.contains('.') {
|
||||
set_nested_lowercased(result, &entry.short_name, value);
|
||||
} else {
|
||||
entry.short_name.clone()
|
||||
};
|
||||
set_nested_lowercased(result, &target, value);
|
||||
obj_insert_rc(result, Rc::clone(&entry.short_name_lc), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,13 +85,13 @@ pub fn apply_alias_entries(
|
||||
}
|
||||
|
||||
/// Navigate an ARM path using precomputed segments (avoids per-call split).
|
||||
fn navigate_arm_path_segments(value: &Value, segments: &[String]) -> Option<Value> {
|
||||
fn navigate_arm_path_segments(value: &Value, segments: &[Rc<str>]) -> Option<Value> {
|
||||
let mut current = value;
|
||||
for segment in segments {
|
||||
current = current
|
||||
.as_object()
|
||||
.ok()?
|
||||
.get(&Value::from(segment.as_str()))?;
|
||||
.get(&Value::String(Rc::clone(segment)))?;
|
||||
}
|
||||
Some(current.clone())
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! 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::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
@@ -41,6 +41,33 @@ pub fn obj_insert(map: &mut ObjMap, key: &str, val: Value) {
|
||||
map.insert(Rc::from(key), val);
|
||||
}
|
||||
|
||||
/// Insert a key-value pair using a pre-allocated `Rc<str>` key.
|
||||
///
|
||||
/// Avoids the `Rc::from(key)` heap allocation that [`obj_insert`] performs.
|
||||
pub fn obj_insert_rc(map: &mut ObjMap, key: Rc<str>, val: Value) {
|
||||
map.insert(key, val);
|
||||
}
|
||||
|
||||
/// Lowercase a string, returning an `Rc<str>`.
|
||||
///
|
||||
/// Both paths allocate an `Rc<str>` (header + string bytes). The fast-path
|
||||
/// avoids creating an intermediate lowercased `String` when the input is
|
||||
/// already all-lowercase ASCII.
|
||||
pub fn rc_lowercase(s: &str) -> Rc<str> {
|
||||
if s.bytes().all(|b| !b.is_ascii_uppercase()) {
|
||||
Rc::from(s)
|
||||
} else {
|
||||
Rc::from(s.to_ascii_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a key-value pair with the key lowercased, using [`rc_lowercase`]
|
||||
/// for the allocation fast-path.
|
||||
pub fn obj_insert_lc(map: &mut ObjMap, key: &str, val: Value) {
|
||||
let lc = rc_lowercase(key);
|
||||
map.insert(lc, val);
|
||||
}
|
||||
|
||||
/// Check whether a key exists.
|
||||
pub fn obj_contains(map: &ObjMap, key: &str) -> bool {
|
||||
map.contains_key(key)
|
||||
@@ -112,7 +139,7 @@ pub fn set_nested_lowercased(result: &mut ObjMap, path: &str, value: Value) {
|
||||
}
|
||||
if segments.len() == 1 {
|
||||
if let Some(&seg) = segments.first() {
|
||||
obj_insert(result, &seg.to_ascii_lowercase(), value);
|
||||
obj_insert_lc(result, seg, value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -144,28 +171,28 @@ fn set_nested_inner(obj: &mut ObjMap, segments: &[&str], value: Value, lowercase
|
||||
};
|
||||
|
||||
if segments.len() == 1 {
|
||||
let key = if lowercase {
|
||||
first.to_ascii_lowercase()
|
||||
let key: Rc<str> = if lowercase {
|
||||
rc_lowercase(first)
|
||||
} else {
|
||||
first.to_string()
|
||||
Rc::from(first)
|
||||
};
|
||||
obj_insert(obj, &key, value);
|
||||
obj_insert_rc(obj, key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
let seg = if lowercase {
|
||||
first.to_ascii_lowercase()
|
||||
let seg: Rc<str> = if lowercase {
|
||||
rc_lowercase(first)
|
||||
} else {
|
||||
first.to_string()
|
||||
Rc::from(first)
|
||||
};
|
||||
|
||||
// Ensure an intermediate object exists at `seg`.
|
||||
if !obj_contains(obj, &seg) {
|
||||
obj_insert(obj, &seg, make_value(new_map()));
|
||||
if !obj.contains_key(&*seg) {
|
||||
obj_insert_rc(obj, Rc::clone(&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) {
|
||||
if let Some(Value::Object(inner_rc)) = obj.get_mut(&*seg) {
|
||||
let inner_btree = Rc::make_mut(inner_rc);
|
||||
set_nested_in_btree(
|
||||
inner_btree,
|
||||
@@ -191,12 +218,12 @@ pub fn set_nested_in_btree(
|
||||
return;
|
||||
};
|
||||
|
||||
let key_str: String = if lowercase {
|
||||
first.to_ascii_lowercase()
|
||||
let key_rc: Rc<str> = if lowercase {
|
||||
rc_lowercase(first)
|
||||
} else {
|
||||
first.to_string()
|
||||
Rc::from(first)
|
||||
};
|
||||
let key_val = Value::String(Rc::from(key_str.as_str()));
|
||||
let key_val = Value::String(Rc::clone(&key_rc));
|
||||
|
||||
if segments.len() == 1 {
|
||||
btree.insert(key_val, value);
|
||||
@@ -243,13 +270,24 @@ pub const ROOT_FIELDS: &[&str] = &[
|
||||
"extendedLocation",
|
||||
];
|
||||
|
||||
const PROPERTIES_DOT: &[u8] = b"properties.";
|
||||
|
||||
/// 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.")
|
||||
&& default_path.len() > PROPERTIES_DOT.len()
|
||||
&& default_path
|
||||
.as_bytes()
|
||||
.get(..PROPERTIES_DOT.len())
|
||||
.is_some_and(|prefix| {
|
||||
prefix
|
||||
.iter()
|
||||
.zip(PROPERTIES_DOT)
|
||||
.all(|(a, b)| a.to_ascii_lowercase() == *b)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a collision-safe key for an alias whose short name collides with a
|
||||
|
||||
@@ -18,6 +18,22 @@ use alloc::vec::Vec;
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use crate::Rc;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Deserialization helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Deserialize a `Vec<T>` that tolerates JSON `null` by mapping it to an
|
||||
/// empty vector.
|
||||
fn deserialize_null_as_empty_vec<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(Option::<Vec<T>>::deserialize(deserializer)?.unwrap_or_default())
|
||||
}
|
||||
|
||||
// ─── Top-level response wrappers ────────────────────────────────────────────
|
||||
|
||||
/// ARM API response envelope: `{ "value": [...] }`
|
||||
@@ -98,7 +114,10 @@ pub struct AliasEntry {
|
||||
|
||||
/// Versioned path entries. Empty for the vast majority of aliases that
|
||||
/// have only a `defaultPath`.
|
||||
#[serde(default)]
|
||||
///
|
||||
/// In real Azure catalog data (~97% of aliases), `az provider list` emits
|
||||
/// `"paths": null` rather than an empty array.
|
||||
#[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
|
||||
pub paths: Vec<AliasPath>,
|
||||
}
|
||||
|
||||
@@ -404,11 +423,13 @@ pub struct ResolvedEntry {
|
||||
// ── Precomputed fields (derived at registry-load time) ──────────────
|
||||
/// Whether `short_name` contains `[*]` (i.e., this is a wildcard/array alias).
|
||||
pub is_wildcard: bool,
|
||||
/// Pre-lowercased short name as `Rc<str>` for allocation-free common-case inserts.
|
||||
pub(crate) short_name_lc: Rc<str>,
|
||||
/// Precomputed `default_path.split('.').collect()` for fast ARM path navigation.
|
||||
pub default_path_segments: Vec<String>,
|
||||
pub(crate) default_path_segments: Vec<Rc<str>>,
|
||||
/// Precomputed path segments for each versioned path, in the same order
|
||||
/// as `versioned_paths`.
|
||||
pub versioned_path_segments: Vec<Vec<String>>,
|
||||
pub(crate) versioned_path_segments: Vec<Vec<Rc<str>>>,
|
||||
}
|
||||
|
||||
impl ResolvedEntry {
|
||||
@@ -420,10 +441,15 @@ impl ResolvedEntry {
|
||||
metadata: Option<AliasPathMetadata>,
|
||||
) -> Self {
|
||||
let is_wildcard = short_name.contains("[*]");
|
||||
let default_path_segments = default_path.split('.').map(String::from).collect();
|
||||
let short_name_lc = if short_name.bytes().all(|b| !b.is_ascii_uppercase()) {
|
||||
Rc::from(short_name.as_str())
|
||||
} else {
|
||||
Rc::from(short_name.to_ascii_lowercase())
|
||||
};
|
||||
let default_path_segments = default_path.split('.').map(Rc::from).collect();
|
||||
let versioned_path_segments = versioned_paths
|
||||
.iter()
|
||||
.map(|(_, p)| p.split('.').map(String::from).collect())
|
||||
.map(|(_, p)| p.split('.').map(Rc::from).collect())
|
||||
.collect();
|
||||
Self {
|
||||
short_name,
|
||||
@@ -431,6 +457,7 @@ impl ResolvedEntry {
|
||||
versioned_paths,
|
||||
metadata,
|
||||
is_wildcard,
|
||||
short_name_lc,
|
||||
default_path_segments,
|
||||
versioned_path_segments,
|
||||
}
|
||||
@@ -456,7 +483,7 @@ impl ResolvedEntry {
|
||||
/// 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] {
|
||||
pub(crate) fn select_path_segments(&self, api_version: Option<&str>) -> &[Rc<str>] {
|
||||
if let Some(ver) = api_version {
|
||||
for (i, (v, _)) in self.versioned_paths.iter().enumerate() {
|
||||
if v.eq_ignore_ascii_case(ver) {
|
||||
|
||||
@@ -18,6 +18,7 @@ use crate::rvm::program::{Program, SpanInfo};
|
||||
use crate::rvm::Instruction;
|
||||
use crate::{Rc, Value};
|
||||
|
||||
use crate::languages::azure_policy::aliases::AliasRegistry;
|
||||
use crate::languages::azure_policy::ast::PolicyRule;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -44,10 +45,9 @@ pub(super) struct Compiler {
|
||||
pub(super) cached_input_reg: Option<u8>,
|
||||
/// Cached register for `LoadContext` — allocated once on first use.
|
||||
pub(super) cached_context_reg: Option<u8>,
|
||||
/// Map from lowercase fully-qualified alias name → short name.
|
||||
pub(super) alias_map: BTreeMap<String, String>,
|
||||
/// Map from lowercase fully-qualified alias name → modifiable flag.
|
||||
pub(super) alias_modifiable: BTreeMap<String, bool>,
|
||||
/// Alias registry for resolving fully-qualified alias names.
|
||||
/// Shared via `Rc` to avoid cloning the 73K-entry alias maps.
|
||||
pub(super) alias_registry: Option<Rc<AliasRegistry>>,
|
||||
/// Default values for policy parameters.
|
||||
pub(super) parameter_defaults: Option<Value>,
|
||||
/// Cached literal-table index for `parameter_defaults` (or an empty object
|
||||
@@ -338,8 +338,13 @@ impl Compiler {
|
||||
path: &str,
|
||||
span: &crate::lexer::Span,
|
||||
) -> Result<String> {
|
||||
let alias_map = match &self.alias_registry {
|
||||
Some(reg) => reg.alias_map(),
|
||||
None => return Ok(path.to_string()),
|
||||
};
|
||||
|
||||
let lc = path.to_ascii_lowercase();
|
||||
if let Some(short) = self.alias_map.get(&lc) {
|
||||
if let Some(short) = alias_map.get(&lc) {
|
||||
let resolved = short.clone();
|
||||
let result = Self::strip_fq_prefix(&resolved).to_ascii_lowercase();
|
||||
return Ok(result);
|
||||
@@ -348,7 +353,7 @@ impl Compiler {
|
||||
// Fallback: derive array path from a corresponding `[*]` alias.
|
||||
if !lc.contains("[*]") {
|
||||
let wildcard_key = alloc::format!("{}[*]", lc);
|
||||
if let Some(short) = self.alias_map.get(&wildcard_key) {
|
||||
if let Some(short) = alias_map.get(&wildcard_key) {
|
||||
let resolved = Self::strip_fq_prefix(short).to_ascii_lowercase();
|
||||
if let Some(base) = resolved.strip_suffix("[*]") {
|
||||
return Ok(base.to_string());
|
||||
@@ -356,14 +361,14 @@ impl Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
if !self.alias_map.is_empty() && !self.alias_fallback_to_raw {
|
||||
if !alias_map.is_empty() && !self.alias_fallback_to_raw {
|
||||
bail!(span.error(&alloc::format!(
|
||||
"unknown alias '{}': field references must use fully-qualified alias names when an alias catalog is loaded",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
if self.alias_map.is_empty() {
|
||||
if alias_map.is_empty() {
|
||||
Ok(path.to_string())
|
||||
} else {
|
||||
let result = Self::strip_fq_prefix(path).to_ascii_lowercase();
|
||||
|
||||
@@ -998,7 +998,13 @@ impl Compiler {
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
Err(e) if !self.alias_map.is_empty() && !self.alias_fallback_to_raw => {
|
||||
Err(e)
|
||||
if self
|
||||
.alias_registry
|
||||
.as_ref()
|
||||
.is_some_and(|r| !r.alias_map().is_empty())
|
||||
&& !self.alias_fallback_to_raw =>
|
||||
{
|
||||
return Err(e);
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -692,13 +692,18 @@ impl Compiler {
|
||||
field_path: &str,
|
||||
span: &crate::lexer::Span,
|
||||
) -> Result<()> {
|
||||
if self.alias_modifiable.is_empty() {
|
||||
let modifiable_map = match &self.alias_registry {
|
||||
Some(reg) => reg.alias_modifiable_map(),
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
if modifiable_map.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let lc = field_path.to_lowercase();
|
||||
|
||||
if let Some(&modifiable) = self.alias_modifiable.get(&lc) {
|
||||
if let Some(&modifiable) = modifiable_map.get(&lc) {
|
||||
if !modifiable {
|
||||
bail!(span.error(&format!(
|
||||
"alias '{}' is not modifiable (defaultMetadata.attributes != 'Modifiable')",
|
||||
@@ -803,7 +808,6 @@ fn unescape_arm_literal(s: &str) -> alloc::string::String {
|
||||
/// 1. Build a template `BTreeMap` with `Value::Undefined` placeholders.
|
||||
/// 2. Sort keys by their literal value (BTreeMap order).
|
||||
/// 3. Emit `ObjectCreate`.
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
pub(super) fn build_object_from_keys(
|
||||
compiler: &mut Compiler,
|
||||
mut keys: Vec<(u16, u8)>,
|
||||
@@ -812,17 +816,33 @@ pub(super) fn build_object_from_keys(
|
||||
// Build template: object with all keys set to Undefined.
|
||||
let mut template = BTreeMap::new();
|
||||
for &(key_idx, _) in &keys {
|
||||
// SAFETY: key_idx was just returned by `add_literal_u16`, so the
|
||||
// index is guaranteed to be in bounds.
|
||||
let key_val = compiler.program.literals[usize::from(key_idx)].clone();
|
||||
// key_idx was returned by `add_literal_u16` in the calling code,
|
||||
// so it is always in bounds. We use `.get()` + `?` instead of
|
||||
// direct indexing to satisfy the crate-wide `deny(indexing_slicing)`.
|
||||
let key_val = compiler
|
||||
.program
|
||||
.literals
|
||||
.get(usize::from(key_idx))
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"internal error in build_object_from_keys: \
|
||||
literal index {} out of bounds (literals len = {})",
|
||||
key_idx,
|
||||
compiler.program.literals.len()
|
||||
)
|
||||
})?
|
||||
.clone();
|
||||
template.insert(key_val, Value::Undefined);
|
||||
}
|
||||
let template_idx = compiler.add_literal_u16(Value::Object(crate::Rc::new(template)))?;
|
||||
|
||||
// Sort keys by literal value (BTreeMap order).
|
||||
// Sort keys by literal value (BTreeMap order). All indices were
|
||||
// validated in the loop above (which returns Err for out-of-bounds),
|
||||
// so `.get()` always returns `Some` here — `None` is unreachable.
|
||||
keys.sort_by(|a, b| {
|
||||
compiler.program.literals[usize::from(a.0)]
|
||||
.cmp(&compiler.program.literals[usize::from(b.0)])
|
||||
let a_val = compiler.program.literals.get(usize::from(a.0));
|
||||
let b_val = compiler.program.literals.get(usize::from(b.0));
|
||||
a_val.cmp(&b_val)
|
||||
});
|
||||
|
||||
let dest = compiler.alloc_register()?;
|
||||
|
||||
@@ -30,11 +30,11 @@ mod metadata;
|
||||
mod template_dispatch;
|
||||
mod utils;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::{String, ToString as _};
|
||||
use alloc::string::ToString as _;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::languages::azure_policy::aliases::AliasRegistry;
|
||||
use crate::languages::azure_policy::ast::{PolicyDefinition, PolicyRule};
|
||||
use crate::rvm::program::Program;
|
||||
use crate::{Rc, Value};
|
||||
@@ -69,17 +69,14 @@ pub fn compile_policy_rule(rule: &PolicyRule) -> Result<Rc<Program>> {
|
||||
|
||||
/// Compile a parsed Azure Policy rule with alias resolution.
|
||||
///
|
||||
/// The `alias_map` maps lowercase fully-qualified alias names to their short
|
||||
/// names. Obtain it from
|
||||
/// [`AliasRegistry::alias_map()`](crate::languages::azure_policy::aliases::AliasRegistry::alias_map).
|
||||
/// The registry provides alias-to-short-name resolution and modifiability
|
||||
/// data. Pass it as an `Rc` to avoid cloning the internal alias maps.
|
||||
pub fn compile_policy_rule_with_aliases(
|
||||
rule: &PolicyRule,
|
||||
alias_map: BTreeMap<String, String>,
|
||||
alias_modifiable: BTreeMap<String, bool>,
|
||||
registry: Rc<AliasRegistry>,
|
||||
) -> Result<Rc<Program>> {
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.alias_map = alias_map;
|
||||
compiler.alias_modifiable = alias_modifiable;
|
||||
compiler.alias_registry = Some(registry);
|
||||
init_effect_annotation(&mut compiler, rule);
|
||||
compiler.compile(rule)
|
||||
}
|
||||
@@ -100,12 +97,10 @@ pub fn compile_policy_definition(defn: &PolicyDefinition) -> Result<Rc<Program>>
|
||||
/// Compile a parsed Azure Policy definition with alias resolution.
|
||||
pub fn compile_policy_definition_with_aliases(
|
||||
defn: &PolicyDefinition,
|
||||
alias_map: BTreeMap<String, String>,
|
||||
alias_modifiable: BTreeMap<String, bool>,
|
||||
registry: Rc<AliasRegistry>,
|
||||
) -> Result<Rc<Program>> {
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.alias_map = alias_map;
|
||||
compiler.alias_modifiable = alias_modifiable;
|
||||
compiler.alias_registry = Some(registry);
|
||||
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
|
||||
compiler.populate_definition_metadata(defn);
|
||||
init_effect_annotation(&mut compiler, &defn.policy_rule);
|
||||
@@ -119,13 +114,11 @@ pub fn compile_policy_definition_with_aliases(
|
||||
/// a known alias are silently treated as raw property paths.
|
||||
pub fn compile_policy_definition_with_aliases_opts(
|
||||
defn: &PolicyDefinition,
|
||||
alias_map: BTreeMap<String, String>,
|
||||
alias_modifiable: BTreeMap<String, bool>,
|
||||
registry: Rc<AliasRegistry>,
|
||||
alias_fallback_to_raw: bool,
|
||||
) -> Result<Rc<Program>> {
|
||||
let mut compiler = Compiler::new();
|
||||
compiler.alias_map = alias_map;
|
||||
compiler.alias_modifiable = alias_modifiable;
|
||||
compiler.alias_registry = Some(registry);
|
||||
compiler.alias_fallback_to_raw = alias_fallback_to_raw;
|
||||
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
|
||||
compiler.populate_definition_metadata(defn);
|
||||
|
||||
@@ -264,18 +264,17 @@ impl<'source> Parser<'source> {
|
||||
"metadata" => {
|
||||
*metadata = Some(self.parse_json_value()?);
|
||||
}
|
||||
"parameters" => {
|
||||
"parameters" if self.token_text() == "{" => {
|
||||
// Parameters must be a JSON object; if not, push to extra.
|
||||
if self.token_text() == "{" {
|
||||
*parameters = self.parse_parameter_definitions()?;
|
||||
} else {
|
||||
let value = self.parse_json_value()?;
|
||||
extra.push(ObjectEntry {
|
||||
key_span,
|
||||
key: key.into(),
|
||||
value,
|
||||
});
|
||||
}
|
||||
*parameters = self.parse_parameter_definitions()?;
|
||||
}
|
||||
"parameters" => {
|
||||
let value = self.parse_json_value()?;
|
||||
extra.push(ObjectEntry {
|
||||
key_span,
|
||||
key: key.into(),
|
||||
value,
|
||||
});
|
||||
}
|
||||
"policyrule" => {
|
||||
// Parse the policyRule directly from the token stream!
|
||||
|
||||
@@ -63,6 +63,14 @@ pub enum CompilerError {
|
||||
#[error("Invalid function expression with package")]
|
||||
InvalidFunctionExpressionWithPackage,
|
||||
|
||||
#[error("partial object rules with constant keys are not yet supported by the RVM compiler")]
|
||||
PartialObjectConstantKeyUnsupported,
|
||||
|
||||
#[error(
|
||||
"partial object rules with nested bracket keys are not yet supported by the RVM compiler"
|
||||
)]
|
||||
PartialObjectNestedKeyUnsupported,
|
||||
|
||||
#[error("Compilation error: {message}")]
|
||||
General { message: String },
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ impl<'a> Compiler<'a> {
|
||||
crate::ast::Expr::RefBrack { .. } if assign.is_some() => {
|
||||
RuleType::PartialObject
|
||||
}
|
||||
crate::ast::Expr::RefBrack { .. } => RuleType::PartialSet,
|
||||
crate::ast::Expr::RefBrack { .. } => RuleType::PartialObject,
|
||||
_ => RuleType::Complete,
|
||||
},
|
||||
_ => RuleType::Complete,
|
||||
@@ -88,6 +88,54 @@ impl<'a> Compiler<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_partial_object_shape(&self, refr: &ExprRef) -> Result<()> {
|
||||
let Expr::RefBrack {
|
||||
refr: prefix,
|
||||
index,
|
||||
..
|
||||
} = refr.as_ref()
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if Self::has_unsupported_bracket_prefix(prefix) {
|
||||
return Err(CompilerError::PartialObjectNestedKeyUnsupported.at(refr.span()));
|
||||
}
|
||||
|
||||
if Self::is_simple_literal(index) {
|
||||
return Err(CompilerError::PartialObjectConstantKeyUnsupported.at(index.span()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_unsupported_bracket_prefix(expr: &ExprRef) -> bool {
|
||||
match expr.as_ref() {
|
||||
Expr::RefBrack { refr, index, .. } => {
|
||||
!Self::is_string_literal(index) || Self::has_unsupported_bracket_prefix(refr)
|
||||
}
|
||||
Expr::RefDot { refr, .. } => Self::has_unsupported_bracket_prefix(refr),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_string_literal(expr: &ExprRef) -> bool {
|
||||
matches!(expr.as_ref(), Expr::String { .. } | Expr::RawString { .. })
|
||||
}
|
||||
|
||||
fn is_simple_literal(expr: &ExprRef) -> bool {
|
||||
match expr.as_ref() {
|
||||
Expr::String { .. }
|
||||
| Expr::RawString { .. }
|
||||
| Expr::Number { .. }
|
||||
| Expr::Bool { .. }
|
||||
| Expr::Null { .. } => true,
|
||||
// Unary expressions like `-1` are constant literals too.
|
||||
Expr::UnaryExpr { expr, .. } => Self::is_simple_literal(expr),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn get_or_assign_rule_index(&mut self, rule_path: &str) -> Result<u16> {
|
||||
if let Some(&index) = self.rule_index_map.get(rule_path) {
|
||||
return Ok(index);
|
||||
@@ -345,6 +393,10 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
let (key_expr, value_expr) = match head {
|
||||
RuleHead::Compr { refr, assign, .. } => {
|
||||
if rule_type == RuleType::PartialObject {
|
||||
self.validate_partial_object_shape(refr)?;
|
||||
}
|
||||
|
||||
self.rule_definition_function_params[rule_index as usize].push(None);
|
||||
self.rule_definition_destructuring_patterns[rule_index as usize]
|
||||
.push(None);
|
||||
|
||||
@@ -88,7 +88,7 @@ fn analyze_file(regos: &[String], expected_scopes: &[Scope]) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
scopes.sort_by(|a, b| a.0.span.line.cmp(&b.0.span.line));
|
||||
scopes.sort_by_key(|a| a.0.span.line);
|
||||
for (idx, (_, scope)) in scopes.iter().enumerate() {
|
||||
if idx > expected_scopes.len() {
|
||||
bail!("extra scope generated.")
|
||||
|
||||
@@ -23,8 +23,7 @@ use regorus::languages::azure_policy::aliases::AliasRegistry;
|
||||
use regorus::languages::azure_policy::compiler;
|
||||
use regorus::languages::azure_policy::parser;
|
||||
use regorus::rvm::RegoVM;
|
||||
use regorus::Source;
|
||||
use regorus::Value;
|
||||
use regorus::{Rc, Source, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
@@ -194,7 +193,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
||||
|
||||
// Load alias registry if an aliases file is specified.
|
||||
let alias_registry = if let Some(ref aliases_file) = test.aliases {
|
||||
let alias_registry: Option<Rc<AliasRegistry>> = if let Some(ref aliases_file) = test.aliases {
|
||||
let aliases_dir = Path::new(file)
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new("."))
|
||||
@@ -209,7 +208,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
})?;
|
||||
let mut registry = AliasRegistry::new();
|
||||
registry.load_from_json(&aliases_json)?;
|
||||
Some(registry)
|
||||
Some(Rc::new(registry))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -281,11 +280,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
);
|
||||
}
|
||||
if let Some(ref registry) = alias_registry {
|
||||
compiler::compile_policy_definition_with_aliases(
|
||||
&defn,
|
||||
registry.alias_map(),
|
||||
registry.alias_modifiable_map(),
|
||||
)
|
||||
compiler::compile_policy_definition_with_aliases(&defn, Rc::clone(registry))
|
||||
} else {
|
||||
compiler::compile_policy_definition(&defn)
|
||||
}
|
||||
@@ -308,11 +303,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
);
|
||||
}
|
||||
if let Some(ref registry) = alias_registry {
|
||||
compiler::compile_policy_rule_with_aliases(
|
||||
&ast,
|
||||
registry.alias_map(),
|
||||
registry.alias_modifiable_map(),
|
||||
)
|
||||
compiler::compile_policy_rule_with_aliases(&ast, Rc::clone(registry))
|
||||
} else {
|
||||
compiler::compile_policy_rule(&ast)
|
||||
}
|
||||
@@ -366,7 +357,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
|
||||
let mut vm = RegoVM::new();
|
||||
vm.load_program(program);
|
||||
vm.set_input(make_input(case, alias_registry.as_ref())?);
|
||||
vm.set_input(make_input(case, alias_registry.as_deref())?);
|
||||
vm.set_context(make_context(case)?);
|
||||
|
||||
// Load host-await responses (for auditIfNotExists / deployIfNotExists policies).
|
||||
@@ -391,7 +382,11 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
if let Some(rt) = effective_type {
|
||||
inject_type_field(&mut raw, rt);
|
||||
}
|
||||
normalizer::normalize(&raw, Some(registry), case.api_version.as_deref())
|
||||
normalizer::normalize(
|
||||
&raw,
|
||||
Some(registry.as_ref()),
|
||||
case.api_version.as_deref(),
|
||||
)
|
||||
} else {
|
||||
raw
|
||||
}
|
||||
|
||||
150
tests/interpreter/cases/rule/partial_object_v1.yaml
Normal file
150
tests/interpreter/cases/rule/partial_object_v1.yaml
Normal file
@@ -0,0 +1,150 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: constant_key_partial_object_v1
|
||||
data: {}
|
||||
input:
|
||||
enabled: true
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
p["fixed"] if {
|
||||
input.enabled
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
fixed: true
|
||||
|
||||
- note: multilevel_partial_object_v1
|
||||
data: {}
|
||||
input:
|
||||
nested:
|
||||
app:
|
||||
read: 1
|
||||
write: 2
|
||||
ops:
|
||||
deploy: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
p[a][b] if {
|
||||
some a, obj in input.nested
|
||||
some b, _ in obj
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
app:
|
||||
read: true
|
||||
write: true
|
||||
ops:
|
||||
deploy: true
|
||||
|
||||
- note: constant_key_partial_object_explicit_value_v1
|
||||
data: {}
|
||||
input:
|
||||
enabled: true
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
p["fixed"] := 7 if {
|
||||
input.enabled
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
fixed: 7
|
||||
|
||||
- note: multilevel_partial_object_explicit_value_v1
|
||||
data: {}
|
||||
input:
|
||||
nested:
|
||||
app:
|
||||
read: 1
|
||||
write: 2
|
||||
ops:
|
||||
deploy: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
p[a][b] := v if {
|
||||
some a, obj in input.nested
|
||||
some b, v in obj
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
app:
|
||||
read: 1
|
||||
write: 2
|
||||
ops:
|
||||
deploy: 3
|
||||
|
||||
- note: issue_712_reproducer_v0_partial_set
|
||||
data: {}
|
||||
input:
|
||||
servers:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import future.keywords.in
|
||||
|
||||
violations[k] {
|
||||
some k, _ in input.servers
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
set!: ["BAR", "BAZ", "FOO"]
|
||||
|
||||
- note: issue_712_reproducer_v1_partial_object
|
||||
data: {}
|
||||
input:
|
||||
servers:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.servers
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: issue_712_reproducer_v1_contains_partial_set
|
||||
data: {}
|
||||
input:
|
||||
servers:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations contains k if {
|
||||
some k, _ in input.servers
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
set!: ["BAR", "BAZ", "FOO"]
|
||||
16
tests/opa.rs
16
tests/opa.rs
@@ -275,6 +275,14 @@ fn is_with_keyword_unsupported_error(err: &anyhow::Error) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn is_partial_object_unsupported_error(err: &anyhow::Error) -> bool {
|
||||
err.chain().any(|cause| {
|
||||
let msg = cause.to_string();
|
||||
msg.contains("partial object rules with constant keys are not yet supported")
|
||||
|| msg.contains("partial object rules with nested bracket keys are not yet supported")
|
||||
})
|
||||
}
|
||||
|
||||
fn maybe_verify_rvm_case(case: &TestCase, is_rego_v0_test: bool, actual: &Value) -> Result<()> {
|
||||
if case.note == "defaultkeyword/function with var arg, ref head query" {
|
||||
println!(
|
||||
@@ -308,6 +316,14 @@ fn maybe_verify_rvm_case(case: &TestCase, is_rego_v0_test: bool, actual: &Value)
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if is_partial_object_unsupported_error(&err) {
|
||||
println!(
|
||||
" skipping RVM check for '{}' (partial object pattern unsupported)",
|
||||
case.note
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
939
tests/rvm/rego/cases/partial_object_rules.yaml
Normal file
939
tests/rvm/rego/cases/partial_object_rules.yaml
Normal file
@@ -0,0 +1,939 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: partial_object_variable_key_collects_all_bindings
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] if {
|
||||
some k, _ in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_explicit_value_collects_all_bindings
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] := v if {
|
||||
some k, v in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
FOO: 1
|
||||
|
||||
- note: partial_object_dynamic_expression_key_collects_all_bindings
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
aliases:
|
||||
FOO: alias-foo
|
||||
BAR: alias-bar
|
||||
BAZ: alias-baz
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[input.aliases[k]] := v if {
|
||||
some k, v in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
alias-bar: 2
|
||||
alias-baz: 3
|
||||
alias-foo: 1
|
||||
|
||||
- note: partial_object_undefined_key_skipped
|
||||
# TODO(#719): RVM incorrectly materializes undefined keys instead of
|
||||
# skipping iterations where the key is undefined.
|
||||
skip: true
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
aliases:
|
||||
FOO: alias-foo
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[input.aliases[k]] := v if {
|
||||
some k, v in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
alias-foo: 1
|
||||
|
||||
- note: partial_object_duplicate_key_last_wins
|
||||
# TODO(#719): regorus silently overwrites conflicting keys instead of
|
||||
# erroring when the same key is produced with different values.
|
||||
skip: true
|
||||
data: {}
|
||||
input: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] := v if {
|
||||
some k, v in {"a": 1}
|
||||
}
|
||||
|
||||
p[k] := v if {
|
||||
some k, v in {"a": 2}
|
||||
}
|
||||
query: data.test.p
|
||||
want_error: "conflict"
|
||||
|
||||
- note: partial_object_duplicate_key_same_value_ok
|
||||
data: {}
|
||||
input: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] if {
|
||||
some k, _ in {"a": 1}
|
||||
}
|
||||
|
||||
p[k] if {
|
||||
some k, _ in {"a": 2}
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
a: true
|
||||
|
||||
- note: partial_object_single_element_input
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
ONLY: 1
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] if {
|
||||
some k, _ in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
ONLY: true
|
||||
|
||||
- note: partial_object_static_bracket_prefix_collects_all_bindings
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p["a"][k] := v if {
|
||||
some k, v in input.items
|
||||
}
|
||||
query: data.test.p.a
|
||||
want_result:
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
FOO: 1
|
||||
|
||||
- note: partial_object_constant_key_unsupported_in_rvm
|
||||
data: {}
|
||||
input:
|
||||
enabled: true
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p["fixed"] if {
|
||||
input.enabled
|
||||
}
|
||||
query: data.test.p.fixed
|
||||
want_error: "partial object rules with constant keys are not yet supported by the RVM compiler"
|
||||
allow_interpreter_success: true
|
||||
|
||||
- note: partial_object_constant_key_explicit_value_unsupported_in_rvm
|
||||
data: {}
|
||||
input:
|
||||
enabled: true
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p["fixed"] := 7 if {
|
||||
input.enabled
|
||||
}
|
||||
query: data.test.p.fixed
|
||||
want_error: "partial object rules with constant keys are not yet supported by the RVM compiler"
|
||||
allow_interpreter_success: true
|
||||
|
||||
- note: partial_object_multiple_bodies_collects_all_bindings
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] if {
|
||||
some k, _ in input.items
|
||||
k in {"FOO", "BAR"}
|
||||
}
|
||||
|
||||
p[k] if {
|
||||
some k, _ in input.items
|
||||
k == "BAZ"
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_set_contains_collects_all_bindings
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p contains k if {
|
||||
some k, _ in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
set!: ["BAR", "BAZ", "FOO"]
|
||||
|
||||
- note: issue_712_reproducer_v1_partial_object
|
||||
data: {}
|
||||
input:
|
||||
servers:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.servers
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: issue_712_reproducer_v1_contains_partial_set
|
||||
data: {}
|
||||
input:
|
||||
servers:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations contains k if {
|
||||
some k, _ in input.servers
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
set!: ["BAR", "BAZ", "FOO"]
|
||||
|
||||
- note: partial_object_multilevel_key_unsupported_in_rvm
|
||||
data: {}
|
||||
input:
|
||||
nested:
|
||||
app:
|
||||
read: 1
|
||||
write: 2
|
||||
ops:
|
||||
deploy: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[a][b] if {
|
||||
some a, obj in input.nested
|
||||
some b, _ in obj
|
||||
}
|
||||
|
||||
main := p
|
||||
query: data.test.main
|
||||
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
|
||||
allow_interpreter_success: true
|
||||
|
||||
- note: partial_object_multilevel_key_explicit_value_unsupported_in_rvm
|
||||
data: {}
|
||||
input:
|
||||
nested:
|
||||
app:
|
||||
read: 1
|
||||
write: 2
|
||||
ops:
|
||||
deploy: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[a][b] := v if {
|
||||
some a, obj in input.nested
|
||||
some b, v in obj
|
||||
}
|
||||
|
||||
main := p
|
||||
query: data.test.main
|
||||
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
|
||||
allow_interpreter_success: true
|
||||
|
||||
- note: partial_object_hidden_dynamic_prefix_unsupported_in_rvm
|
||||
data: {}
|
||||
input:
|
||||
nested:
|
||||
app:
|
||||
q:
|
||||
read: 1
|
||||
write: 2
|
||||
ops:
|
||||
q:
|
||||
deploy: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[a].q[b] if {
|
||||
some a, obj in input.nested
|
||||
some b, _ in obj.q
|
||||
}
|
||||
|
||||
main := p
|
||||
query: data.test.main
|
||||
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
|
||||
allow_interpreter_success: true
|
||||
|
||||
- note: partial_object_array_iteration_collects_all_bindings
|
||||
data: {}
|
||||
input:
|
||||
items: ["FOO", "BAR", "BAZ"]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[v] if {
|
||||
some _, v in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_empty_input_is_empty_object
|
||||
data: {}
|
||||
input:
|
||||
items: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] if {
|
||||
some k, _ in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result: {}
|
||||
|
||||
- note: partial_object_duplicate_paths_same_key_same_value_deduplicates
|
||||
data: {}
|
||||
input:
|
||||
pairs:
|
||||
- alias: shared
|
||||
value: 1
|
||||
- alias: alpha
|
||||
value: 10
|
||||
- alias: shared
|
||||
value: 1
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[entry.alias] := entry.value if {
|
||||
some entry in input.pairs
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
alpha: 10
|
||||
shared: 1
|
||||
|
||||
- note: partial_object_duplicate_paths_same_key_different_values_conflict
|
||||
# TODO(#719): regorus silently overwrites conflicting keys instead of
|
||||
# erroring when the same key is produced with different values.
|
||||
skip: true
|
||||
data: {}
|
||||
input:
|
||||
pairs:
|
||||
- alias: shared
|
||||
value: 1
|
||||
- alias: shared
|
||||
value: 2
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[entry.alias] := entry.value if {
|
||||
some entry in input.pairs
|
||||
}
|
||||
query: data.test.p
|
||||
want_error: "conflict"
|
||||
|
||||
- note: partial_object_undefined_key_skips_iteration
|
||||
# TODO(#719): RVM incorrectly materializes undefined keys instead of
|
||||
# skipping iterations where the key is undefined.
|
||||
skip: true
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
aliases:
|
||||
FOO: alias-foo
|
||||
BAZ: alias-baz
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[input.aliases[k]] := v if {
|
||||
some k, v in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
alias-baz: 3
|
||||
alias-foo: 1
|
||||
|
||||
- note: partial_object_undefined_value_skips_iteration
|
||||
# TODO(#719): RVM incorrectly materializes undefined values instead of
|
||||
# skipping iterations where the value is undefined.
|
||||
skip: true
|
||||
data: {}
|
||||
input:
|
||||
keys: ["FOO", "BAR", "BAZ"]
|
||||
values:
|
||||
FOO: 1
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] := input.values[k] if {
|
||||
some _, k in input.keys
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
BAZ: 3
|
||||
FOO: 1
|
||||
|
||||
- note: partial_object_mixed_undefined_key_value_cases_skip_bad_iterations
|
||||
# TODO(#719): RVM incorrectly materializes undefined keys/values instead of
|
||||
# skipping iterations where the key or value is undefined.
|
||||
skip: true
|
||||
data: {}
|
||||
input:
|
||||
rows:
|
||||
- src: keep
|
||||
- src: missing_alias
|
||||
- src: missing_value
|
||||
- src: missing_both
|
||||
aliases:
|
||||
keep: alias-keep
|
||||
missing_value: alias-no-value
|
||||
values:
|
||||
keep: 1
|
||||
missing_alias: 2
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[input.aliases[row.src]] := input.values[row.src] if {
|
||||
some row in input.rows
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
alias-keep: 1
|
||||
|
||||
- note: partial_object_and_partial_set_same_name_conflict
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
FOO: 1
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] if {
|
||||
some k, _ in input.items
|
||||
}
|
||||
|
||||
p contains "shadow" if {
|
||||
true
|
||||
}
|
||||
query: data.test.p
|
||||
want_error: "has multiple types"
|
||||
|
||||
- note: partial_object_complete_rule_conflicts_with_partial_object
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
a: 1
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p := {"fixed": 1}
|
||||
|
||||
p[k] := v if {
|
||||
some k, v in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_error: "multiple types"
|
||||
|
||||
- note: partial_object_partial_set_conflicts_with_partial_object
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
a: 1
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p contains k if {
|
||||
some k, _ in input.items
|
||||
}
|
||||
|
||||
p[k] := 1 if {
|
||||
some k, _ in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_error: "multiple types"
|
||||
|
||||
- note: partial_object_large_range_counts_all_entries
|
||||
data: {}
|
||||
input: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[key] := n if {
|
||||
n := numbers.range(0, 255)[_]
|
||||
key := sprintf("k-%d", [n])
|
||||
}
|
||||
|
||||
main := count(p)
|
||||
query: data.test.main
|
||||
want_result: 256
|
||||
|
||||
- note: partial_object_rbac_duplicate_actions_deduplicate
|
||||
data:
|
||||
role_permissions:
|
||||
reader: ["read", "list"]
|
||||
writer: ["read", "write"]
|
||||
auditor: ["read", "list"]
|
||||
input:
|
||||
user_roles: ["reader", "writer", "auditor"]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
allowed_actions[action] if {
|
||||
some role in input.user_roles
|
||||
some action in data.role_permissions[role]
|
||||
}
|
||||
query: data.test.allowed_actions
|
||||
want_result:
|
||||
list: true
|
||||
read: true
|
||||
write: true
|
||||
|
||||
- note: partial_object_violations_real_world_pattern
|
||||
data: {}
|
||||
input:
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: false
|
||||
- name: worker
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
- name: sidecar
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
violations[msg] if {
|
||||
some container in input.spec.containers
|
||||
not container.securityContext.readOnlyRootFilesystem
|
||||
msg := sprintf("Container %s must use readOnlyRootFilesystem", [container.name])
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
Container api must use readOnlyRootFilesystem: true
|
||||
Container sidecar must use readOnlyRootFilesystem: true
|
||||
|
||||
- note: partial_object_resource_mapping_filters_valid_resources
|
||||
data: {}
|
||||
input:
|
||||
resources:
|
||||
svc-api:
|
||||
cpu: 1
|
||||
job-cleanup:
|
||||
cpu: 2
|
||||
svc-worker:
|
||||
cpu: 4
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
valid_resource(name) if {
|
||||
startswith(name, "svc-")
|
||||
}
|
||||
|
||||
resources[name] := config if {
|
||||
some name, config in input.resources
|
||||
valid_resource(name)
|
||||
}
|
||||
query: data.test.resources
|
||||
want_result:
|
||||
svc-api:
|
||||
cpu: 1
|
||||
svc-worker:
|
||||
cpu: 4
|
||||
|
||||
- note: partial_object_computed_concat_key_constant_body
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[concat("", ["edge", "-", "key"])] if {
|
||||
true
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
edge-key: true
|
||||
|
||||
- note: partial_object_duplicate_computed_key_same_value_merges
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
A: 0
|
||||
a: 0
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[lower(k)] := 1 if {
|
||||
some k, _ in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
a: 1
|
||||
|
||||
- note: partial_object_function_key_and_object_value
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
a: 1
|
||||
b: 2
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
f(x) := concat(":", [x, "suffix"])
|
||||
|
||||
p[f(k)] := {"nested": v + 1} if {
|
||||
some k, v in input.items
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
"a:suffix":
|
||||
nested: 2
|
||||
"b:suffix":
|
||||
nested: 3
|
||||
|
||||
- note: partial_object_array_index_key_uses_selected_elements
|
||||
data: {}
|
||||
input:
|
||||
keys: ["alpha", "beta"]
|
||||
values: [10, 20]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[input.keys[i]] := v if {
|
||||
some i, v in input.values
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
alpha: 10
|
||||
beta: 20
|
||||
|
||||
- note: partial_object_computed_empty_and_special_string_keys
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[concat("", [""])] := "empty" if {
|
||||
true
|
||||
}
|
||||
|
||||
p[concat("", ["a/b?c#d"])] := "special" if {
|
||||
true
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
"": "empty"
|
||||
a/b?c#d: "special"
|
||||
|
||||
- note: partial_object_not_filters_blocked_entries
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
allowed: true
|
||||
blocked: true
|
||||
blocked:
|
||||
blocked: true
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] if {
|
||||
some k, _ in input.items
|
||||
not input.blocked[k]
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
allowed: true
|
||||
|
||||
- note: partial_object_dot_bracket_object_value_collects_all_bindings
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
a: 1
|
||||
b: 2
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p.config[k] := {"nested": v} if {
|
||||
some k, v in input.items
|
||||
}
|
||||
query: data.test.p.config
|
||||
want_result:
|
||||
a:
|
||||
nested: 1
|
||||
b:
|
||||
nested: 2
|
||||
|
||||
- note: partial_object_dynamic_prefix_static_suffix_unsupported_in_rvm
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
a: 1
|
||||
b: 2
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k]["fixed"] := upper(k) if {
|
||||
some k, _ in input.items
|
||||
}
|
||||
|
||||
main := p
|
||||
query: data.test.main
|
||||
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
|
||||
allow_interpreter_success: true
|
||||
|
||||
- note: partial_object_literal_prefix_nested_unsupported_in_rvm
|
||||
data: {}
|
||||
input:
|
||||
items:
|
||||
a: 1
|
||||
b: 2
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[1][k] := v if {
|
||||
some k, v in input.items
|
||||
}
|
||||
|
||||
main := p
|
||||
query: data.test.main
|
||||
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
|
||||
allow_interpreter_success: true
|
||||
|
||||
- note: partial_object_three_level_nested_dynamic_unsupported_in_rvm
|
||||
data: {}
|
||||
input:
|
||||
nested:
|
||||
app:
|
||||
read: 1
|
||||
write: 2
|
||||
ops:
|
||||
deploy: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p["root"][a][b] := v if {
|
||||
some a, obj in input.nested
|
||||
some b, v in obj
|
||||
}
|
||||
|
||||
main := p
|
||||
query: data.test.main
|
||||
want_error: "partial object rules with nested bracket keys are not yet supported by the RVM compiler"
|
||||
allow_interpreter_success: true
|
||||
|
||||
- note: partial_object_with_body_unsupported_in_rvm
|
||||
data: {}
|
||||
input:
|
||||
enabled: false
|
||||
items:
|
||||
a: 1
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
gate if {
|
||||
input.enabled
|
||||
}
|
||||
|
||||
p[k] := v if {
|
||||
some k, v in input.items
|
||||
data.test.gate with input as {"enabled": true}
|
||||
}
|
||||
query: data.test.p
|
||||
want_error: "the `with` keyword is not supported by the compiler yet"
|
||||
allow_interpreter_success: true
|
||||
|
||||
- note: partial_object_every_vacuous_truth_collects_empty_arrays
|
||||
# TODO(#719): RVM currently includes the failing `bad` group here, while the
|
||||
# interpreter returns only `empty` and `ok` (expected per vacuous truth
|
||||
# semantics).
|
||||
skip: true
|
||||
data: {}
|
||||
input:
|
||||
groups:
|
||||
ok: [1, 2]
|
||||
bad: [1, 0]
|
||||
empty: []
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] if {
|
||||
some k, arr in input.groups
|
||||
every v in arr {
|
||||
v > 0
|
||||
}
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
empty: true
|
||||
ok: true
|
||||
|
||||
- note: partial_object_join_var_multiple_bindings
|
||||
data:
|
||||
a: ["1", "2", "3", "4"]
|
||||
g:
|
||||
a: ["1", "0", "0", "0"]
|
||||
b: ["0", "2", "0", "0"]
|
||||
c: ["0", "0", "0", "4"]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] := v if {
|
||||
data.a[i] = v
|
||||
data.g[k][i] = v
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
a: "1"
|
||||
b: "2"
|
||||
c: "4"
|
||||
|
||||
- note: partial_object_composite_value
|
||||
data:
|
||||
g:
|
||||
a: [1, 0, 0, 0]
|
||||
b: [0, 2, 0, 0]
|
||||
c: [0, 0, 0, 4]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] := [i, {"v2": v}] if {
|
||||
data.g[k] = x
|
||||
x[i] = v
|
||||
v != 0
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
a: [0, {v2: 1}]
|
||||
b: [1, {v2: 2}]
|
||||
c: [3, {v2: 4}]
|
||||
|
||||
- note: partial_object_true_semantics_dedupes_duplicate_keys
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p[k] if {
|
||||
ks := ["a", "b", "c", "a"]
|
||||
ks[_] = k
|
||||
}
|
||||
query: data.test.p
|
||||
want_result:
|
||||
a: true
|
||||
b: true
|
||||
c: true
|
||||
Reference in New Issue
Block a user