mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5467cd9e69 | |||
| dae3052781 | |||
| 3111bf58f2 | |||
| be3fde7706 | |||
| d2c483e93e |
@@ -8,3 +8,5 @@ steps:
|
|||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # full history needed for git diff against main
|
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
|
||||||
|
|||||||
@@ -25,15 +25,21 @@ Key constraints (details in copilot-instructions.md):
|
|||||||
## Step 1: Get the Diff
|
## Step 1: Get the Diff
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
# Primary: use gh pr diff (works in cloud agent + any PR context).
|
||||||
|| git merge-base origin/main HEAD 2>/dev/null)
|
# Fallback: git merge-base for local non-PR usage.
|
||||||
if [ -z "$BASE" ]; then
|
if gh pr diff --name-only >/dev/null 2>&1; then
|
||||||
echo "ERROR: Cannot find upstream/main or origin/main. Cannot determine review scope."
|
echo "---STAT---"
|
||||||
exit 1
|
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
|
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."
|
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
|
### Summary
|
||||||
|
|
||||||
X findings (N critical, N high, N medium, N low). One sentence overall assessment.
|
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.
|
||||||
|
|||||||
@@ -40,22 +40,25 @@ Use `read_agent` with `wait: true` to wait for each background agent.
|
|||||||
## Step 1: Get the Diff and Build Inventory
|
## Step 1: Get the Diff and Build Inventory
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
# Primary: use gh pr diff (works in cloud agent + any PR context).
|
||||||
|| git merge-base origin/main HEAD 2>/dev/null)
|
# Fallback: git merge-base for local non-PR usage.
|
||||||
if [ -z "$BASE" ]; then
|
if gh pr diff --name-only >/dev/null 2>&1; then
|
||||||
echo "ERROR: Cannot find upstream/main or origin/main."
|
echo "---STAT---"
|
||||||
exit 1
|
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
|
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."
|
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,
|
**Build a risk-classified inventory.** List every changed function, struct,
|
||||||
impl, trait, pub item, and significant code block. Number them and tag with
|
impl, trait, pub item, and significant code block. Number them and tag with
|
||||||
risk predicates:
|
risk predicates:
|
||||||
@@ -106,8 +109,10 @@ Use `model: "gpt-5.4"` in the task tool call (provides model diversity).
|
|||||||
> Get the diff:
|
> Get the diff:
|
||||||
> ```
|
> ```
|
||||||
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||||
> || git merge-base origin/main HEAD 2>/dev/null)
|
> || git merge-base origin/main HEAD 2>/dev/null \
|
||||||
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
> || 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:
|
> 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:
|
> Get the diff AND read full source files for context:
|
||||||
> ```
|
> ```
|
||||||
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||||
> || git merge-base origin/main HEAD 2>/dev/null)
|
> || git merge-base origin/main HEAD 2>/dev/null \
|
||||||
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
> || 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.
|
> 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:
|
> Get the diff:
|
||||||
> ```
|
> ```
|
||||||
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||||
> || git merge-base origin/main HEAD 2>/dev/null)
|
> || git merge-base origin/main HEAD 2>/dev/null \
|
||||||
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
> || 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.
|
> Use `view` to read surrounding context.
|
||||||
>
|
>
|
||||||
@@ -439,8 +448,10 @@ Launch **1 general-purpose agent in background mode**.
|
|||||||
> Get the diff:
|
> Get the diff:
|
||||||
> ```
|
> ```
|
||||||
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||||
> || git merge-base origin/main HEAD 2>/dev/null)
|
> || git merge-base origin/main HEAD 2>/dev/null \
|
||||||
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
> || 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.
|
> Use `view` to read full source files.
|
||||||
>
|
>
|
||||||
@@ -481,8 +492,8 @@ Launch **1 general-purpose agent in background mode**.
|
|||||||
|
|
||||||
## Step 5: Synthesize and Report
|
## Step 5: Synthesize and Report
|
||||||
|
|
||||||
**IMPORTANT:** This is the primary output. Everything above was preparation.
|
**CRITICAL:** Write the report to `/tmp/deep-review-report.md` FIRST, then display it.
|
||||||
Keep the report COMPACT — one finding per block, no filler prose.
|
Use a shell command to write the file before any other output in this step.
|
||||||
|
|
||||||
Apply verdicts from the adversarial verifier:
|
Apply verdicts from the adversarial verifier:
|
||||||
- **CONFIRMED**: keep at stated severity
|
- **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.
|
X findings (N critical, N high, N medium, N low). Y "likely" findings.
|
||||||
Z dropped (one-line reasons).
|
Z dropped (one-line reasons).
|
||||||
Risk assessment in one sentence.
|
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`
|
||||||
|
|||||||
Generated
+12
-12
@@ -180,9 +180,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.61"
|
version = "1.2.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -650,9 +650,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.0"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foldhash 0.2.0",
|
"foldhash 0.2.0",
|
||||||
]
|
]
|
||||||
@@ -835,7 +835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.17.0",
|
"hashbrown 0.17.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
@@ -881,9 +881,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonschema"
|
name = "jsonschema"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc59d2432e047d6090ba1d83c782d0128bd6203857978218f5614dbd3287281f"
|
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytecount",
|
"bytecount",
|
||||||
@@ -1353,9 +1353,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "referencing"
|
name = "referencing"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb674900ca31acd75c4aaf63f48e43e719631c0539ea5a9e64163d1296bcb730"
|
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"fluent-uri",
|
"fluent-uri",
|
||||||
@@ -1410,7 +1410,7 @@ dependencies = [
|
|||||||
"dashmap",
|
"dashmap",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"globset",
|
"globset",
|
||||||
"hashbrown 0.17.0",
|
"hashbrown 0.17.1",
|
||||||
"icu_casemap",
|
"icu_casemap",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
@@ -2193,9 +2193,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerofrom-derive",
|
"zerofrom-derive",
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-1
@@ -114,7 +114,7 @@ regex = {version = "1.12.3", optional = true, default-features = false }
|
|||||||
semver = {version = "1.0.28", optional = true, default-features = false }
|
semver = {version = "1.0.28", optional = true, default-features = false }
|
||||||
url = { version = "2.5.4", optional = true }
|
url = { version = "2.5.4", optional = true }
|
||||||
uuid = { version = "1.22.0", default-features = false, features = ["v4", "fast-rng"], optional = true }
|
uuid = { version = "1.22.0", default-features = false, features = ["v4", "fast-rng"], optional = true }
|
||||||
jsonschema = { version = "0.46.4", default-features = false, optional = true }
|
jsonschema = { version = "0.46.5", default-features = false, optional = true }
|
||||||
chrono = { version = "0.4.44", optional = true }
|
chrono = { version = "0.4.44", optional = true }
|
||||||
chrono-tz = { version = "0.10.1", optional = true }
|
chrono-tz = { version = "0.10.1", optional = true }
|
||||||
ipnet = { version = "2.12.0", optional = true, default-features = false }
|
ipnet = { version = "2.12.0", optional = true, default-features = false }
|
||||||
|
|||||||
Generated
+12
-12
@@ -172,9 +172,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.61"
|
version = "1.2.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -508,9 +508,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.0"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foldhash 0.2.0",
|
"foldhash 0.2.0",
|
||||||
]
|
]
|
||||||
@@ -687,7 +687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.17.0",
|
"hashbrown 0.17.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
@@ -724,9 +724,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonschema"
|
name = "jsonschema"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc59d2432e047d6090ba1d83c782d0128bd6203857978218f5614dbd3287281f"
|
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytecount",
|
"bytecount",
|
||||||
@@ -1082,9 +1082,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "referencing"
|
name = "referencing"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb674900ca31acd75c4aaf63f48e43e719631c0539ea5a9e64163d1296bcb730"
|
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"fluent-uri",
|
"fluent-uri",
|
||||||
@@ -1136,7 +1136,7 @@ dependencies = [
|
|||||||
"dashmap",
|
"dashmap",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"globset",
|
"globset",
|
||||||
"hashbrown 0.17.0",
|
"hashbrown 0.17.1",
|
||||||
"icu_casemap",
|
"icu_casemap",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
@@ -1837,9 +1837,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerofrom-derive",
|
"zerofrom-derive",
|
||||||
]
|
]
|
||||||
|
|||||||
Generated
+11
-11
@@ -109,9 +109,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.61"
|
version = "1.2.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -380,9 +380,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.0"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -530,7 +530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.17.0",
|
"hashbrown 0.17.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
@@ -610,9 +610,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonschema"
|
name = "jsonschema"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc59d2432e047d6090ba1d83c782d0128bd6203857978218f5614dbd3287281f"
|
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytecount",
|
"bytecount",
|
||||||
@@ -954,9 +954,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "referencing"
|
name = "referencing"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb674900ca31acd75c4aaf63f48e43e719631c0539ea5a9e64163d1296bcb730"
|
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"fluent-uri",
|
"fluent-uri",
|
||||||
@@ -1659,9 +1659,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerofrom-derive",
|
"zerofrom-derive",
|
||||||
]
|
]
|
||||||
|
|||||||
Generated
+11
-11
@@ -103,9 +103,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.61"
|
version = "1.2.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -364,9 +364,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.0"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -514,7 +514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.17.0",
|
"hashbrown 0.17.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
@@ -545,9 +545,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonschema"
|
name = "jsonschema"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc59d2432e047d6090ba1d83c782d0128bd6203857978218f5614dbd3287281f"
|
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytecount",
|
"bytecount",
|
||||||
@@ -963,9 +963,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "referencing"
|
name = "referencing"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb674900ca31acd75c4aaf63f48e43e719631c0539ea5a9e64163d1296bcb730"
|
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"fluent-uri",
|
"fluent-uri",
|
||||||
@@ -1613,9 +1613,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerofrom-derive",
|
"zerofrom-derive",
|
||||||
]
|
]
|
||||||
|
|||||||
Generated
+11
-11
@@ -121,9 +121,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.61"
|
version = "1.2.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -393,9 +393,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.0"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -543,7 +543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.17.0",
|
"hashbrown 0.17.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
@@ -583,9 +583,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonschema"
|
name = "jsonschema"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc59d2432e047d6090ba1d83c782d0128bd6203857978218f5614dbd3287281f"
|
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytecount",
|
"bytecount",
|
||||||
@@ -994,9 +994,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "referencing"
|
name = "referencing"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb674900ca31acd75c4aaf63f48e43e719631c0539ea5a9e64163d1296bcb730"
|
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"fluent-uri",
|
"fluent-uri",
|
||||||
@@ -1672,9 +1672,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerofrom-derive",
|
"zerofrom-derive",
|
||||||
]
|
]
|
||||||
|
|||||||
Generated
+11
-11
@@ -120,9 +120,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.61"
|
version = "1.2.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
@@ -396,9 +396,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.0"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -546,7 +546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.17.0",
|
"hashbrown 0.17.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
@@ -577,9 +577,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonschema"
|
name = "jsonschema"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc59d2432e047d6090ba1d83c782d0128bd6203857978218f5614dbd3287281f"
|
checksum = "6a5fe5206f06e589caf25e79fc05ccdf91fca745685fe9fe1a13bbdfb479a631"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytecount",
|
"bytecount",
|
||||||
@@ -953,9 +953,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "referencing"
|
name = "referencing"
|
||||||
version = "0.46.4"
|
version = "0.46.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb674900ca31acd75c4aaf63f48e43e719631c0539ea5a9e64163d1296bcb730"
|
checksum = "69e4e17ef386c5383591d07623d3de49cbc601156e7582973e6db98d66a57de2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"fluent-uri",
|
"fluent-uri",
|
||||||
@@ -1692,9 +1692,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerofrom"
|
name = "zerofrom"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerofrom-derive",
|
"zerofrom-derive",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ use regorus::languages::azure_policy::aliases::AliasRegistry;
|
|||||||
use regorus::languages::azure_policy::compiler;
|
use regorus::languages::azure_policy::compiler;
|
||||||
use regorus::languages::azure_policy::parser;
|
use regorus::languages::azure_policy::parser;
|
||||||
use regorus::rvm::RegoVM;
|
use regorus::rvm::RegoVM;
|
||||||
use regorus::Source;
|
use regorus::{Rc, Source, Value};
|
||||||
use regorus::Value;
|
|
||||||
|
|
||||||
/// Evaluate an Azure Policy definition against a resource.
|
/// Evaluate an Azure Policy definition against a resource.
|
||||||
///
|
///
|
||||||
@@ -60,11 +59,8 @@ pub fn azure_policy_eval(
|
|||||||
println!("Parsed policy definition from {policy_definition}");
|
println!("Parsed policy definition from {policy_definition}");
|
||||||
|
|
||||||
// 3. Compile to RVM bytecode.
|
// 3. Compile to RVM bytecode.
|
||||||
let program = compiler::compile_policy_definition_with_aliases(
|
let registry = Rc::new(registry);
|
||||||
&defn,
|
let program = compiler::compile_policy_definition_with_aliases(&defn, Rc::clone(®istry))?;
|
||||||
registry.alias_map(),
|
|
||||||
registry.alias_modifiable_map(),
|
|
||||||
)?;
|
|
||||||
println!("Compiled policy to RVM bytecode");
|
println!("Compiled policy to RVM bytecode");
|
||||||
|
|
||||||
// 4. Build normalized input.
|
// 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 {
|
if let Some(ref rt) = resource_type {
|
||||||
let rt_lower = rt.to_lowercase();
|
let rt_lower = rt.to_lowercase();
|
||||||
let mut found = false;
|
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) {
|
if alias_name.to_lowercase().starts_with(&rt_lower) {
|
||||||
println!(" {alias_name}");
|
println!(" {alias_name}");
|
||||||
found = true;
|
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}'");
|
bail!("no aliases found for resource type '{rt}'");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (alias_name, _) in registry.alias_map() {
|
for alias_name in registry.alias_map().keys() {
|
||||||
println!(" {alias_name}");
|
println!(" {alias_name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1782,6 +1782,7 @@ impl Interpreter {
|
|||||||
|
|
||||||
let mut comps = self.eval_rule_ref(&rule_ref)?;
|
let mut comps = self.eval_rule_ref(&rule_ref)?;
|
||||||
if let Some(ke) = &key_expr {
|
if let Some(ke) = &key_expr {
|
||||||
|
is_const_rule = is_const_rule && Self::is_simple_literal(ke)?;
|
||||||
comps.push(self.eval_expr(ke)?);
|
comps.push(self.eval_expr(ke)?);
|
||||||
}
|
}
|
||||||
let output = if let Some(oe) = &output_expr {
|
let output = if let Some(oe) = &output_expr {
|
||||||
|
|||||||
@@ -172,11 +172,31 @@ impl AliasRegistry {
|
|||||||
let prefix = alloc::format!("{}/", fq_type);
|
let prefix = alloc::format!("{}/", fq_type);
|
||||||
|
|
||||||
for alias in aliases {
|
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.
|
// Derive the short name by stripping the resource type prefix.
|
||||||
let raw_short = if alias.name.len() > prefix.len()
|
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
|
} else if let Some(rest) = alias
|
||||||
.name
|
.name
|
||||||
.rfind('/')
|
.rfind('/')
|
||||||
@@ -260,20 +280,19 @@ impl AliasRegistry {
|
|||||||
.map(String::as_str)
|
.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
|
/// Keys are lowercase fully-qualified alias names; values are short names.
|
||||||
/// alias names without holding a reference to the registry.
|
pub const fn alias_map(&self) -> &BTreeMap<String, String> {
|
||||||
pub fn alias_map(&self) -> BTreeMap<String, String> {
|
&self.alias_to_short
|
||||||
self.alias_to_short.clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
/// Keys are lowercase fully-qualified alias names; values are `true` when
|
||||||
/// has `defaultMetadata.attributes = "Modifiable"`.
|
/// the alias has `defaultMetadata.attributes = "Modifiable"`.
|
||||||
pub fn alias_modifiable_map(&self) -> BTreeMap<String, bool> {
|
pub const fn alias_modifiable_map(&self) -> &BTreeMap<String, bool> {
|
||||||
self.alias_modifiable.clone()
|
&self.alias_modifiable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Normalize a raw ARM resource and wrap it in the input envelope.
|
/// 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
|
//! Per-alias path resolution: reads values from versioned ARM paths and places
|
||||||
//! them at alias short name paths in the normalized output.
|
//! them at alias short name paths in the normalized output.
|
||||||
|
|
||||||
use alloc::string::String;
|
use crate::Rc;
|
||||||
|
|
||||||
use crate::Value;
|
use crate::Value;
|
||||||
|
|
||||||
use super::super::obj_map::remove_element_field;
|
use super::super::obj_map::remove_element_field;
|
||||||
use super::super::obj_map::{
|
use super::super::obj_map::{
|
||||||
collision_safe_key, is_root_field_collision, obj_contains, obj_insert, obj_remove,
|
collision_safe_key, is_root_field_collision, obj_contains, obj_insert, obj_insert_rc,
|
||||||
set_nested_lowercased, ObjMap,
|
obj_remove, set_nested_lowercased, ObjMap,
|
||||||
};
|
};
|
||||||
use super::super::types::ResolvedAliases;
|
use super::super::types::ResolvedAliases;
|
||||||
use super::element_remap::apply_element_remap_precomputed;
|
use super::element_remap::apply_element_remap_precomputed;
|
||||||
@@ -48,12 +47,14 @@ pub fn apply_alias_entries(
|
|||||||
if let Some(value) = value {
|
if let Some(value) = value {
|
||||||
let value = normalize_value(&value, &entry.short_name, None);
|
let value = normalize_value(&value, &entry.short_name, None);
|
||||||
|
|
||||||
let target = if is_root_field_collision(&entry.short_name, &entry.default_path) {
|
if is_root_field_collision(&entry.short_name, &entry.default_path) {
|
||||||
collision_safe_key(&entry.short_name)
|
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 {
|
} else {
|
||||||
entry.short_name.clone()
|
obj_insert_rc(result, Rc::clone(&entry.short_name_lc), value);
|
||||||
};
|
}
|
||||||
set_nested_lowercased(result, &target, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,13 +85,13 @@ pub fn apply_alias_entries(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Navigate an ARM path using precomputed segments (avoids per-call split).
|
/// 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;
|
let mut current = value;
|
||||||
for segment in segments {
|
for segment in segments {
|
||||||
current = current
|
current = current
|
||||||
.as_object()
|
.as_object()
|
||||||
.ok()?
|
.ok()?
|
||||||
.get(&Value::from(segment.as_str()))?;
|
.get(&Value::String(Rc::clone(segment)))?;
|
||||||
}
|
}
|
||||||
Some(current.clone())
|
Some(current.clone())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//! then converts to `Value::Object` (a `BTreeMap<Value, Value>`) only at
|
//! then converts to `Value::Object` (a `BTreeMap<Value, Value>`) only at
|
||||||
//! the output boundary via [`make_value`].
|
//! the output boundary via [`make_value`].
|
||||||
|
|
||||||
use alloc::string::{String, ToString as _};
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@@ -41,6 +41,33 @@ pub fn obj_insert(map: &mut ObjMap, key: &str, val: Value) {
|
|||||||
map.insert(Rc::from(key), val);
|
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.
|
/// Check whether a key exists.
|
||||||
pub fn obj_contains(map: &ObjMap, key: &str) -> bool {
|
pub fn obj_contains(map: &ObjMap, key: &str) -> bool {
|
||||||
map.contains_key(key)
|
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 segments.len() == 1 {
|
||||||
if let Some(&seg) = segments.first() {
|
if let Some(&seg) = segments.first() {
|
||||||
obj_insert(result, &seg.to_ascii_lowercase(), value);
|
obj_insert_lc(result, seg, value);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -144,28 +171,28 @@ fn set_nested_inner(obj: &mut ObjMap, segments: &[&str], value: Value, lowercase
|
|||||||
};
|
};
|
||||||
|
|
||||||
if segments.len() == 1 {
|
if segments.len() == 1 {
|
||||||
let key = if lowercase {
|
let key: Rc<str> = if lowercase {
|
||||||
first.to_ascii_lowercase()
|
rc_lowercase(first)
|
||||||
} else {
|
} else {
|
||||||
first.to_string()
|
Rc::from(first)
|
||||||
};
|
};
|
||||||
obj_insert(obj, &key, value);
|
obj_insert_rc(obj, key, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let seg = if lowercase {
|
let seg: Rc<str> = if lowercase {
|
||||||
first.to_ascii_lowercase()
|
rc_lowercase(first)
|
||||||
} else {
|
} else {
|
||||||
first.to_string()
|
Rc::from(first)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure an intermediate object exists at `seg`.
|
// Ensure an intermediate object exists at `seg`.
|
||||||
if !obj_contains(obj, &seg) {
|
if !obj.contains_key(&*seg) {
|
||||||
obj_insert(obj, &seg, make_value(new_map()));
|
obj_insert_rc(obj, Rc::clone(&seg), make_value(new_map()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Descend directly into the BTreeMap, avoiding ObjMap round-trip.
|
// 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);
|
let inner_btree = Rc::make_mut(inner_rc);
|
||||||
set_nested_in_btree(
|
set_nested_in_btree(
|
||||||
inner_btree,
|
inner_btree,
|
||||||
@@ -191,12 +218,12 @@ pub fn set_nested_in_btree(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let key_str: String = if lowercase {
|
let key_rc: Rc<str> = if lowercase {
|
||||||
first.to_ascii_lowercase()
|
rc_lowercase(first)
|
||||||
} else {
|
} 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 {
|
if segments.len() == 1 {
|
||||||
btree.insert(key_val, value);
|
btree.insert(key_val, value);
|
||||||
@@ -243,13 +270,24 @@ pub const ROOT_FIELDS: &[&str] = &[
|
|||||||
"extendedLocation",
|
"extendedLocation",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const PROPERTIES_DOT: &[u8] = b"properties.";
|
||||||
|
|
||||||
/// Check whether an alias short name collides with a reserved ARM root field
|
/// Check whether an alias short name collides with a reserved ARM root field
|
||||||
/// and needs a collision-safe key.
|
/// and needs a collision-safe key.
|
||||||
pub fn is_root_field_collision(short_name: &str, default_path: &str) -> bool {
|
pub fn is_root_field_collision(short_name: &str, default_path: &str) -> bool {
|
||||||
ROOT_FIELDS
|
ROOT_FIELDS
|
||||||
.iter()
|
.iter()
|
||||||
.any(|f| f.eq_ignore_ascii_case(short_name))
|
.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
|
/// 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 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 ────────────────────────────────────────────
|
// ─── Top-level response wrappers ────────────────────────────────────────────
|
||||||
|
|
||||||
/// ARM API response envelope: `{ "value": [...] }`
|
/// ARM API response envelope: `{ "value": [...] }`
|
||||||
@@ -98,7 +114,10 @@ pub struct AliasEntry {
|
|||||||
|
|
||||||
/// Versioned path entries. Empty for the vast majority of aliases that
|
/// Versioned path entries. Empty for the vast majority of aliases that
|
||||||
/// have only a `defaultPath`.
|
/// 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>,
|
pub paths: Vec<AliasPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,11 +423,13 @@ pub struct ResolvedEntry {
|
|||||||
// ── Precomputed fields (derived at registry-load time) ──────────────
|
// ── Precomputed fields (derived at registry-load time) ──────────────
|
||||||
/// Whether `short_name` contains `[*]` (i.e., this is a wildcard/array alias).
|
/// Whether `short_name` contains `[*]` (i.e., this is a wildcard/array alias).
|
||||||
pub is_wildcard: bool,
|
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.
|
/// 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
|
/// Precomputed path segments for each versioned path, in the same order
|
||||||
/// as `versioned_paths`.
|
/// as `versioned_paths`.
|
||||||
pub versioned_path_segments: Vec<Vec<String>>,
|
pub(crate) versioned_path_segments: Vec<Vec<Rc<str>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolvedEntry {
|
impl ResolvedEntry {
|
||||||
@@ -420,10 +441,15 @@ impl ResolvedEntry {
|
|||||||
metadata: Option<AliasPathMetadata>,
|
metadata: Option<AliasPathMetadata>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let is_wildcard = short_name.contains("[*]");
|
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
|
let versioned_path_segments = versioned_paths
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, p)| p.split('.').map(String::from).collect())
|
.map(|(_, p)| p.split('.').map(Rc::from).collect())
|
||||||
.collect();
|
.collect();
|
||||||
Self {
|
Self {
|
||||||
short_name,
|
short_name,
|
||||||
@@ -431,6 +457,7 @@ impl ResolvedEntry {
|
|||||||
versioned_paths,
|
versioned_paths,
|
||||||
metadata,
|
metadata,
|
||||||
is_wildcard,
|
is_wildcard,
|
||||||
|
short_name_lc,
|
||||||
default_path_segments,
|
default_path_segments,
|
||||||
versioned_path_segments,
|
versioned_path_segments,
|
||||||
}
|
}
|
||||||
@@ -456,7 +483,7 @@ impl ResolvedEntry {
|
|||||||
/// Returns the versioned segments if `api_version` matches, otherwise
|
/// Returns the versioned segments if `api_version` matches, otherwise
|
||||||
/// the default segments. This avoids per-call `split('.')` for both
|
/// the default segments. This avoids per-call `split('.')` for both
|
||||||
/// default and versioned scalar alias navigation.
|
/// 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 {
|
if let Some(ver) = api_version {
|
||||||
for (i, (v, _)) in self.versioned_paths.iter().enumerate() {
|
for (i, (v, _)) in self.versioned_paths.iter().enumerate() {
|
||||||
if v.eq_ignore_ascii_case(ver) {
|
if v.eq_ignore_ascii_case(ver) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use crate::rvm::program::{Program, SpanInfo};
|
|||||||
use crate::rvm::Instruction;
|
use crate::rvm::Instruction;
|
||||||
use crate::{Rc, Value};
|
use crate::{Rc, Value};
|
||||||
|
|
||||||
|
use crate::languages::azure_policy::aliases::AliasRegistry;
|
||||||
use crate::languages::azure_policy::ast::PolicyRule;
|
use crate::languages::azure_policy::ast::PolicyRule;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -44,10 +45,9 @@ pub(super) struct Compiler {
|
|||||||
pub(super) cached_input_reg: Option<u8>,
|
pub(super) cached_input_reg: Option<u8>,
|
||||||
/// Cached register for `LoadContext` — allocated once on first use.
|
/// Cached register for `LoadContext` — allocated once on first use.
|
||||||
pub(super) cached_context_reg: Option<u8>,
|
pub(super) cached_context_reg: Option<u8>,
|
||||||
/// Map from lowercase fully-qualified alias name → short name.
|
/// Alias registry for resolving fully-qualified alias names.
|
||||||
pub(super) alias_map: BTreeMap<String, String>,
|
/// Shared via `Rc` to avoid cloning the 73K-entry alias maps.
|
||||||
/// Map from lowercase fully-qualified alias name → modifiable flag.
|
pub(super) alias_registry: Option<Rc<AliasRegistry>>,
|
||||||
pub(super) alias_modifiable: BTreeMap<String, bool>,
|
|
||||||
/// Default values for policy parameters.
|
/// Default values for policy parameters.
|
||||||
pub(super) parameter_defaults: Option<Value>,
|
pub(super) parameter_defaults: Option<Value>,
|
||||||
/// Cached literal-table index for `parameter_defaults` (or an empty object
|
/// Cached literal-table index for `parameter_defaults` (or an empty object
|
||||||
@@ -338,8 +338,13 @@ impl Compiler {
|
|||||||
path: &str,
|
path: &str,
|
||||||
span: &crate::lexer::Span,
|
span: &crate::lexer::Span,
|
||||||
) -> Result<String> {
|
) -> 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();
|
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 resolved = short.clone();
|
||||||
let result = Self::strip_fq_prefix(&resolved).to_ascii_lowercase();
|
let result = Self::strip_fq_prefix(&resolved).to_ascii_lowercase();
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
@@ -348,7 +353,7 @@ impl Compiler {
|
|||||||
// Fallback: derive array path from a corresponding `[*]` alias.
|
// Fallback: derive array path from a corresponding `[*]` alias.
|
||||||
if !lc.contains("[*]") {
|
if !lc.contains("[*]") {
|
||||||
let wildcard_key = alloc::format!("{}[*]", lc);
|
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();
|
let resolved = Self::strip_fq_prefix(short).to_ascii_lowercase();
|
||||||
if let Some(base) = resolved.strip_suffix("[*]") {
|
if let Some(base) = resolved.strip_suffix("[*]") {
|
||||||
return Ok(base.to_string());
|
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!(
|
bail!(span.error(&alloc::format!(
|
||||||
"unknown alias '{}': field references must use fully-qualified alias names when an alias catalog is loaded",
|
"unknown alias '{}': field references must use fully-qualified alias names when an alias catalog is loaded",
|
||||||
path
|
path
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.alias_map.is_empty() {
|
if alias_map.is_empty() {
|
||||||
Ok(path.to_string())
|
Ok(path.to_string())
|
||||||
} else {
|
} else {
|
||||||
let result = Self::strip_fq_prefix(path).to_ascii_lowercase();
|
let result = Self::strip_fq_prefix(path).to_ascii_lowercase();
|
||||||
|
|||||||
@@ -998,7 +998,13 @@ impl Compiler {
|
|||||||
return Ok(result);
|
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);
|
return Err(e);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -692,13 +692,18 @@ impl Compiler {
|
|||||||
field_path: &str,
|
field_path: &str,
|
||||||
span: &crate::lexer::Span,
|
span: &crate::lexer::Span,
|
||||||
) -> Result<()> {
|
) -> 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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let lc = field_path.to_lowercase();
|
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 {
|
if !modifiable {
|
||||||
bail!(span.error(&format!(
|
bail!(span.error(&format!(
|
||||||
"alias '{}' is not modifiable (defaultMetadata.attributes != 'Modifiable')",
|
"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.
|
/// 1. Build a template `BTreeMap` with `Value::Undefined` placeholders.
|
||||||
/// 2. Sort keys by their literal value (BTreeMap order).
|
/// 2. Sort keys by their literal value (BTreeMap order).
|
||||||
/// 3. Emit `ObjectCreate`.
|
/// 3. Emit `ObjectCreate`.
|
||||||
#[allow(clippy::indexing_slicing)]
|
|
||||||
pub(super) fn build_object_from_keys(
|
pub(super) fn build_object_from_keys(
|
||||||
compiler: &mut Compiler,
|
compiler: &mut Compiler,
|
||||||
mut keys: Vec<(u16, u8)>,
|
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.
|
// Build template: object with all keys set to Undefined.
|
||||||
let mut template = BTreeMap::new();
|
let mut template = BTreeMap::new();
|
||||||
for &(key_idx, _) in &keys {
|
for &(key_idx, _) in &keys {
|
||||||
// SAFETY: key_idx was just returned by `add_literal_u16`, so the
|
// key_idx was returned by `add_literal_u16` in the calling code,
|
||||||
// index is guaranteed to be in bounds.
|
// so it is always in bounds. We use `.get()` + `?` instead of
|
||||||
let key_val = compiler.program.literals[usize::from(key_idx)].clone();
|
// 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);
|
template.insert(key_val, Value::Undefined);
|
||||||
}
|
}
|
||||||
let template_idx = compiler.add_literal_u16(Value::Object(crate::Rc::new(template)))?;
|
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| {
|
keys.sort_by(|a, b| {
|
||||||
compiler.program.literals[usize::from(a.0)]
|
let a_val = compiler.program.literals.get(usize::from(a.0));
|
||||||
.cmp(&compiler.program.literals[usize::from(b.0)])
|
let b_val = compiler.program.literals.get(usize::from(b.0));
|
||||||
|
a_val.cmp(&b_val)
|
||||||
});
|
});
|
||||||
|
|
||||||
let dest = compiler.alloc_register()?;
|
let dest = compiler.alloc_register()?;
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ mod metadata;
|
|||||||
mod template_dispatch;
|
mod template_dispatch;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use alloc::collections::BTreeMap;
|
use alloc::string::ToString as _;
|
||||||
use alloc::string::{String, ToString as _};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::languages::azure_policy::aliases::AliasRegistry;
|
||||||
use crate::languages::azure_policy::ast::{PolicyDefinition, PolicyRule};
|
use crate::languages::azure_policy::ast::{PolicyDefinition, PolicyRule};
|
||||||
use crate::rvm::program::Program;
|
use crate::rvm::program::Program;
|
||||||
use crate::{Rc, Value};
|
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.
|
/// Compile a parsed Azure Policy rule with alias resolution.
|
||||||
///
|
///
|
||||||
/// The `alias_map` maps lowercase fully-qualified alias names to their short
|
/// The registry provides alias-to-short-name resolution and modifiability
|
||||||
/// names. Obtain it from
|
/// data. Pass it as an `Rc` to avoid cloning the internal alias maps.
|
||||||
/// [`AliasRegistry::alias_map()`](crate::languages::azure_policy::aliases::AliasRegistry::alias_map).
|
|
||||||
pub fn compile_policy_rule_with_aliases(
|
pub fn compile_policy_rule_with_aliases(
|
||||||
rule: &PolicyRule,
|
rule: &PolicyRule,
|
||||||
alias_map: BTreeMap<String, String>,
|
registry: Rc<AliasRegistry>,
|
||||||
alias_modifiable: BTreeMap<String, bool>,
|
|
||||||
) -> Result<Rc<Program>> {
|
) -> Result<Rc<Program>> {
|
||||||
let mut compiler = Compiler::new();
|
let mut compiler = Compiler::new();
|
||||||
compiler.alias_map = alias_map;
|
compiler.alias_registry = Some(registry);
|
||||||
compiler.alias_modifiable = alias_modifiable;
|
|
||||||
init_effect_annotation(&mut compiler, rule);
|
init_effect_annotation(&mut compiler, rule);
|
||||||
compiler.compile(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.
|
/// Compile a parsed Azure Policy definition with alias resolution.
|
||||||
pub fn compile_policy_definition_with_aliases(
|
pub fn compile_policy_definition_with_aliases(
|
||||||
defn: &PolicyDefinition,
|
defn: &PolicyDefinition,
|
||||||
alias_map: BTreeMap<String, String>,
|
registry: Rc<AliasRegistry>,
|
||||||
alias_modifiable: BTreeMap<String, bool>,
|
|
||||||
) -> Result<Rc<Program>> {
|
) -> Result<Rc<Program>> {
|
||||||
let mut compiler = Compiler::new();
|
let mut compiler = Compiler::new();
|
||||||
compiler.alias_map = alias_map;
|
compiler.alias_registry = Some(registry);
|
||||||
compiler.alias_modifiable = alias_modifiable;
|
|
||||||
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
|
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
|
||||||
compiler.populate_definition_metadata(defn);
|
compiler.populate_definition_metadata(defn);
|
||||||
init_effect_annotation(&mut compiler, &defn.policy_rule);
|
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.
|
/// a known alias are silently treated as raw property paths.
|
||||||
pub fn compile_policy_definition_with_aliases_opts(
|
pub fn compile_policy_definition_with_aliases_opts(
|
||||||
defn: &PolicyDefinition,
|
defn: &PolicyDefinition,
|
||||||
alias_map: BTreeMap<String, String>,
|
registry: Rc<AliasRegistry>,
|
||||||
alias_modifiable: BTreeMap<String, bool>,
|
|
||||||
alias_fallback_to_raw: bool,
|
alias_fallback_to_raw: bool,
|
||||||
) -> Result<Rc<Program>> {
|
) -> Result<Rc<Program>> {
|
||||||
let mut compiler = Compiler::new();
|
let mut compiler = Compiler::new();
|
||||||
compiler.alias_map = alias_map;
|
compiler.alias_registry = Some(registry);
|
||||||
compiler.alias_modifiable = alias_modifiable;
|
|
||||||
compiler.alias_fallback_to_raw = alias_fallback_to_raw;
|
compiler.alias_fallback_to_raw = alias_fallback_to_raw;
|
||||||
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
|
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
|
||||||
compiler.populate_definition_metadata(defn);
|
compiler.populate_definition_metadata(defn);
|
||||||
|
|||||||
@@ -63,6 +63,14 @@ pub enum CompilerError {
|
|||||||
#[error("Invalid function expression with package")]
|
#[error("Invalid function expression with package")]
|
||||||
InvalidFunctionExpressionWithPackage,
|
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}")]
|
#[error("Compilation error: {message}")]
|
||||||
General { message: String },
|
General { message: String },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ impl<'a> Compiler<'a> {
|
|||||||
crate::ast::Expr::RefBrack { .. } if assign.is_some() => {
|
crate::ast::Expr::RefBrack { .. } if assign.is_some() => {
|
||||||
RuleType::PartialObject
|
RuleType::PartialObject
|
||||||
}
|
}
|
||||||
crate::ast::Expr::RefBrack { .. } => RuleType::PartialSet,
|
crate::ast::Expr::RefBrack { .. } => RuleType::PartialObject,
|
||||||
_ => RuleType::Complete,
|
_ => RuleType::Complete,
|
||||||
},
|
},
|
||||||
_ => 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> {
|
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) {
|
if let Some(&index) = self.rule_index_map.get(rule_path) {
|
||||||
return Ok(index);
|
return Ok(index);
|
||||||
@@ -345,6 +393,10 @@ impl<'a> Compiler<'a> {
|
|||||||
|
|
||||||
let (key_expr, value_expr) = match head {
|
let (key_expr, value_expr) = match head {
|
||||||
RuleHead::Compr { refr, assign, .. } => {
|
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_function_params[rule_index as usize].push(None);
|
||||||
self.rule_definition_destructuring_patterns[rule_index as usize]
|
self.rule_definition_destructuring_patterns[rule_index as usize]
|
||||||
.push(None);
|
.push(None);
|
||||||
|
|||||||
+11
-16
@@ -23,8 +23,7 @@ use regorus::languages::azure_policy::aliases::AliasRegistry;
|
|||||||
use regorus::languages::azure_policy::compiler;
|
use regorus::languages::azure_policy::compiler;
|
||||||
use regorus::languages::azure_policy::parser;
|
use regorus::languages::azure_policy::parser;
|
||||||
use regorus::rvm::RegoVM;
|
use regorus::rvm::RegoVM;
|
||||||
use regorus::Source;
|
use regorus::{Rc, Source, Value};
|
||||||
use regorus::Value;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -194,7 +193,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
||||||
|
|
||||||
// Load alias registry if an aliases file is specified.
|
// 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)
|
let aliases_dir = Path::new(file)
|
||||||
.parent()
|
.parent()
|
||||||
.unwrap_or_else(|| Path::new("."))
|
.unwrap_or_else(|| Path::new("."))
|
||||||
@@ -209,7 +208,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
})?;
|
})?;
|
||||||
let mut registry = AliasRegistry::new();
|
let mut registry = AliasRegistry::new();
|
||||||
registry.load_from_json(&aliases_json)?;
|
registry.load_from_json(&aliases_json)?;
|
||||||
Some(registry)
|
Some(Rc::new(registry))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -281,11 +280,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some(ref registry) = alias_registry {
|
if let Some(ref registry) = alias_registry {
|
||||||
compiler::compile_policy_definition_with_aliases(
|
compiler::compile_policy_definition_with_aliases(&defn, Rc::clone(registry))
|
||||||
&defn,
|
|
||||||
registry.alias_map(),
|
|
||||||
registry.alias_modifiable_map(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
compiler::compile_policy_definition(&defn)
|
compiler::compile_policy_definition(&defn)
|
||||||
}
|
}
|
||||||
@@ -308,11 +303,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some(ref registry) = alias_registry {
|
if let Some(ref registry) = alias_registry {
|
||||||
compiler::compile_policy_rule_with_aliases(
|
compiler::compile_policy_rule_with_aliases(&ast, Rc::clone(registry))
|
||||||
&ast,
|
|
||||||
registry.alias_map(),
|
|
||||||
registry.alias_modifiable_map(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
compiler::compile_policy_rule(&ast)
|
compiler::compile_policy_rule(&ast)
|
||||||
}
|
}
|
||||||
@@ -366,7 +357,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
|
|
||||||
let mut vm = RegoVM::new();
|
let mut vm = RegoVM::new();
|
||||||
vm.load_program(program);
|
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)?);
|
vm.set_context(make_context(case)?);
|
||||||
|
|
||||||
// Load host-await responses (for auditIfNotExists / deployIfNotExists policies).
|
// 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 {
|
if let Some(rt) = effective_type {
|
||||||
inject_type_field(&mut raw, rt);
|
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 {
|
} else {
|
||||||
raw
|
raw
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -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<()> {
|
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" {
|
if case.note == "defaultkeyword/function with var arg, ref head query" {
|
||||||
println!(
|
println!(
|
||||||
@@ -308,6 +316,14 @@ fn maybe_verify_rvm_case(case: &TestCase, is_rego_v0_test: bool, actual: &Value)
|
|||||||
return Ok(());
|
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);
|
return Err(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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