mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
7 Commits
main
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
639bfe3246 | ||
|
|
4cc82e2fda | ||
|
|
196b6d68aa | ||
|
|
c65e844f63 | ||
|
|
730e6de75a | ||
|
|
72515f6d4c | ||
|
|
839933c933 |
@@ -1,12 +0,0 @@
|
|||||||
;;; Directory Local Variables -*- no-byte-compile: t; -*-
|
|
||||||
;;; For more information see (info "(emacs) Directory Variables")
|
|
||||||
|
|
||||||
;; Regorus is a cargo-verus project (package.metadata.verus.verify = true), so
|
|
||||||
;; verus-mode.el runs `cargo verus verify' rather than the raw `verus' binary.
|
|
||||||
;; The cargo-verus path ignores `package.metadata.verus.ide.extra_args' and
|
|
||||||
;; instead reads `verus-cargo-verus-arguments'. We set it here so that Verus is
|
|
||||||
;; invoked with the `verus' Cargo feature enabled.
|
|
||||||
;;
|
|
||||||
;; Everything before `--' is passed to cargo-verus; everything after `--' is
|
|
||||||
;; forwarded to the Verus binary. The `--' is required by verus-mode.el.
|
|
||||||
((verus-mode . ((verus-cargo-verus-arguments . ("--features" "verus" "--")))))
|
|
||||||
2
.github/copilot-setup-steps.yml
vendored
2
.github/copilot-setup-steps.yml
vendored
@@ -8,5 +8,3 @@ 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
|
|
||||||
|
|||||||
28
.github/skills/code-review/SKILL.md
vendored
28
.github/skills/code-review/SKILL.md
vendored
@@ -25,21 +25,15 @@ Key constraints (details in copilot-instructions.md):
|
|||||||
## Step 1: Get the Diff
|
## Step 1: Get the Diff
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Primary: use gh pr diff (works in cloud agent + any PR context).
|
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||||
# Fallback: git merge-base for local non-PR usage.
|
|| git merge-base origin/main HEAD 2>/dev/null)
|
||||||
if gh pr diff --name-only >/dev/null 2>&1; then
|
if [ -z "$BASE" ]; then
|
||||||
echo "---STAT---"
|
echo "ERROR: Cannot find upstream/main or origin/main. Cannot determine review scope."
|
||||||
gh pr diff --name-only
|
exit 1
|
||||||
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."
|
||||||
@@ -202,9 +196,3 @@ 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.
|
|
||||||
|
|||||||
59
.github/skills/deep-review/SKILL.md
vendored
59
.github/skills/deep-review/SKILL.md
vendored
@@ -40,25 +40,22 @@ 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
|
||||||
# Primary: use gh pr diff (works in cloud agent + any PR context).
|
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|
||||||
# Fallback: git merge-base for local non-PR usage.
|
|| git merge-base origin/main HEAD 2>/dev/null)
|
||||||
if gh pr diff --name-only >/dev/null 2>&1; then
|
if [ -z "$BASE" ]; then
|
||||||
echo "---STAT---"
|
echo "ERROR: Cannot find upstream/main or origin/main."
|
||||||
gh pr diff --name-only
|
exit 1
|
||||||
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:
|
||||||
@@ -109,10 +106,8 @@ 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 merge-base main HEAD 2>/dev/null)
|
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
||||||
> # If no merge-base, use: gh pr diff
|
|
||||||
> git diff "$BASE"..HEAD # or: gh pr diff
|
|
||||||
> ```
|
> ```
|
||||||
>
|
>
|
||||||
> Key regorus constraints:
|
> Key regorus constraints:
|
||||||
@@ -166,10 +161,8 @@ 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 merge-base main HEAD 2>/dev/null)
|
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
||||||
> # 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.
|
||||||
>
|
>
|
||||||
@@ -226,10 +219,8 @@ 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 merge-base main HEAD 2>/dev/null)
|
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
||||||
> # 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.
|
||||||
>
|
>
|
||||||
@@ -448,10 +439,8 @@ 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 merge-base main HEAD 2>/dev/null)
|
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
|
||||||
> # 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.
|
||||||
>
|
>
|
||||||
@@ -492,8 +481,8 @@ Launch **1 general-purpose agent in background mode**.
|
|||||||
|
|
||||||
## Step 5: Synthesize and Report
|
## Step 5: Synthesize and Report
|
||||||
|
|
||||||
**CRITICAL:** Write the report to `/tmp/deep-review-report.md` FIRST, then display it.
|
**IMPORTANT:** This is the primary output. Everything above was preparation.
|
||||||
Use a shell command to write the file before any other output in this step.
|
Keep the report COMPACT — one finding per block, no filler prose.
|
||||||
|
|
||||||
Apply verdicts from the adversarial verifier:
|
Apply verdicts from the adversarial verifier:
|
||||||
- **CONFIRMED**: keep at stated severity
|
- **CONFIRMED**: keep at stated severity
|
||||||
@@ -533,9 +522,3 @@ 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`
|
|
||||||
|
|||||||
18
.github/workflows/codeql.yml
vendored
18
.github/workflows/codeql.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
# Setup language-specific dependencies BEFORE CodeQL init for proper tracing setup
|
# Setup language-specific dependencies BEFORE CodeQL init for proper tracing setup
|
||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
@@ -86,26 +86,26 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
if: matrix.language == 'python'
|
if: matrix.language == 'python'
|
||||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
if: matrix.language == 'java-kotlin'
|
if: matrix.language == 'java-kotlin'
|
||||||
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
|
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||||
with:
|
with:
|
||||||
distribution: 'corretto'
|
distribution: 'corretto'
|
||||||
java-version: '8'
|
java-version: '8'
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: matrix.language == 'go'
|
if: matrix.language == 'go'
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.21'
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
if: matrix.language == 'csharp'
|
if: matrix.language == 'csharp'
|
||||||
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
|
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||||
with:
|
with:
|
||||||
global-json-file: ./bindings/csharp/global.json
|
global-json-file: ./bindings/csharp/global.json
|
||||||
|
|
||||||
@@ -115,12 +115,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
if: matrix.language == 'javascript-typescript'
|
if: matrix.language == 'javascript-typescript'
|
||||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '18'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
@@ -141,7 +141,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
if: matrix.language == 'rust' && contains(matrix.working-directory, 'ruby')
|
if: matrix.language == 'rust' && contains(matrix.working-directory, 'ruby')
|
||||||
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.4.2'
|
ruby-version: '3.4.2'
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
@@ -188,6 +188,6 @@ jobs:
|
|||||||
run: cargo xtask build-wasm --release
|
run: cargo xtask build-wasm --release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
# ONLY cargo update and cargo metadata (which do NOT execute build
|
# ONLY cargo update and cargo metadata (which do NOT execute build
|
||||||
# scripts) may run against this checkout. Do NOT add cargo build/check/
|
# scripts) may run against this checkout. Do NOT add cargo build/check/
|
||||||
# test/run steps.
|
# test/run steps.
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4.2.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
|
||||||
with:
|
with:
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|||||||
4
.github/workflows/dependency-audit.yml
vendored
4
.github/workflows/dependency-audit.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- bindings/wasm/Cargo.lock
|
- bindings/wasm/Cargo.lock
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Run cargo audit
|
- name: Run cargo audit
|
||||||
uses: rustsec/audit-check@v2
|
uses: rustsec/audit-check@v2
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
- xtask/Cargo.toml
|
- xtask/Cargo.toml
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Rust
|
- name: Setup Rust
|
||||||
uses: ./.github/actions/toolchains/rust
|
uses: ./.github/actions/toolchains/rust
|
||||||
|
|||||||
2
.github/workflows/feature-matrix.yml
vendored
2
.github/workflows/feature-matrix.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
|||||||
features: arc,opa-no-std
|
features: arc,opa-no-std
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: ./.github/actions/toolchains/rust
|
uses: ./.github/actions/toolchains/rust
|
||||||
- name: Cache cargo
|
- name: Cache cargo
|
||||||
|
|||||||
2
.github/workflows/miri.yml
vendored
2
.github/workflows/miri.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
MIRIFLAGS: "-Zmiri-disable-isolation"
|
MIRIFLAGS: "-Zmiri-disable-isolation"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v6
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
with:
|
with:
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
|
|||||||
2
.github/workflows/pr-extensions.yml
vendored
2
.github/workflows/pr-extensions.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: ./.github/actions/toolchains/rust
|
uses: ./.github/actions/toolchains/rust
|
||||||
- name: Cache cargo
|
- name: Cache cargo
|
||||||
|
|||||||
2
.github/workflows/pr.yml
vendored
2
.github/workflows/pr.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: ./.github/actions/toolchains/rust
|
uses: ./.github/actions/toolchains/rust
|
||||||
- name: Cache cargo
|
- name: Cache cargo
|
||||||
|
|||||||
10
.github/workflows/publish-java.yml
vendored
10
.github/workflows/publish-java.yml
vendored
@@ -35,10 +35,10 @@ jobs:
|
|||||||
os: windows-latest
|
os: windows-latest
|
||||||
extension: dll
|
extension: dll
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
|
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 8
|
||||||
distribution: "corretto"
|
distribution: "corretto"
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
||||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
||||||
@@ -66,10 +66,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
|
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 8
|
||||||
distribution: "corretto"
|
distribution: "corretto"
|
||||||
|
|||||||
12
.github/workflows/publish-python.yml
vendored
12
.github/workflows/publish-python.yml
vendored
@@ -20,8 +20,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
|
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
@@ -52,8 +52,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
target: [x64, x86]
|
target: [x64, x86]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
architecture: ${{ matrix.target }}
|
architecture: ${{ matrix.target }}
|
||||||
@@ -84,8 +84,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
target: [x86_64, aarch64, universal2-apple-darwin]
|
target: [x86_64, aarch64, universal2-apple-darwin]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
|
|||||||
4
.github/workflows/publish-wasm.yml
vendored
4
.github/workflows/publish-wasm.yml
vendored
@@ -15,11 +15,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
# Setup .npmrc file to publish to npm
|
# Setup .npmrc file to publish to npm
|
||||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|||||||
4
.github/workflows/release-plz.yml
vendored
4
.github/workflows/release-plz.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: ./.github/actions/toolchains/rust
|
uses: ./.github/actions/toolchains/rust
|
||||||
- name: Run release-plz
|
- name: Run release-plz
|
||||||
uses: MarcoIeni/release-plz-action@2eb1d8bcb770b4c48ccfaad919734b38b51958c9 # v0.5.131
|
uses: MarcoIeni/release-plz-action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|||||||
4
.github/workflows/rust-clippy.yml
vendored
4
.github/workflows/rust-clippy.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: ./.github/actions/toolchains/rust
|
uses: ./.github/actions/toolchains/rust
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload analysis results to GitHub
|
- name: Upload analysis results to GitHub
|
||||||
if: ${{ hashFiles('rust-clippy-results.sarif') != '' }}
|
if: ${{ hashFiles('rust-clippy-results.sarif') != '' }}
|
||||||
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v3.29.11
|
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.11
|
||||||
with:
|
with:
|
||||||
sarif_file: rust-clippy-results.sarif
|
sarif_file: rust-clippy-results.sarif
|
||||||
wait-for-processing: true
|
wait-for-processing: true
|
||||||
|
|||||||
2
.github/workflows/test-c-cpp.yml
vendored
2
.github/workflows/test-c-cpp.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
10
.github/workflows/test-csharp.yml
vendored
10
.github/workflows/test-csharp.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
**/release/libregorus_ffi.dylib
|
**/release/libregorus_ffi.dylib
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
@@ -73,11 +73,11 @@ jobs:
|
|||||||
needs: build-ffi
|
needs: build-ffi
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
|
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||||
with:
|
with:
|
||||||
global-json-file: ./bindings/csharp/global.json
|
global-json-file: ./bindings/csharp/global.json
|
||||||
|
|
||||||
@@ -131,13 +131,13 @@ jobs:
|
|||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
|
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||||
with:
|
with:
|
||||||
global-json-file: ./bindings/csharp/global.json
|
global-json-file: ./bindings/csharp/global.json
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/test-ffi.yml
vendored
2
.github/workflows/test-ffi.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
|
|||||||
4
.github/workflows/test-go.yml
vendored
4
.github/workflows/test-go.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Fetch FFI crate dependencies
|
- name: Fetch FFI crate dependencies
|
||||||
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
|
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
|
||||||
|
|
||||||
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
architecture: x64
|
architecture: x64
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/test-java.yml
vendored
4
.github/workflows/test-java.yml
vendored
@@ -16,11 +16,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
|
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 8
|
||||||
distribution: "corretto"
|
distribution: "corretto"
|
||||||
|
|||||||
2
.github/workflows/test-musl.yml
vendored
2
.github/workflows/test-musl.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
with:
|
with:
|
||||||
targets: x86_64-unknown-linux-musl
|
targets: x86_64-unknown-linux-musl
|
||||||
|
|||||||
2
.github/workflows/test-no-std.yml
vendored
2
.github/workflows/test-no-std.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
with:
|
with:
|
||||||
targets: thumbv7m-none-eabi
|
targets: thumbv7m-none-eabi
|
||||||
|
|||||||
8
.github/workflows/test-python.yml
vendored
8
.github/workflows/test-python.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.host.name }}
|
runs-on: ${{ matrix.host.name }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
- name: Fetch Python crate dependencies
|
- name: Fetch Python crate dependencies
|
||||||
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml --target ${{ matrix.host.target }}
|
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml --target ${{ matrix.host.target }}
|
||||||
|
|
||||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
architecture: x64
|
architecture: x64
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.host.name }}
|
runs-on: ${{ matrix.host.name }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: ./.github/actions/toolchains/rust
|
- uses: ./.github/actions/toolchains/rust
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
- name: Fetch Python crate dependencies
|
- name: Fetch Python crate dependencies
|
||||||
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml
|
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml
|
||||||
|
|
||||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
architecture: x64
|
architecture: x64
|
||||||
|
|||||||
2
.github/workflows/test-ruby.yml
vendored
2
.github/workflows/test-ruby.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/test-wasm.yml
vendored
4
.github/workflows/test-wasm.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
run: cargo fetch --locked --manifest-path bindings/wasm/Cargo.toml
|
run: cargo fetch --locked --manifest-path bindings/wasm/Cargo.toml
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/tests-debug.yml
vendored
2
.github/workflows/tests-debug.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: ./.github/actions/toolchains/rust
|
uses: ./.github/actions/toolchains/rust
|
||||||
- name: Cache cargo
|
- name: Cache cargo
|
||||||
|
|||||||
80
.github/workflows/verus.yml
vendored
80
.github/workflows/verus.yml
vendored
@@ -1,80 +0,0 @@
|
|||||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
#
|
|
||||||
name: verus
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
# This workflow only checks out code, downloads a pinned Verus release asset,
|
|
||||||
# and runs verification. It never writes to the repository, so restrict the
|
|
||||||
# GITHUB_TOKEN to read-only access to repository contents.
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
- name: Setup Rust toolchain
|
|
||||||
uses: ./.github/actions/toolchains/rust
|
|
||||||
with:
|
|
||||||
components: ""
|
|
||||||
- name: Cache cargo
|
|
||||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
|
||||||
with:
|
|
||||||
shared-key: ${{ runner.os }}-regorus-verus
|
|
||||||
- name: Install Verus and run verification
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euxo pipefail
|
|
||||||
asset_url=https://github.com/verus-lang/verus/releases/download/release%2F0.2026.07.12.0b42f4c/verus-0.2026.07.12.0b42f4c-x86-linux.zip
|
|
||||||
asset_sha256=f6f4f5d08e07d3e1ad721d775bda5ba96b9dd0c73b48fc17f2e071866fbd01c0
|
|
||||||
test -n "$asset_url"
|
|
||||||
curl -fsSL "$asset_url" -o verus.zip
|
|
||||||
|
|
||||||
# Verify the download integrity before trusting/executing its contents.
|
|
||||||
echo "${asset_sha256} verus.zip" | sha256sum --check --strict
|
|
||||||
|
|
||||||
unzip -q verus.zip -d verus-dist
|
|
||||||
|
|
||||||
# Search under an absolute path so that `find` yields absolute paths;
|
|
||||||
# this keeps the PATH entries below valid regardless of the working
|
|
||||||
# directory.
|
|
||||||
verus_bin="$(find "$PWD/verus-dist" -type f -name verus -perm -u+x | head -n1)"
|
|
||||||
cargo_verus_bin="$(find "$PWD/verus-dist" -type f -name cargo-verus -perm -u+x | head -n1)"
|
|
||||||
version_json="$(find "$PWD/verus-dist" -type f -name version.json | head -n1)"
|
|
||||||
test -n "$verus_bin"
|
|
||||||
test -n "$cargo_verus_bin"
|
|
||||||
test -n "$version_json"
|
|
||||||
|
|
||||||
# Verus is built against a specific Rust toolchain and refuses to run
|
|
||||||
# against any other version. Read the required toolchain from the
|
|
||||||
# release metadata so we track it automatically instead of hardcoding.
|
|
||||||
required_toolchain="$(sed -n 's/.*"toolchain"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$version_json")"
|
|
||||||
test -n "$required_toolchain"
|
|
||||||
echo "Verus requires Rust toolchain: $required_toolchain"
|
|
||||||
|
|
||||||
# Install the exact toolchain Verus expects, including the extra
|
|
||||||
# components (rustc-dev, llvm-tools) that Verus links against and that
|
|
||||||
# are not part of the default rustup profile.
|
|
||||||
rustup toolchain install "$required_toolchain" \
|
|
||||||
--profile minimal \
|
|
||||||
--component rustc-dev --component llvm-tools --component rustfmt
|
|
||||||
|
|
||||||
# Force cargo/rustc to resolve to the Verus toolchain for the commands
|
|
||||||
# below, overriding any repository/directory toolchain override.
|
|
||||||
export RUSTUP_TOOLCHAIN="$required_toolchain"
|
|
||||||
|
|
||||||
# Put cargo-verus on PATH for the commands below.
|
|
||||||
export PATH="$(dirname "$cargo_verus_bin"):$(dirname "$verus_bin"):$PATH"
|
|
||||||
cargo verus --help
|
|
||||||
cargo fetch --locked
|
|
||||||
cargo verus verify --locked --features verus
|
|
||||||
44
CHANGELOG.md
44
CHANGELOG.md
@@ -6,50 +6,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [0.11.0](https://github.com/microsoft/regorus/compare/regorus-v0.10.1...regorus-v0.11.0) - 2026-07-21
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- *(compiler)* support registered host-await builtins for natural function call syntax ([#667](https://github.com/microsoft/regorus/pull/667))
|
|
||||||
- *(value)* introduce Set storage abstraction ([#740](https://github.com/microsoft/regorus/pull/740))
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- *(rvm)* assert every-quantifier results so failing cases don't pass ([#765](https://github.com/microsoft/regorus/pull/765))
|
|
||||||
- `Engine::add_data` now deep-merges nested data documents instead of only merging top-level keys. Adding `{ "a": { "x": 1 } }` followed by `{ "a": { "y": 2 } }` now yields `{ "a": { "x": 1, "y": 2 } }` (matching OPA's data-document merge). Nested sets under a shared key are unioned. Only genuine leaf conflicts (the same path holding two different values) are reported as errors. ([#760](https://github.com/microsoft/regorus/pull/760))
|
|
||||||
- A zero-arg function producing two different complete values (e.g. `f() := { "a": 1 }` and `f() := { "b": 2 }`) is now reported as a conflict, matching OPA's complete-rule semantics, instead of silently combining the outputs.
|
|
||||||
|
|
||||||
### Security
|
|
||||||
|
|
||||||
- `Engine::add_data` now rejects data nested beyond 128 levels instead of risking a stack overflow on adversarially deep input.
|
|
||||||
|
|
||||||
### Other
|
|
||||||
|
|
||||||
- *(deps)* bump the rust-dependencies group across 5 directories with 11 updates ([#764](https://github.com/microsoft/regorus/pull/764))
|
|
||||||
- Expand keyword-in-ref coverage for complex parser edge cases (interpreter + RVM) ([#744](https://github.com/microsoft/regorus/pull/744))
|
|
||||||
- *(deps)* bump the rust-dependencies group across 5 directories with 4 updates ([#754](https://github.com/microsoft/regorus/pull/754))
|
|
||||||
- *(deps)* bump the rust-dependencies group across 5 directories with 6 updates ([#750](https://github.com/microsoft/regorus/pull/750))
|
|
||||||
- *(value)* migrate Value::Object to Object storage abstraction ([#736](https://github.com/microsoft/regorus/pull/736))
|
|
||||||
- normalize path separators in folder filter on Windows ([#742](https://github.com/microsoft/regorus/pull/742))
|
|
||||||
- Introduce Object storage abstraction ([#735](https://github.com/microsoft/regorus/pull/735))
|
|
||||||
- *(rvm)* add debug-mode invariant assertions ([#737](https://github.com/microsoft/regorus/pull/737))
|
|
||||||
- *(deps)* bump the rust-dependencies group across 5 directories with 5 updates ([#734](https://github.com/microsoft/regorus/pull/734))
|
|
||||||
|
|
||||||
## [0.10.1](https://github.com/microsoft/regorus/compare/regorus-v0.10.0...regorus-v0.10.1) - 2026-05-22
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- *(ffi)* eliminate aliasing UB + add Azure Policy JSON compilation FFI ([#727](https://github.com/microsoft/regorus/pull/727))
|
|
||||||
- *(interpreter,rvm)* correct partial object rule iteration and classification ([#718](https://github.com/microsoft/regorus/pull/718))
|
|
||||||
- *(copilot)* robust diff computation for cloud agent environments ([#709](https://github.com/microsoft/regorus/pull/709))
|
|
||||||
|
|
||||||
### Other
|
|
||||||
|
|
||||||
- *(azure_policy)* reduce AliasRegistry allocations via Rc sharing ([#725](https://github.com/microsoft/regorus/pull/725))
|
|
||||||
- *(normalizer)* use Rc<str> interning to reduce alias resolution allocations ([#726](https://github.com/microsoft/regorus/pull/726))
|
|
||||||
- *(deps)* bump the rust-dependencies group across 5 directories with 2 updates ([#724](https://github.com/microsoft/regorus/pull/724))
|
|
||||||
- *(deps)* bump the rust-dependencies group across 5 directories with 4 updates ([#717](https://github.com/microsoft/regorus/pull/717))
|
|
||||||
|
|
||||||
## [0.10.0] - 2026-05-05
|
## [0.10.0] - 2026-05-05
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
749
Cargo.lock
generated
749
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
29
Cargo.toml
29
Cargo.toml
@@ -8,17 +8,12 @@ members = [
|
|||||||
[package]
|
[package]
|
||||||
name = "regorus"
|
name = "regorus"
|
||||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||||
version = "0.11.0"
|
version = "0.10.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
||||||
repository = "https://github.com/microsoft/regorus"
|
repository = "https://github.com/microsoft/regorus"
|
||||||
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
|
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
|
||||||
|
|
||||||
# Support verification with Verus, a Rust verifier (https://github.com/verus-lang/verus)
|
|
||||||
|
|
||||||
[package.metadata.verus]
|
|
||||||
verify = true
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -26,7 +21,6 @@ doctest = false
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["full-opa", "arc", "rvm"]
|
default = ["full-opa", "arc", "rvm"]
|
||||||
verus = ["dep:vstd"]
|
|
||||||
|
|
||||||
arc = []
|
arc = []
|
||||||
ast = []
|
ast = []
|
||||||
@@ -49,7 +43,7 @@ cache = ["dep:lru"]
|
|||||||
rvm = ["dep:postcard", "dep:indexmap"]
|
rvm = ["dep:postcard", "dep:indexmap"]
|
||||||
semver = ["dep:semver"]
|
semver = ["dep:semver"]
|
||||||
allocator-memory-limits = ["std", "mimalloc", "mimalloc/allocator-memory-limits"]
|
allocator-memory-limits = ["std", "mimalloc", "mimalloc/allocator-memory-limits"]
|
||||||
std = ["rand/std", "rand/std_rng", "serde_json/std", "indexmap?/std", "msvc_spectre_libs", "dep:parking_lot", "vstd?/std" ]
|
std = ["rand/std", "rand/std_rng", "serde_json/std", "indexmap?/std", "msvc_spectre_libs", "dep:parking_lot" ]
|
||||||
time = ["dep:chrono", "dep:chrono-tz"]
|
time = ["dep:chrono", "dep:chrono-tz"]
|
||||||
uuid = ["dep:uuid"]
|
uuid = ["dep:uuid"]
|
||||||
urlquery = ["dep:url"]
|
urlquery = ["dep:url"]
|
||||||
@@ -104,23 +98,23 @@ rand = ["dep:rand"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.102", default-features = false }
|
anyhow = { version = "1.0.102", default-features = false }
|
||||||
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
|
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
|
||||||
serde_json = { version = "1.0.150", default-features = false, features = ["alloc"] }
|
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
|
||||||
hashbrown = { version = "0.17", default-features = false, features = ["default-hasher"], optional = true }
|
hashbrown = { version = "0.17", default-features = false, features = ["default-hasher"], optional = true }
|
||||||
lazy_static = { version = "1.4.0", default-features = false }
|
lazy_static = { version = "1.4.0", default-features = false }
|
||||||
thiserror = { version = "2.0", default-features = false }
|
thiserror = { version = "2.0", default-features = false }
|
||||||
|
|
||||||
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
|
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
|
||||||
num-bigint = { version = "0.5", default-features = false }
|
num-bigint = { version = "0.4", default-features = false }
|
||||||
num-traits = { version = "0.2", default-features = false }
|
num-traits = { version = "0.2", default-features = false }
|
||||||
parking_lot = { version = "0.12", optional = true }
|
parking_lot = { version = "0.12", optional = true }
|
||||||
spin = { version = "0.12.0", default-features = false, features = ["mutex", "spin_mutex"] }
|
spin = { version = "0.10.0", default-features = false, features = ["mutex", "spin_mutex"] }
|
||||||
|
|
||||||
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
|
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
|
||||||
regex = {version = "1.12.3", optional = true, default-features = false }
|
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.48.5", default-features = false, optional = true }
|
jsonschema = { version = "0.46.4", 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 }
|
||||||
@@ -134,17 +128,12 @@ rand = { version = "0.10.0", default-features = false, features = ["thread_rng"]
|
|||||||
msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true }
|
msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true }
|
||||||
dashmap = { version = "6.1", default-features = false, optional = true }
|
dashmap = { version = "6.1", default-features = false, optional = true }
|
||||||
lru = { version = "0.18", default-features = false, optional = true }
|
lru = { version = "0.18", default-features = false, optional = true }
|
||||||
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.7", optional = true }
|
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.6", optional = true }
|
||||||
|
|
||||||
# rvm related deps
|
# rvm related deps
|
||||||
indexmap = { version = "2.13.1", default-features = false, features = ["serde"], optional = true }
|
indexmap = { version = "2.13.1", default-features = false, features = ["serde"], optional = true }
|
||||||
postcard = { version = "1.1.3", default-features = false, features = ["alloc"], optional = true }
|
postcard = { version = "1.1.3", default-features = false, features = ["alloc"], optional = true }
|
||||||
|
|
||||||
# Verus-related dependencies.
|
|
||||||
# vstd is enabled via the `verus` feature. In no_std builds only the `alloc` feature is used;
|
|
||||||
# the crate's `std` feature additionally enables `vstd/std` (matching vstd's default features).
|
|
||||||
vstd = { version = "=0.0.0-2026-07-12-0122", optional = true, default-features = false, features = ["alloc"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0.102"
|
anyhow = "1.0.102"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
@@ -225,7 +214,3 @@ doctest=false
|
|||||||
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps
|
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[lints.rust]
|
|
||||||
# Allow `verus_keep_ghost` configuration flag (used by Verus)
|
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(verus_keep_ghost)'] }
|
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ namespace regorus {
|
|||||||
return std::unique_ptr<Engine>(new Engine(regorus_engine_clone(engine)));
|
return std::unique_ptr<Engine>(new Engine(regorus_engine_clone(engine)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result prepare() {
|
||||||
|
return Result(regorus_engine_prepare(engine));
|
||||||
|
}
|
||||||
|
|
||||||
Result set_rego_v0(bool enable) {
|
Result set_rego_v0(bool enable) {
|
||||||
return Result(regorus_engine_set_rego_v0(engine, enable));
|
return Result(regorus_engine_set_rego_v0(engine, enable));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
<RegorusPackageVersion>0.11.0</RegorusPackageVersion>
|
<RegorusPackageVersion>0.10.0</RegorusPackageVersion>
|
||||||
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
|
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -150,76 +150,3 @@ const string ContextJson = """
|
|||||||
var allowed = RbacEngine.EvaluateCondition(Condition, ContextJson);
|
var allowed = RbacEngine.EvaluateCondition(Condition, ContextJson);
|
||||||
Console.WriteLine($"RBAC condition allowed: {allowed}");
|
Console.WriteLine($"RBAC condition allowed: {allowed}");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Azure Policy JSON Evaluation
|
|
||||||
|
|
||||||
Compile and evaluate Azure Policy JSON `policyRule` definitions directly — no Rego translation required.
|
|
||||||
The `AzurePolicyCompiler` compiles JSON policy rules into RVM programs that can be executed with the `Rvm` engine.
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using Regorus;
|
|
||||||
|
|
||||||
// 1. Load alias definitions for the resource provider
|
|
||||||
const string AliasesJson = """
|
|
||||||
[{
|
|
||||||
"namespace": "Microsoft.Storage",
|
|
||||||
"resourceTypes": [{
|
|
||||||
"resourceType": "storageAccounts",
|
|
||||||
"aliases": [{
|
|
||||||
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
|
||||||
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
|
||||||
"paths": []
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
""";
|
|
||||||
|
|
||||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
|
||||||
|
|
||||||
// 2. Compile a JSON policy rule (the native Azure Policy language)
|
|
||||||
const string PolicyRule = """
|
|
||||||
{
|
|
||||||
"if": {
|
|
||||||
"allOf": [
|
|
||||||
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
|
|
||||||
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"then": { "effect": "deny" }
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, PolicyRule);
|
|
||||||
|
|
||||||
// 3. Normalize an ARM resource and evaluate
|
|
||||||
var armResource = """
|
|
||||||
{
|
|
||||||
"type": "Microsoft.Storage/storageAccounts",
|
|
||||||
"name": "mystorage",
|
|
||||||
"properties": { "supportsHttpsTrafficOnly": false }
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
var envelope = registry.NormalizeAndWrap(armResource);
|
|
||||||
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
vm.SetInputJson(envelope!);
|
|
||||||
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
// result: {"effect": "deny"} for non-compliant, "<undefined>" for compliant
|
|
||||||
Console.WriteLine($"Policy result: {result}");
|
|
||||||
```
|
|
||||||
|
|
||||||
**Context-dependent policies:** If your policy uses context functions like
|
|
||||||
`subscription()`, `resourceGroup()`, or `requestContext()`, you must also set
|
|
||||||
the VM context separately:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// The context JSON from NormalizeAndWrap is in the input envelope,
|
|
||||||
// but must also be provided to the VM's ambient context:
|
|
||||||
vm.SetContextJson(contextJson);
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also compile full policy definitions (with parameters) using
|
|
||||||
`AzurePolicyCompiler.CompilePolicyDefinition()`. See
|
|
||||||
`bindings/csharp/Regorus.Tests/AzurePolicyCompilerTests.cs` for comprehensive examples.
|
|
||||||
|
|||||||
@@ -43,28 +43,31 @@ public class AliasRegistryTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Create_and_dispose_succeeds()
|
public void Create_and_dispose_succeeds()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.Empty();
|
using var registry = new AliasRegistry();
|
||||||
Assert.AreEqual(0, registry.Length);
|
Assert.AreEqual(0, registry.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void LoadJson_populates_registry()
|
public void LoadJson_populates_registry()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
Assert.AreEqual(1, registry.Length);
|
Assert.AreEqual(1, registry.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void LoadManifest_populates_registry()
|
public void LoadManifest_populates_registry()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromManifest(ManifestJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadManifest(ManifestJson);
|
||||||
Assert.AreEqual(1, registry.Length);
|
Assert.AreEqual(1, registry.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void NormalizeAndWrap_produces_envelope()
|
public void NormalizeAndWrap_produces_envelope()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
|
|
||||||
var resource = @"{
|
var resource = @"{
|
||||||
""name"": ""acct1"",
|
""name"": ""acct1"",
|
||||||
@@ -90,7 +93,8 @@ public class AliasRegistryTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void NormalizeAndWrap_with_context_and_parameters()
|
public void NormalizeAndWrap_with_context_and_parameters()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
|
|
||||||
var resource = @"{
|
var resource = @"{
|
||||||
""name"": ""acct1"",
|
""name"": ""acct1"",
|
||||||
@@ -111,7 +115,8 @@ public class AliasRegistryTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Denormalize_restores_properties()
|
public void Denormalize_restores_properties()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
|
|
||||||
var normalized = @"{
|
var normalized = @"{
|
||||||
""name"": ""acct1"",
|
""name"": ""acct1"",
|
||||||
@@ -132,7 +137,8 @@ public class AliasRegistryTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Round_trip_normalize_then_denormalize()
|
public void Round_trip_normalize_then_denormalize()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(AliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(AliasesJson);
|
||||||
|
|
||||||
var resource = @"{
|
var resource = @"{
|
||||||
""name"": ""acct1"",
|
""name"": ""acct1"",
|
||||||
@@ -160,7 +166,8 @@ public class AliasRegistryTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void DataPlane_manifest_normalize()
|
public void DataPlane_manifest_normalize()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromManifest(ManifestJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadManifest(ManifestJson);
|
||||||
|
|
||||||
var resource = @"{
|
var resource = @"{
|
||||||
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
|
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
|
||||||
@@ -178,7 +185,7 @@ public class AliasRegistryTests
|
|||||||
[ExpectedException(typeof(InvalidOperationException))]
|
[ExpectedException(typeof(InvalidOperationException))]
|
||||||
public void LoadJson_invalid_throws()
|
public void LoadJson_invalid_throws()
|
||||||
{
|
{
|
||||||
using var builder = new AliasRegistryBuilder();
|
using var registry = new AliasRegistry();
|
||||||
builder.LoadJson("not valid json");
|
registry.LoadJson("not valid json");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,436 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
|
||||||
// Licensed under the MIT License.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using Regorus;
|
|
||||||
|
|
||||||
namespace Regorus.Tests;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tests for <see cref="AzurePolicyCompiler"/> — compiling Azure Policy JSON
|
|
||||||
/// policyRule and policyDefinition into RVM programs and evaluating them.
|
|
||||||
/// </summary>
|
|
||||||
[TestClass]
|
|
||||||
public class AzurePolicyCompilerTests
|
|
||||||
{
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Test data
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
private const string StorageAliasesJson = @"[{
|
|
||||||
""namespace"": ""Microsoft.Storage"",
|
|
||||||
""resourceTypes"": [{
|
|
||||||
""resourceType"": ""storageAccounts"",
|
|
||||||
""capabilities"": ""SupportsTags, SupportsLocation"",
|
|
||||||
""aliases"": [
|
|
||||||
{
|
|
||||||
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
|
|
||||||
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
|
|
||||||
""paths"": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
""name"": ""Microsoft.Storage/storageAccounts/minimumTlsVersion"",
|
|
||||||
""defaultPath"": ""properties.minimumTlsVersion"",
|
|
||||||
""paths"": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}]";
|
|
||||||
|
|
||||||
/// <summary>Simple policy rule that checks the resource type.</summary>
|
|
||||||
private const string SimpleAuditRule = @"{
|
|
||||||
""if"": {
|
|
||||||
""field"": ""type"",
|
|
||||||
""equals"": ""Microsoft.Storage/storageAccounts""
|
|
||||||
},
|
|
||||||
""then"": { ""effect"": ""audit"" }
|
|
||||||
}";
|
|
||||||
|
|
||||||
/// <summary>Policy rule that uses an alias to check HTTPS-only.</summary>
|
|
||||||
private const string HttpsDenyRule = @"{
|
|
||||||
""if"": {
|
|
||||||
""allOf"": [
|
|
||||||
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
|
|
||||||
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
""then"": { ""effect"": ""deny"" }
|
|
||||||
}";
|
|
||||||
|
|
||||||
/// <summary>Full policy definition with parameters.</summary>
|
|
||||||
private const string PolicyDefinitionWithParams = @"{
|
|
||||||
""displayName"": ""Require HTTPS for storage accounts"",
|
|
||||||
""policyType"": ""Custom"",
|
|
||||||
""mode"": ""Indexed"",
|
|
||||||
""parameters"": {
|
|
||||||
""effect"": {
|
|
||||||
""type"": ""String"",
|
|
||||||
""defaultValue"": ""deny""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
""policyRule"": {
|
|
||||||
""if"": {
|
|
||||||
""allOf"": [
|
|
||||||
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
|
|
||||||
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
""then"": { ""effect"": ""[parameters('effect')]"" }
|
|
||||||
}
|
|
||||||
}";
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Helper
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wrap a normalized resource JSON and parameters into the input envelope
|
|
||||||
/// expected by compiled Azure Policy RVM programs.
|
|
||||||
/// </summary>
|
|
||||||
private static string WrapInput(string resourceJson, string parametersJson = "{}")
|
|
||||||
{
|
|
||||||
return $@"{{""resource"": {resourceJson}, ""parameters"": {parametersJson}}}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compile a policy rule, load it into an RVM, set input, and execute.
|
|
||||||
/// Returns the result string from <c>ExecuteEntryPoint("main")</c>.
|
|
||||||
/// </summary>
|
|
||||||
private static string? CompileAndEval(
|
|
||||||
AliasRegistry? registry,
|
|
||||||
string policyRuleJson,
|
|
||||||
string inputJson)
|
|
||||||
{
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, policyRuleJson);
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
vm.SetInputJson(inputJson);
|
|
||||||
return vm.ExecuteEntryPoint("main");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// CompilePolicyRule tests
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void CompilePolicyRule_no_aliases_succeeds()
|
|
||||||
{
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
|
|
||||||
Assert.IsNotNull(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void CompilePolicyRule_with_aliases_succeeds()
|
|
||||||
{
|
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
||||||
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
|
||||||
Assert.IsNotNull(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(ArgumentNullException))]
|
|
||||||
public void CompilePolicyRule_null_json_throws()
|
|
||||||
{
|
|
||||||
AzurePolicyCompiler.CompilePolicyRule(null, null!);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(InvalidOperationException))]
|
|
||||||
public void CompilePolicyRule_invalid_json_throws()
|
|
||||||
{
|
|
||||||
AzurePolicyCompiler.CompilePolicyRule(null, "not valid json");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// CompilePolicyDefinition tests
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void CompilePolicyDefinition_no_aliases_succeeds()
|
|
||||||
{
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyDefinition(null, PolicyDefinitionWithParams);
|
|
||||||
Assert.IsNotNull(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void CompilePolicyDefinition_with_aliases_succeeds()
|
|
||||||
{
|
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
||||||
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyDefinition(registry, PolicyDefinitionWithParams);
|
|
||||||
Assert.IsNotNull(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(ArgumentNullException))]
|
|
||||||
public void CompilePolicyDefinition_null_json_throws()
|
|
||||||
{
|
|
||||||
AzurePolicyCompiler.CompilePolicyDefinition(null, null!);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(InvalidOperationException))]
|
|
||||||
public void CompilePolicyDefinition_invalid_json_throws()
|
|
||||||
{
|
|
||||||
AzurePolicyCompiler.CompilePolicyDefinition(null, @"{""not"": ""a definition""}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// End-to-end evaluation tests
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Eval_simple_rule_matching_resource_returns_effect()
|
|
||||||
{
|
|
||||||
var input = WrapInput(
|
|
||||||
@"{""type"": ""microsoft.storage/storageaccounts""}");
|
|
||||||
|
|
||||||
var result = CompileAndEval(null, SimpleAuditRule, input);
|
|
||||||
Assert.IsNotNull(result, "expected a result for matching resource");
|
|
||||||
|
|
||||||
var doc = JsonNode.Parse(result!)!;
|
|
||||||
Assert.AreEqual("audit", doc["effect"]?.GetValue<string>(),
|
|
||||||
$"expected 'audit' effect, got: {result}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Eval_simple_rule_non_matching_resource_returns_undefined()
|
|
||||||
{
|
|
||||||
var input = WrapInput(
|
|
||||||
@"{""type"": ""microsoft.compute/virtualmachines""}");
|
|
||||||
|
|
||||||
var result = CompileAndEval(null, SimpleAuditRule, input);
|
|
||||||
Assert.IsNotNull(result);
|
|
||||||
StringAssert.Contains(result!, "undefined",
|
|
||||||
"expected undefined for non-matching resource type");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Eval_alias_rule_non_compliant_returns_deny()
|
|
||||||
{
|
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
||||||
|
|
||||||
// Non-compliant: HTTPS not enabled (normalized/lowercased form)
|
|
||||||
var input = WrapInput(
|
|
||||||
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": false}");
|
|
||||||
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
vm.SetInputJson(input);
|
|
||||||
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
Assert.IsNotNull(result);
|
|
||||||
|
|
||||||
var doc = JsonNode.Parse(result!)!;
|
|
||||||
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
|
|
||||||
$"expected 'deny' for non-compliant resource, got: {result}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Eval_alias_rule_compliant_returns_undefined()
|
|
||||||
{
|
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
||||||
|
|
||||||
// Compliant: HTTPS enabled
|
|
||||||
var input = WrapInput(
|
|
||||||
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": true}");
|
|
||||||
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
vm.SetInputJson(input);
|
|
||||||
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
Assert.IsNotNull(result);
|
|
||||||
StringAssert.Contains(result!, "undefined",
|
|
||||||
"expected undefined for compliant resource");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Eval_definition_with_default_parameters()
|
|
||||||
{
|
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
||||||
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyDefinition(
|
|
||||||
registry, PolicyDefinitionWithParams);
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
|
|
||||||
// Non-compliant resource
|
|
||||||
var input = WrapInput(
|
|
||||||
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": false}");
|
|
||||||
vm.SetInputJson(input);
|
|
||||||
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
Assert.IsNotNull(result);
|
|
||||||
|
|
||||||
var doc = JsonNode.Parse(result!)!;
|
|
||||||
// Default parameter value is "deny"
|
|
||||||
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
|
|
||||||
$"expected default 'deny' effect, got: {result}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Eval_with_normalized_arm_resource_end_to_end()
|
|
||||||
{
|
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
||||||
|
|
||||||
// Simulate the full production flow:
|
|
||||||
// 1. Start with an ARM resource
|
|
||||||
var armResource = @"{
|
|
||||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
|
||||||
""name"": ""mystorage"",
|
|
||||||
""location"": ""eastus"",
|
|
||||||
""properties"": {
|
|
||||||
""supportsHttpsTrafficOnly"": false,
|
|
||||||
""minimumTlsVersion"": ""TLS1_0""
|
|
||||||
}
|
|
||||||
}";
|
|
||||||
|
|
||||||
// 2. Normalize via AliasRegistry
|
|
||||||
var normalizedEnvelope = registry.NormalizeAndWrap(
|
|
||||||
armResource,
|
|
||||||
apiVersion: null,
|
|
||||||
contextJson: "{}",
|
|
||||||
parametersJson: "{}");
|
|
||||||
Assert.IsNotNull(normalizedEnvelope);
|
|
||||||
|
|
||||||
// 3. Compile the policy rule
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
|
||||||
|
|
||||||
// 4. Execute
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
vm.SetInputJson(normalizedEnvelope!);
|
|
||||||
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
Assert.IsNotNull(result);
|
|
||||||
|
|
||||||
var doc = JsonNode.Parse(result!)!;
|
|
||||||
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
|
|
||||||
$"expected 'deny' for non-HTTPS storage account, got: {result}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Eval_normalized_compliant_resource_end_to_end()
|
|
||||||
{
|
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
|
||||||
|
|
||||||
var armResource = @"{
|
|
||||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
|
||||||
""name"": ""secureastorage"",
|
|
||||||
""location"": ""westus"",
|
|
||||||
""properties"": {
|
|
||||||
""supportsHttpsTrafficOnly"": true,
|
|
||||||
""minimumTlsVersion"": ""TLS1_2""
|
|
||||||
}
|
|
||||||
}";
|
|
||||||
|
|
||||||
var normalizedEnvelope = registry.NormalizeAndWrap(
|
|
||||||
armResource,
|
|
||||||
apiVersion: null,
|
|
||||||
contextJson: "{}",
|
|
||||||
parametersJson: "{}");
|
|
||||||
Assert.IsNotNull(normalizedEnvelope);
|
|
||||||
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
vm.SetInputJson(normalizedEnvelope!);
|
|
||||||
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
Assert.IsNotNull(result);
|
|
||||||
StringAssert.Contains(result!, "undefined",
|
|
||||||
"expected undefined for compliant HTTPS storage account");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Program_can_be_serialized_and_reloaded()
|
|
||||||
{
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
|
|
||||||
|
|
||||||
// Serialize to binary
|
|
||||||
var binary = program.SerializeBinary();
|
|
||||||
Assert.IsTrue(binary.Length > 0, "serialized program should not be empty");
|
|
||||||
|
|
||||||
// Deserialize and run
|
|
||||||
using var restored = Program.DeserializeBinary(binary, out var isPartial);
|
|
||||||
Assert.IsFalse(isPartial, "program should not be partial");
|
|
||||||
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(restored);
|
|
||||||
var input = WrapInput(@"{""type"": ""microsoft.storage/storageaccounts""}");
|
|
||||||
vm.SetInputJson(input);
|
|
||||||
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
Assert.IsNotNull(result);
|
|
||||||
var doc = JsonNode.Parse(result!)!;
|
|
||||||
Assert.AreEqual("audit", doc["effect"]?.GetValue<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Program_generates_listing()
|
|
||||||
{
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
|
|
||||||
var listing = program.GenerateListing();
|
|
||||||
Assert.IsFalse(string.IsNullOrWhiteSpace(listing),
|
|
||||||
"generated listing should not be empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Context-dependent policy tests
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Policy rule that uses subscription() context function.
|
|
||||||
private const string ContextPolicyRule = @"{
|
|
||||||
""if"": {
|
|
||||||
""allOf"": [
|
|
||||||
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
|
|
||||||
{ ""value"": ""[subscription().subscriptionId]"", ""equals"": ""sub-123"" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
""then"": { ""effect"": ""deny"" }
|
|
||||||
}";
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Eval_context_policy_with_set_context_returns_effect()
|
|
||||||
{
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, ContextPolicyRule);
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
|
|
||||||
vm.SetContextJson(@"{""subscription"": {""subscriptionId"": ""sub-123""}}");
|
|
||||||
|
|
||||||
var input = WrapInput(
|
|
||||||
@"{""type"": ""microsoft.storage/storageaccounts""}");
|
|
||||||
vm.SetInputJson(input);
|
|
||||||
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
Assert.IsNotNull(result);
|
|
||||||
var doc = JsonNode.Parse(result!)!;
|
|
||||||
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
|
|
||||||
$"expected 'deny' with matching context, got: {result}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Eval_context_policy_without_context_returns_undefined()
|
|
||||||
{
|
|
||||||
using var program = AzurePolicyCompiler.CompilePolicyRule(null, ContextPolicyRule);
|
|
||||||
using var vm = new Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
|
|
||||||
// No context set — subscription() will be undefined
|
|
||||||
var input = WrapInput(
|
|
||||||
@"{""type"": ""microsoft.storage/storageaccounts""}");
|
|
||||||
vm.SetInputJson(input);
|
|
||||||
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
Assert.IsNotNull(result);
|
|
||||||
StringAssert.Contains(result!, "undefined",
|
|
||||||
"expected undefined without context set");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -62,7 +62,8 @@ public class AzurePolicyTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void AliasRegistry_NormalizeAndWrap_produces_input_envelope()
|
public void AliasRegistry_NormalizeAndWrap_produces_input_envelope()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(StorageAliasesJson);
|
||||||
|
|
||||||
var result = registry.NormalizeAndWrap(
|
var result = registry.NormalizeAndWrap(
|
||||||
StorageResourceJson,
|
StorageResourceJson,
|
||||||
@@ -83,7 +84,8 @@ public class AzurePolicyTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void AliasRegistry_NormalizeAndWrap_flattens_properties()
|
public void AliasRegistry_NormalizeAndWrap_flattens_properties()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(StorageAliasesJson);
|
||||||
|
|
||||||
var result = registry.NormalizeAndWrap(StorageResourceJson);
|
var result = registry.NormalizeAndWrap(StorageResourceJson);
|
||||||
Assert.IsNotNull(result);
|
Assert.IsNotNull(result);
|
||||||
@@ -105,7 +107,8 @@ public class AzurePolicyTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void AliasRegistry_NormalizeAndWrap_preserves_type_field()
|
public void AliasRegistry_NormalizeAndWrap_preserves_type_field()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(StorageAliasesJson);
|
||||||
|
|
||||||
var result = registry.NormalizeAndWrap(StorageResourceJson);
|
var result = registry.NormalizeAndWrap(StorageResourceJson);
|
||||||
var doc = JsonNode.Parse(result!);
|
var doc = JsonNode.Parse(result!);
|
||||||
@@ -122,7 +125,8 @@ public class AzurePolicyTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void AliasRegistry_NormalizeAndWrap_includes_parameters()
|
public void AliasRegistry_NormalizeAndWrap_includes_parameters()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(StorageAliasesJson);
|
||||||
|
|
||||||
var parametersJson = @"{ ""effect"": ""Deny"" }";
|
var parametersJson = @"{ ""effect"": ""Deny"" }";
|
||||||
var result = registry.NormalizeAndWrap(
|
var result = registry.NormalizeAndWrap(
|
||||||
@@ -139,7 +143,8 @@ public class AzurePolicyTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void AliasRegistry_Denormalize_roundtrips_correctly()
|
public void AliasRegistry_Denormalize_roundtrips_correctly()
|
||||||
{
|
{
|
||||||
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(StorageAliasesJson);
|
||||||
|
|
||||||
// Normalize the ARM resource.
|
// Normalize the ARM resource.
|
||||||
var envelope = registry.NormalizeAndWrap(StorageResourceJson);
|
var envelope = registry.NormalizeAndWrap(StorageResourceJson);
|
||||||
@@ -172,7 +177,8 @@ public class AzurePolicyTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
var aliasesJson = File.ReadAllText(aliasesPath);
|
var aliasesJson = File.ReadAllText(aliasesPath);
|
||||||
using var registry = AliasRegistry.FromJson(aliasesJson);
|
using var registry = new AliasRegistry();
|
||||||
|
registry.LoadJson(aliasesJson);
|
||||||
|
|
||||||
// The test_aliases.json file contains multiple providers.
|
// The test_aliases.json file contains multiple providers.
|
||||||
Assert.IsTrue(registry.Length > 0,
|
Assert.IsTrue(registry.Length > 0,
|
||||||
|
|||||||
@@ -115,10 +115,6 @@ public class MemoryGrowthTests
|
|||||||
|
|
||||||
if (i % LogEvery == 0)
|
if (i % LogEvery == 0)
|
||||||
{
|
{
|
||||||
// Collect transient managed garbage so the working-set delta reflects
|
|
||||||
// retained (leaked) memory rather than uncollected allocations. A real
|
|
||||||
// native leak from a missed Dispose() would survive GC and still be caught.
|
|
||||||
ForceFullGc();
|
|
||||||
process.Refresh();
|
process.Refresh();
|
||||||
var workingSet = process.WorkingSet64;
|
var workingSet = process.WorkingSet64;
|
||||||
var managed = GC.GetTotalMemory(false);
|
var managed = GC.GetTotalMemory(false);
|
||||||
@@ -232,10 +228,6 @@ public class MemoryGrowthTests
|
|||||||
|
|
||||||
if (i % LogEvery == 0)
|
if (i % LogEvery == 0)
|
||||||
{
|
{
|
||||||
// Collect transient managed garbage so the working-set delta reflects
|
|
||||||
// retained (leaked) memory rather than uncollected allocations. A real
|
|
||||||
// native leak from a missed Dispose() would survive GC and still be caught.
|
|
||||||
ForceFullGc();
|
|
||||||
process.Refresh();
|
process.Refresh();
|
||||||
var workingSet = process.WorkingSet64;
|
var workingSet = process.WorkingSet64;
|
||||||
var managed = GC.GetTotalMemory(false);
|
var managed = GC.GetTotalMemory(false);
|
||||||
|
|||||||
@@ -8,43 +8,51 @@ using Regorus.Internal;
|
|||||||
namespace Regorus
|
namespace Regorus
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Immutable Azure Policy alias registry used for resource normalization
|
/// Manages Azure Policy alias definitions used for resource normalization
|
||||||
/// and policy compilation.
|
/// and policy compilation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe sealed class AliasRegistry : SafeHandleWrapper
|
public unsafe sealed class AliasRegistry : SafeHandleWrapper
|
||||||
{
|
{
|
||||||
internal AliasRegistry(RegorusAliasRegistryHandle handle)
|
/// <summary>
|
||||||
: base(handle, nameof(AliasRegistry))
|
/// Create an empty alias registry.
|
||||||
|
/// </summary>
|
||||||
|
public AliasRegistry()
|
||||||
|
: base(RegorusAliasRegistryHandle.Create(), nameof(AliasRegistry))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create an empty immutable alias registry.
|
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static AliasRegistry Empty()
|
/// <param name="json">JSON array of ProviderAliases (e.g. from Get-AzPolicyAlias or ResourceTypesAndAliases.json)</param>
|
||||||
|
public void LoadJson(string json)
|
||||||
{
|
{
|
||||||
using var builder = new AliasRegistryBuilder();
|
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||||
return builder.Build();
|
{
|
||||||
|
UseHandle(regPtr =>
|
||||||
|
{
|
||||||
|
CheckAndDropResult(API.regorus_alias_registry_load_json(
|
||||||
|
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create an immutable alias registry from control-plane alias JSON.
|
/// Load a data-plane policy manifest from a JSON string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static AliasRegistry FromJson(string json)
|
/// <param name="json">JSON object containing a DataPolicyManifest</param>
|
||||||
|
public void LoadManifest(string json)
|
||||||
{
|
{
|
||||||
using var builder = new AliasRegistryBuilder();
|
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
||||||
builder.LoadJson(json);
|
{
|
||||||
return builder.Build();
|
UseHandle(regPtr =>
|
||||||
}
|
{
|
||||||
|
CheckAndDropResult(API.regorus_alias_registry_load_manifest(
|
||||||
/// <summary>
|
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
|
||||||
/// Create an immutable alias registry from a data-plane manifest JSON document.
|
return 0;
|
||||||
/// </summary>
|
});
|
||||||
public static AliasRegistry FromManifest(string json)
|
});
|
||||||
{
|
|
||||||
using var builder = new AliasRegistryBuilder();
|
|
||||||
builder.LoadManifest(json);
|
|
||||||
return builder.Build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -66,6 +74,11 @@ namespace Regorus
|
|||||||
/// Normalize an ARM resource JSON and wrap it into the standard input envelope
|
/// Normalize an ARM resource JSON and wrap it into the standard input envelope
|
||||||
/// expected by a compiled Azure Policy program.
|
/// expected by a compiled Azure Policy program.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="resourceJson">Raw ARM resource JSON</param>
|
||||||
|
/// <param name="apiVersion">API version string (e.g. "2023-01-01"), or null to use default alias paths</param>
|
||||||
|
/// <param name="contextJson">Additional context JSON object (pass "{}" if none)</param>
|
||||||
|
/// <param name="parametersJson">Policy parameter values JSON (pass "{}" if none)</param>
|
||||||
|
/// <returns>JSON string: { "resource": <normalized>, "context": <context>, "parameters": <params> }</returns>
|
||||||
public string? NormalizeAndWrap(string resourceJson, string? apiVersion = null, string contextJson = "{}", string parametersJson = "{}")
|
public string? NormalizeAndWrap(string resourceJson, string? apiVersion = null, string contextJson = "{}", string parametersJson = "{}")
|
||||||
{
|
{
|
||||||
return Utf8Marshaller.WithUtf8(resourceJson, resPtr =>
|
return Utf8Marshaller.WithUtf8(resourceJson, resPtr =>
|
||||||
@@ -83,22 +96,27 @@ namespace Regorus
|
|||||||
(byte*)ctxPtr, (byte*)paramsPtr));
|
(byte*)ctxPtr, (byte*)paramsPtr));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else
|
||||||
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
|
{
|
||||||
UseHandle(regPtr =>
|
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
|
||||||
{
|
UseHandle(regPtr =>
|
||||||
return ResultHelpers.GetStringResult(
|
{
|
||||||
API.regorus_alias_registry_normalize_and_wrap(
|
return ResultHelpers.GetStringResult(
|
||||||
(RegorusAliasRegistry*)regPtr,
|
API.regorus_alias_registry_normalize_and_wrap(
|
||||||
(byte*)resPtr, (byte*)apiPtr,
|
(RegorusAliasRegistry*)regPtr,
|
||||||
(byte*)ctxPtr, (byte*)paramsPtr));
|
(byte*)resPtr, (byte*)apiPtr,
|
||||||
}));
|
(byte*)ctxPtr, (byte*)paramsPtr));
|
||||||
|
}));
|
||||||
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="normalizedJson">The normalized resource JSON</param>
|
||||||
|
/// <param name="apiVersion">API version string, or null to use default alias paths</param>
|
||||||
|
/// <returns>Denormalized ARM JSON string</returns>
|
||||||
public string? Denormalize(string normalizedJson, string? apiVersion = null)
|
public string? Denormalize(string normalizedJson, string? apiVersion = null)
|
||||||
{
|
{
|
||||||
return Utf8Marshaller.WithUtf8(normalizedJson, normPtr =>
|
return Utf8Marshaller.WithUtf8(normalizedJson, normPtr =>
|
||||||
@@ -113,16 +131,23 @@ namespace Regorus
|
|||||||
(byte*)normPtr, null));
|
(byte*)normPtr, null));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else
|
||||||
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
|
{
|
||||||
UseHandle(regPtr =>
|
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
|
||||||
{
|
UseHandle(regPtr =>
|
||||||
return ResultHelpers.GetStringResult(
|
{
|
||||||
API.regorus_alias_registry_denormalize(
|
return ResultHelpers.GetStringResult(
|
||||||
(RegorusAliasRegistry*)regPtr,
|
API.regorus_alias_registry_denormalize(
|
||||||
(byte*)normPtr, (byte*)apiPtr));
|
(RegorusAliasRegistry*)regPtr,
|
||||||
}));
|
(byte*)normPtr, (byte*)apiPtr));
|
||||||
|
}));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? CheckAndDropResult(RegorusResult result)
|
||||||
|
{
|
||||||
|
return ResultHelpers.GetStringResult(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
|
||||||
// Licensed under the MIT License.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Regorus.Internal;
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
namespace Regorus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Mutable, single-threaded builder for <see cref="AliasRegistry"/>.
|
|
||||||
/// Load alias data, then call <see cref="Build"/> to freeze the registry.
|
|
||||||
/// </summary>
|
|
||||||
public unsafe sealed class AliasRegistryBuilder : SafeHandleWrapper
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Create an empty alias registry builder.
|
|
||||||
/// </summary>
|
|
||||||
public AliasRegistryBuilder()
|
|
||||||
: base(RegorusAliasRegistryBuilderHandle.Create(), nameof(AliasRegistryBuilder))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
|
|
||||||
/// </summary>
|
|
||||||
public void LoadJson(string json)
|
|
||||||
{
|
|
||||||
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
|
||||||
{
|
|
||||||
UseHandle(builderPtr =>
|
|
||||||
{
|
|
||||||
ResultHelpers.GetStringResult(API.regorus_alias_registry_builder_load_json(
|
|
||||||
(RegorusAliasRegistryBuilder*)builderPtr,
|
|
||||||
(byte*)jsonPtr));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Load a data-plane policy manifest from a JSON string.
|
|
||||||
/// </summary>
|
|
||||||
public void LoadManifest(string json)
|
|
||||||
{
|
|
||||||
Utf8Marshaller.WithUtf8(json, jsonPtr =>
|
|
||||||
{
|
|
||||||
UseHandle(builderPtr =>
|
|
||||||
{
|
|
||||||
ResultHelpers.GetStringResult(API.regorus_alias_registry_builder_load_manifest(
|
|
||||||
(RegorusAliasRegistryBuilder*)builderPtr,
|
|
||||||
(byte*)jsonPtr));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Freeze the builder into an immutable, thread-safe alias registry.
|
|
||||||
/// </summary>
|
|
||||||
public AliasRegistry Build()
|
|
||||||
{
|
|
||||||
return UseHandle(builderPtr =>
|
|
||||||
{
|
|
||||||
var registryPtr = ResultHelpers.GetPointerResult(
|
|
||||||
API.regorus_alias_registry_builder_build((RegorusAliasRegistryBuilder*)builderPtr));
|
|
||||||
return new AliasRegistry(RegorusAliasRegistryHandle.FromPointer(registryPtr));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
|
||||||
// Licensed under the MIT License.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Regorus.Internal;
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
namespace Regorus
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides static methods for compiling Azure Policy JSON definitions
|
|
||||||
/// into RVM programs that can be executed by <see cref="Rvm"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// This class bridges the gap between Azure Policy JSON (the native
|
|
||||||
/// Azure policy language with <c>policyRule</c>, <c>field</c>,
|
|
||||||
/// <c>equals</c>, etc.) and Regorus's RVM execution engine.
|
|
||||||
/// </para>
|
|
||||||
///
|
|
||||||
/// <para>
|
|
||||||
/// <b>Typical workflow:</b>
|
|
||||||
/// </para>
|
|
||||||
/// <list type="number">
|
|
||||||
/// <item>Load alias definitions with <see cref="AliasRegistryBuilder"/> and freeze them into an <see cref="AliasRegistry"/>.</item>
|
|
||||||
/// <item>Normalize the ARM resource via <see cref="AliasRegistry.NormalizeAndWrap"/>.</item>
|
|
||||||
/// <item>Compile the JSON policyRule with <see cref="CompilePolicyRule"/> or the
|
|
||||||
/// full definition with <see cref="CompilePolicyDefinition"/>.</item>
|
|
||||||
/// <item>Execute the resulting <see cref="Program"/> in an <see cref="Rvm"/>
|
|
||||||
/// instance with the normalized input.</item>
|
|
||||||
/// </list>
|
|
||||||
///
|
|
||||||
/// <para>
|
|
||||||
/// <b>Context-dependent policies:</b> Policies that use context functions
|
|
||||||
/// such as <c>subscription()</c>, <c>resourceGroup()</c>, or
|
|
||||||
/// <c>requestContext()</c> require the VM context to be set separately via
|
|
||||||
/// <see cref="Rvm.SetContextJson"/> before execution. The context JSON
|
|
||||||
/// returned by <see cref="AliasRegistry.NormalizeAndWrap"/> is passed as
|
|
||||||
/// <c>input.context</c> but is <b>not</b> automatically wired into the VM's
|
|
||||||
/// ambient context — the caller must do both:
|
|
||||||
/// <c>vm.SetInputJson(envelope)</c> and <c>vm.SetContextJson(contextJson)</c>.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public static unsafe class AzurePolicyCompiler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Compile an Azure Policy JSON policy rule into an RVM <see cref="Program"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="aliasRegistry">
|
|
||||||
/// Alias registry for resolving fully-qualified alias names in field
|
|
||||||
/// references. Pass <c>null</c> if no alias resolution is needed.
|
|
||||||
/// <para>
|
|
||||||
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
|
|
||||||
/// property paths and will silently produce incorrect evaluation results for
|
|
||||||
/// policies that use aliases. Modify/Append effect policies will also skip
|
|
||||||
/// the compile-time modifiability validation. Only pass <c>null</c> when the
|
|
||||||
/// policy is known to contain no alias references (e.g. simple type/location
|
|
||||||
/// checks or unit-test scenarios).
|
|
||||||
/// </para>
|
|
||||||
/// </param>
|
|
||||||
/// <param name="policyRuleJson">
|
|
||||||
/// JSON string containing the policyRule object, e.g.
|
|
||||||
/// <c>{ "if": { "field": "type", "equals": "..." }, "then": { "effect": "deny" } }</c>
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A compiled <see cref="Program"/> ready to be loaded into an
|
|
||||||
/// <see cref="Rvm"/> instance.
|
|
||||||
/// </returns>
|
|
||||||
/// <exception cref="ArgumentNullException">
|
|
||||||
/// Thrown when <paramref name="policyRuleJson"/> is <c>null</c>.
|
|
||||||
/// </exception>
|
|
||||||
/// <exception cref="Exception">
|
|
||||||
/// Thrown when parsing or compilation fails.
|
|
||||||
/// </exception>
|
|
||||||
public static Program CompilePolicyRule(AliasRegistry? aliasRegistry, string policyRuleJson)
|
|
||||||
{
|
|
||||||
if (policyRuleJson is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(policyRuleJson));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Utf8Marshaller.WithUtf8(policyRuleJson, rulePtr =>
|
|
||||||
{
|
|
||||||
if (aliasRegistry is null)
|
|
||||||
{
|
|
||||||
var result = API.regorus_compile_azure_policy_rule(
|
|
||||||
null, (byte*)rulePtr);
|
|
||||||
return GetProgramResult(result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return aliasRegistry.UseHandleForInterop(regPtr =>
|
|
||||||
{
|
|
||||||
var result = API.regorus_compile_azure_policy_rule(
|
|
||||||
(RegorusAliasRegistry*)regPtr, (byte*)rulePtr);
|
|
||||||
return GetProgramResult(result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compile a full Azure Policy definition JSON into an RVM <see cref="Program"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="aliasRegistry">
|
|
||||||
/// Alias registry for resolving fully-qualified alias names in field
|
|
||||||
/// references. Pass <c>null</c> if no alias resolution is needed.
|
|
||||||
/// <para>
|
|
||||||
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
|
|
||||||
/// property paths and will silently produce incorrect evaluation results for
|
|
||||||
/// policies that use aliases. Modify/Append effect policies will also skip
|
|
||||||
/// the compile-time modifiability validation. Only pass <c>null</c> when the
|
|
||||||
/// policy is known to contain no alias references (e.g. simple type/location
|
|
||||||
/// checks or unit-test scenarios).
|
|
||||||
/// </para>
|
|
||||||
/// </param>
|
|
||||||
/// <param name="policyDefinitionJson">
|
|
||||||
/// JSON string containing the full policy definition, which includes
|
|
||||||
/// <c>policyRule</c>, <c>parameters</c>, <c>displayName</c>, etc.
|
|
||||||
/// Accepted in both wrapped and unwrapped forms.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// A compiled <see cref="Program"/> ready to be loaded into an
|
|
||||||
/// <see cref="Rvm"/> instance.
|
|
||||||
/// </returns>
|
|
||||||
/// <exception cref="ArgumentNullException">
|
|
||||||
/// Thrown when <paramref name="policyDefinitionJson"/> is <c>null</c>.
|
|
||||||
/// </exception>
|
|
||||||
/// <exception cref="Exception">
|
|
||||||
/// Thrown when parsing or compilation fails.
|
|
||||||
/// </exception>
|
|
||||||
public static Program CompilePolicyDefinition(AliasRegistry? aliasRegistry, string policyDefinitionJson)
|
|
||||||
{
|
|
||||||
if (policyDefinitionJson is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(policyDefinitionJson));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Utf8Marshaller.WithUtf8(policyDefinitionJson, defnPtr =>
|
|
||||||
{
|
|
||||||
if (aliasRegistry is null)
|
|
||||||
{
|
|
||||||
var result = API.regorus_compile_azure_policy_definition(
|
|
||||||
null, (byte*)defnPtr);
|
|
||||||
return GetProgramResult(result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return aliasRegistry.UseHandleForInterop(regPtr =>
|
|
||||||
{
|
|
||||||
var result = API.regorus_compile_azure_policy_definition(
|
|
||||||
(RegorusAliasRegistry*)regPtr, (byte*)defnPtr);
|
|
||||||
return GetProgramResult(result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Program GetProgramResult(RegorusResult result)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (result.status != RegorusStatus.Ok)
|
|
||||||
{
|
|
||||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
|
||||||
throw result.status.CreateException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Expected program pointer but got different data type");
|
|
||||||
}
|
|
||||||
|
|
||||||
var handle = RegorusProgramHandle.FromPointer((IntPtr)result.pointer_value);
|
|
||||||
return new Program(handle);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
API.regorus_result_drop(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -68,6 +68,18 @@ namespace Regorus
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prepare internal evaluation structures without executing a query.
|
||||||
|
/// This is optional: if skipped, the first evaluation pays this setup cost.
|
||||||
|
/// </summary>
|
||||||
|
public void Prepare()
|
||||||
|
{
|
||||||
|
UseHandle(enginePtr =>
|
||||||
|
{
|
||||||
|
CheckAndDropResult(Regorus.Internal.API.regorus_engine_prepare((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void SetStrictBuiltinErrors(bool strict)
|
public void SetStrictBuiltinErrors(bool strict)
|
||||||
{
|
{
|
||||||
UseHandle(enginePtr =>
|
UseHandle(enginePtr =>
|
||||||
|
|||||||
@@ -92,6 +92,12 @@ namespace Regorus.Internal
|
|||||||
[DllImport(LibraryName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
[DllImport(LibraryName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
internal static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine);
|
internal static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prepare a RegorusEngine for evaluation without executing a query.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_engine_prepare", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern RegorusResult regorus_engine_prepare(RegorusEngine* engine);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compile an RVM program from the engine state with entry points.
|
/// Compile an RVM program from the engine state with entry points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -178,14 +184,6 @@ namespace Regorus.Internal
|
|||||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
internal static extern RegorusResult regorus_rvm_set_input(RegorusRvm* vm, byte* input_json);
|
internal static extern RegorusResult regorus_rvm_set_input(RegorusRvm* vm, byte* input_json);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the context document for the RVM.
|
|
||||||
/// The context provides host-supplied ambient data (e.g. resourceGroup(), subscription())
|
|
||||||
/// that Azure Policy functions can access.
|
|
||||||
/// </summary>
|
|
||||||
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
|
||||||
internal static extern RegorusResult regorus_rvm_set_context(RegorusRvm* vm, byte* context_json);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Execute the program.
|
/// Execute the program.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -498,20 +496,6 @@ namespace Regorus.Internal
|
|||||||
[DllImport(LibraryName, EntryPoint = "regorus_compile_policy_for_target", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
[DllImport(LibraryName, EntryPoint = "regorus_compile_policy_for_target", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
internal static extern RegorusResult regorus_compile_policy_for_target(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len);
|
internal static extern RegorusResult regorus_compile_policy_for_target(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compile an Azure Policy JSON policy rule into an RVM program.
|
|
||||||
/// </summary>
|
|
||||||
[DllImport(LibraryName, EntryPoint = "regorus_compile_azure_policy_rule", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
|
||||||
internal static extern RegorusResult regorus_compile_azure_policy_rule(
|
|
||||||
RegorusAliasRegistry* registry, byte* policy_rule_json);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compile a full Azure Policy definition JSON into an RVM program.
|
|
||||||
/// </summary>
|
|
||||||
[DllImport(LibraryName, EntryPoint = "regorus_compile_azure_policy_definition", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
|
||||||
internal static extern RegorusResult regorus_compile_azure_policy_definition(
|
|
||||||
RegorusAliasRegistry* registry, byte* policy_definition_json);
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Compiled Policy Methods
|
#region Compiled Policy Methods
|
||||||
@@ -695,34 +679,10 @@ namespace Regorus.Internal
|
|||||||
#region Alias Registry Methods
|
#region Alias Registry Methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new alias registry builder.
|
/// Create a new, empty AliasRegistry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
internal static extern RegorusAliasRegistryBuilder* regorus_alias_registry_builder_new();
|
internal static extern RegorusAliasRegistry* regorus_alias_registry_new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Drop an alias registry builder.
|
|
||||||
/// </summary>
|
|
||||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
|
||||||
internal static extern void regorus_alias_registry_builder_drop(RegorusAliasRegistryBuilder* builder);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Load control-plane alias data into the builder.
|
|
||||||
/// </summary>
|
|
||||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
|
||||||
internal static extern RegorusResult regorus_alias_registry_builder_load_json(RegorusAliasRegistryBuilder* builder, byte* json);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Load a data-plane policy manifest into the builder.
|
|
||||||
/// </summary>
|
|
||||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
|
||||||
internal static extern RegorusResult regorus_alias_registry_builder_load_manifest(RegorusAliasRegistryBuilder* builder, byte* json);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Freeze a builder into an immutable alias registry.
|
|
||||||
/// </summary>
|
|
||||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_build", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
|
||||||
internal static extern RegorusResult regorus_alias_registry_builder_build(RegorusAliasRegistryBuilder* builder);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drop an AliasRegistry.
|
/// Drop an AliasRegistry.
|
||||||
@@ -730,6 +690,18 @@ namespace Regorus.Internal
|
|||||||
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
internal static extern void regorus_alias_registry_drop(RegorusAliasRegistry* registry);
|
internal static extern void regorus_alias_registry_drop(RegorusAliasRegistry* registry);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load control-plane alias data (array of ProviderAliases) into the registry.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern RegorusResult regorus_alias_registry_load_json(RegorusAliasRegistry* registry, byte* json);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load a data-plane policy manifest into the registry.
|
||||||
|
/// </summary>
|
||||||
|
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern RegorusResult regorus_alias_registry_load_manifest(RegorusAliasRegistry* registry, byte* json);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return the number of resource types loaded in the alias registry.
|
/// Return the number of resource types loaded in the alias registry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -957,14 +929,6 @@ namespace Regorus.Internal
|
|||||||
public byte* content;
|
public byte* content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wrapper for AliasRegistryBuilder.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
internal unsafe partial struct RegorusAliasRegistryBuilder
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wrapper for AliasRegistry.
|
/// Wrapper for AliasRegistry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Regorus
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe sealed class Program : SafeHandleWrapper
|
public unsafe sealed class Program : SafeHandleWrapper
|
||||||
{
|
{
|
||||||
internal Program(RegorusProgramHandle handle)
|
private Program(RegorusProgramHandle handle)
|
||||||
: base(handle, nameof(Program))
|
: base(handle, nameof(Program))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,29 +69,5 @@ namespace Regorus.Internal
|
|||||||
API.regorus_result_drop(result);
|
API.regorus_result_drop(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal static IntPtr GetPointerResult(RegorusResult result)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (result.status != RegorusStatus.Ok)
|
|
||||||
{
|
|
||||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
|
||||||
throw result.status.CreateException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected pointer result.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (IntPtr)result.pointer_value;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
API.regorus_result_drop(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,24 +106,6 @@ namespace Regorus
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the context document for the VM.
|
|
||||||
/// The context provides host-supplied ambient data (e.g. resourceGroup(),
|
|
||||||
/// subscription()) that Azure Policy functions can access via LoadContext
|
|
||||||
/// instructions.
|
|
||||||
/// </summary>
|
|
||||||
public void SetContextJson(string contextJson)
|
|
||||||
{
|
|
||||||
Utf8Marshaller.WithUtf8(contextJson, contextPtr =>
|
|
||||||
{
|
|
||||||
UseHandle(vmPtr =>
|
|
||||||
{
|
|
||||||
CheckAndDropResult(API.regorus_rvm_set_context((RegorusRvm*)vmPtr, (byte*)contextPtr));
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the execution mode (0 = run-to-completion, 1 = suspendable).
|
/// Set the execution mode (0 = run-to-completion, 1 = suspendable).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -184,48 +184,28 @@ namespace Regorus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class RegorusAliasRegistryBuilderHandle : SafeHandleZeroOrMinusOneIsInvalid
|
|
||||||
{
|
|
||||||
private RegorusAliasRegistryBuilderHandle() : base(ownsHandle: true)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static RegorusAliasRegistryBuilderHandle Create()
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
var raw = Internal.API.regorus_alias_registry_builder_new();
|
|
||||||
if (raw is null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Failed to create Regorus alias registry builder.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var handle = new RegorusAliasRegistryBuilderHandle();
|
|
||||||
handle.SetHandle((IntPtr)raw);
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool ReleaseHandle()
|
|
||||||
{
|
|
||||||
if (!IsInvalid)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
Internal.API.regorus_alias_registry_builder_drop((Internal.RegorusAliasRegistryBuilder*)handle);
|
|
||||||
}
|
|
||||||
SetHandle(IntPtr.Zero);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class RegorusAliasRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
|
internal sealed class RegorusAliasRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||||
{
|
{
|
||||||
private RegorusAliasRegistryHandle() : base(ownsHandle: true)
|
private RegorusAliasRegistryHandle() : base(ownsHandle: true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static RegorusAliasRegistryHandle Create()
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
var raw = Internal.API.regorus_alias_registry_new();
|
||||||
|
if (raw is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to create Regorus alias registry.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle = new RegorusAliasRegistryHandle();
|
||||||
|
handle.SetHandle((IntPtr)raw);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal static RegorusAliasRegistryHandle FromPointer(IntPtr pointer)
|
internal static RegorusAliasRegistryHandle FromPointer(IntPtr pointer)
|
||||||
{
|
{
|
||||||
if (pointer == IntPtr.Zero)
|
if (pointer == IntPtr.Zero)
|
||||||
|
|||||||
@@ -232,9 +232,6 @@ allow if {
|
|||||||
|
|
||||||
Console.WriteLine("\n8. RVM host await (suspend/resume):");
|
Console.WriteLine("\n8. RVM host await (suspend/resume):");
|
||||||
DemonstrateRvmHostAwait();
|
DemonstrateRvmHostAwait();
|
||||||
|
|
||||||
Console.WriteLine("\n9. Azure Policy JSON compilation:");
|
|
||||||
DemonstrateAzurePolicyJsonCompilation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
|
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
|
||||||
@@ -495,80 +492,4 @@ allow if {
|
|||||||
var resumed = vm.Resume("{\"tier\":\"gold\"}");
|
var resumed = vm.Resume("{\"tier\":\"gold\"}");
|
||||||
Console.WriteLine($"HostAwait resumed result: {resumed}");
|
Console.WriteLine($"HostAwait resumed result: {resumed}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Azure Policy JSON constants
|
|
||||||
private const string STORAGE_ALIASES_JSON = @"[{
|
|
||||||
""namespace"": ""Microsoft.Storage"",
|
|
||||||
""resourceTypes"": [{
|
|
||||||
""resourceType"": ""storageAccounts"",
|
|
||||||
""capabilities"": ""SupportsTags, SupportsLocation"",
|
|
||||||
""aliases"": [
|
|
||||||
{
|
|
||||||
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
|
|
||||||
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
|
|
||||||
""paths"": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}]";
|
|
||||||
|
|
||||||
private const string HTTPS_DENY_RULE = @"{
|
|
||||||
""if"": {
|
|
||||||
""allOf"": [
|
|
||||||
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
|
|
||||||
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
""then"": { ""effect"": ""deny"" }
|
|
||||||
}";
|
|
||||||
|
|
||||||
static void DemonstrateAzurePolicyJsonCompilation()
|
|
||||||
{
|
|
||||||
// 1. Set up alias registry
|
|
||||||
using var registry = Regorus.AliasRegistry.FromJson(STORAGE_ALIASES_JSON);
|
|
||||||
Console.WriteLine("Loaded storage account aliases");
|
|
||||||
|
|
||||||
// 2. Compile the JSON policy rule directly (no Rego needed)
|
|
||||||
using var program = Regorus.AzurePolicyCompiler.CompilePolicyRule(registry, HTTPS_DENY_RULE);
|
|
||||||
Console.WriteLine("Compiled Azure Policy JSON rule to RVM program");
|
|
||||||
|
|
||||||
// 3. Normalize an ARM resource
|
|
||||||
var armResource = @"{
|
|
||||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
|
||||||
""name"": ""insecurestorage"",
|
|
||||||
""location"": ""eastus"",
|
|
||||||
""properties"": { ""supportsHttpsTrafficOnly"": false }
|
|
||||||
}";
|
|
||||||
var envelope = registry.NormalizeAndWrap(armResource, apiVersion: null, contextJson: "{}", parametersJson: "{}");
|
|
||||||
Console.WriteLine($"Normalized ARM resource to evaluation envelope");
|
|
||||||
|
|
||||||
// 4. Execute in the RVM
|
|
||||||
// Note: For policies using context functions (subscription(), resourceGroup()),
|
|
||||||
// call vm.SetContextJson(contextJson) before execution. The context from
|
|
||||||
// NormalizeAndWrap is in the envelope but must also be set on the VM separately.
|
|
||||||
using var vm = new Regorus.Rvm();
|
|
||||||
vm.LoadProgram(program);
|
|
||||||
vm.SetInputJson(envelope!);
|
|
||||||
// vm.SetContextJson(contextJson); // ← required for context-dependent policies
|
|
||||||
var result = vm.ExecuteEntryPoint("main");
|
|
||||||
Console.WriteLine($"Evaluation result (non-compliant): {result}");
|
|
||||||
|
|
||||||
// 5. Test with a compliant resource
|
|
||||||
var compliantResource = @"{
|
|
||||||
""type"": ""Microsoft.Storage/storageAccounts"",
|
|
||||||
""name"": ""securestorage"",
|
|
||||||
""location"": ""eastus"",
|
|
||||||
""properties"": { ""supportsHttpsTrafficOnly"": true }
|
|
||||||
}";
|
|
||||||
var compliantEnvelope = registry.NormalizeAndWrap(compliantResource, apiVersion: null, contextJson: "{}", parametersJson: "{}");
|
|
||||||
using var vm2 = new Regorus.Rvm();
|
|
||||||
vm2.LoadProgram(program);
|
|
||||||
vm2.SetInputJson(compliantEnvelope!);
|
|
||||||
var compliantResult = vm2.ExecuteEntryPoint("main");
|
|
||||||
Console.WriteLine($"Evaluation result (compliant): {compliantResult}");
|
|
||||||
|
|
||||||
// 6. Demonstrate program serialization
|
|
||||||
var binary = program.SerializeBinary();
|
|
||||||
Console.WriteLine($"Serialized program size: {binary.Length} bytes");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
647
bindings/ffi/Cargo.lock
generated
647
bindings/ffi/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "regorus-ffi"
|
name = "regorus-ffi"
|
||||||
version = "0.11.0"
|
version = "0.10.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ crate-type = ["cdylib", "staticlib"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
regorus = { path = "../..", default-features = false }
|
regorus = { path = "../..", default-features = false }
|
||||||
serde_json = "1.0.150"
|
serde_json = "1.0.140"
|
||||||
parking_lot = { version = "0.12", optional = true }
|
parking_lot = { version = "0.12", optional = true }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
@@ -5,108 +5,66 @@
|
|||||||
|
|
||||||
#![cfg(feature = "azure_policy")]
|
#![cfg(feature = "azure_policy")]
|
||||||
|
|
||||||
use crate::common::{from_c_str, to_ref, to_shared_ref, RegorusResult, RegorusStatus};
|
use crate::common::{from_c_str, to_ref, RegorusResult, RegorusStatus};
|
||||||
use crate::panic_guard::with_unwind_guard;
|
use crate::panic_guard::with_unwind_guard;
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::sync::Arc;
|
use anyhow::Result;
|
||||||
use anyhow::{anyhow, Result};
|
use core::ffi::c_char;
|
||||||
use core::ffi::{c_char, c_void};
|
use core::ptr;
|
||||||
use core::{mem, ptr};
|
|
||||||
|
|
||||||
use regorus::languages::azure_policy::aliases::AliasRegistry;
|
use regorus::languages::azure_policy::aliases::AliasRegistry;
|
||||||
|
|
||||||
/// Mutable builder for `AliasRegistry`.
|
/// Opaque wrapper for `AliasRegistry`.
|
||||||
///
|
|
||||||
/// This handle is intentionally single-threaded and must not be used
|
|
||||||
/// concurrently. Callers should finish loading alias data and then freeze it
|
|
||||||
/// into a `RegorusAliasRegistry` via `regorus_alias_registry_builder_build`.
|
|
||||||
pub struct RegorusAliasRegistryBuilder {
|
|
||||||
registry: AliasRegistry,
|
|
||||||
built: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RegorusAliasRegistryBuilder {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
registry: AliasRegistry::new(),
|
|
||||||
built: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn registry_mut(&mut self) -> Result<&mut AliasRegistry> {
|
|
||||||
if self.built {
|
|
||||||
return Err(anyhow!("alias registry builder has already been built"));
|
|
||||||
}
|
|
||||||
Ok(&mut self.registry)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(&mut self) -> Result<RegorusAliasRegistry> {
|
|
||||||
if self.built {
|
|
||||||
return Err(anyhow!("alias registry builder has already been built"));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.built = true;
|
|
||||||
Ok(RegorusAliasRegistry {
|
|
||||||
registry: Arc::new(mem::replace(&mut self.registry, AliasRegistry::new())),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Frozen, immutable alias registry.
|
|
||||||
pub struct RegorusAliasRegistry {
|
pub struct RegorusAliasRegistry {
|
||||||
registry: Arc<AliasRegistry>,
|
registry: AliasRegistry,
|
||||||
}
|
|
||||||
|
|
||||||
impl RegorusAliasRegistry {
|
|
||||||
/// Return a shared reference to the inner registry for use by the compiler.
|
|
||||||
pub(crate) fn inner(&self) -> Arc<AliasRegistry> {
|
|
||||||
Arc::clone(&self.registry)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Builder lifecycle
|
// Lifecycle
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Create a new, empty `AliasRegistry` builder.
|
/// Create a new, empty `AliasRegistry`.
|
||||||
///
|
///
|
||||||
/// The caller must eventually call `regorus_alias_registry_builder_drop`.
|
/// The caller must eventually call `regorus_alias_registry_drop` to free the handle.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_alias_registry_builder_new() -> *mut RegorusAliasRegistryBuilder {
|
pub extern "C" fn regorus_alias_registry_new() -> *mut RegorusAliasRegistry {
|
||||||
Box::into_raw(Box::new(RegorusAliasRegistryBuilder::new()))
|
let wrapper = RegorusAliasRegistry {
|
||||||
|
registry: AliasRegistry::new(),
|
||||||
|
};
|
||||||
|
Box::into_raw(Box::new(wrapper))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop a `RegorusAliasRegistryBuilder`.
|
/// Drop a `RegorusAliasRegistry`.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_alias_registry_builder_drop(builder: *mut RegorusAliasRegistryBuilder) {
|
pub extern "C" fn regorus_alias_registry_drop(registry: *mut RegorusAliasRegistry) {
|
||||||
if let Ok(builder) = to_ref(builder) {
|
if let Ok(r) = to_ref(registry) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = Box::from_raw(ptr::from_mut(builder));
|
let _ = Box::from_raw(ptr::from_mut(r));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Builder loading
|
// Loading
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Load control-plane alias data (array of `ProviderAliases`) into the builder.
|
/// Load control-plane alias data (array of `ProviderAliases`) into the registry.
|
||||||
///
|
///
|
||||||
/// `json` must be a valid null-terminated UTF-8 string containing the JSON
|
/// `json` must be a valid null-terminated UTF-8 string containing the JSON
|
||||||
/// array returned by `Get-AzPolicyAlias` or the static
|
/// array returned by `Get-AzPolicyAlias` or the static
|
||||||
/// `ResourceTypesAndAliases.json` file.
|
/// `ResourceTypesAndAliases.json` file.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_alias_registry_builder_load_json(
|
pub extern "C" fn regorus_alias_registry_load_json(
|
||||||
builder: *mut RegorusAliasRegistryBuilder,
|
registry: *mut RegorusAliasRegistry,
|
||||||
json: *const c_char,
|
json: *const c_char,
|
||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<()> {
|
let output = || -> Result<()> {
|
||||||
let json_str = from_c_str(json)?;
|
let json_str = from_c_str(json)?;
|
||||||
to_ref(builder)?.registry_mut()?.load_from_json(&json_str)?;
|
to_ref(registry)?.registry.load_from_json(&json_str)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}();
|
}();
|
||||||
|
|
||||||
@@ -120,20 +78,20 @@ pub extern "C" fn regorus_alias_registry_builder_load_json(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a data-plane policy manifest into the builder.
|
/// Load a data-plane policy manifest into the registry.
|
||||||
///
|
///
|
||||||
/// `json` must be a valid null-terminated UTF-8 string containing a single
|
/// `json` must be a valid null-terminated UTF-8 string containing a single
|
||||||
/// `DataPolicyManifest` JSON object.
|
/// `DataPolicyManifest` JSON object.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_alias_registry_builder_load_manifest(
|
pub extern "C" fn regorus_alias_registry_load_manifest(
|
||||||
builder: *mut RegorusAliasRegistryBuilder,
|
registry: *mut RegorusAliasRegistry,
|
||||||
json: *const c_char,
|
json: *const c_char,
|
||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<()> {
|
let output = || -> Result<()> {
|
||||||
let json_str = from_c_str(json)?;
|
let json_str = from_c_str(json)?;
|
||||||
to_ref(builder)?
|
to_ref(registry)?
|
||||||
.registry_mut()?
|
.registry
|
||||||
.load_data_policy_manifest_json(&json_str)?;
|
.load_data_policy_manifest_json(&json_str)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}();
|
}();
|
||||||
@@ -148,52 +106,16 @@ pub extern "C" fn regorus_alias_registry_builder_load_manifest(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Freeze a builder into an immutable `RegorusAliasRegistry`.
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn regorus_alias_registry_builder_build(
|
|
||||||
builder: *mut RegorusAliasRegistryBuilder,
|
|
||||||
) -> RegorusResult {
|
|
||||||
with_unwind_guard(|| {
|
|
||||||
let output = || -> Result<*mut RegorusAliasRegistry> {
|
|
||||||
let registry = to_ref(builder)?.build()?;
|
|
||||||
Ok(Box::into_raw(Box::new(registry)))
|
|
||||||
}();
|
|
||||||
|
|
||||||
match output {
|
|
||||||
Ok(registry) => RegorusResult::ok_pointer(registry as *mut c_void),
|
|
||||||
Err(e) => {
|
|
||||||
RegorusResult::err_with_message(RegorusStatus::InvalidArgument, format!("{e}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Frozen registry lifecycle
|
// Queries
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Drop a `RegorusAliasRegistry`.
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn regorus_alias_registry_drop(registry: *mut RegorusAliasRegistry) {
|
|
||||||
if let Ok(registry) = to_ref(registry) {
|
|
||||||
unsafe {
|
|
||||||
let _ = Box::from_raw(ptr::from_mut(registry));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Frozen registry queries
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Return the number of resource types loaded in the alias registry.
|
/// Return the number of resource types loaded in the alias registry.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_alias_registry_len(
|
pub extern "C" fn regorus_alias_registry_len(registry: *mut RegorusAliasRegistry) -> RegorusResult {
|
||||||
registry: *const RegorusAliasRegistry,
|
|
||||||
) -> RegorusResult {
|
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<i64> {
|
let output = || -> Result<i64> {
|
||||||
let len = to_shared_ref(registry)?.registry.len();
|
let len = to_ref(registry)?.registry.len();
|
||||||
Ok(len as i64)
|
Ok(len as i64)
|
||||||
}();
|
}();
|
||||||
|
|
||||||
@@ -212,9 +134,15 @@ pub extern "C" fn regorus_alias_registry_len(
|
|||||||
///
|
///
|
||||||
/// Returns a JSON string:
|
/// Returns a JSON string:
|
||||||
/// `{ "resource": <normalized>, "context": <context>, "parameters": <params> }`.
|
/// `{ "resource": <normalized>, "context": <context>, "parameters": <params> }`.
|
||||||
|
///
|
||||||
|
/// * `resource_json` – raw ARM resource JSON
|
||||||
|
/// * `api_version` – API version string (e.g. `"2023-01-01"`), or null to use
|
||||||
|
/// the default alias paths
|
||||||
|
/// * `context_json` – JSON object for additional context (pass `"{}"` if none)
|
||||||
|
/// * `parameters_json` – JSON object of policy parameter values (pass `"{}"` if none)
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
|
pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
|
||||||
registry: *const RegorusAliasRegistry,
|
registry: *mut RegorusAliasRegistry,
|
||||||
resource_json: *const c_char,
|
resource_json: *const c_char,
|
||||||
api_version: *const c_char,
|
api_version: *const c_char,
|
||||||
context_json: *const c_char,
|
context_json: *const c_char,
|
||||||
@@ -240,7 +168,7 @@ pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
|
|||||||
let context = regorus::Value::from_json_str(&context_str)?;
|
let context = regorus::Value::from_json_str(&context_str)?;
|
||||||
let params = regorus::Value::from_json_str(¶ms_str)?;
|
let params = regorus::Value::from_json_str(¶ms_str)?;
|
||||||
|
|
||||||
let wrapped = to_shared_ref(registry)?.registry.normalize_and_wrap(
|
let wrapped = to_ref(registry)?.registry.normalize_and_wrap(
|
||||||
&resource,
|
&resource,
|
||||||
api_ver.as_deref(),
|
api_ver.as_deref(),
|
||||||
Some(context),
|
Some(context),
|
||||||
@@ -257,9 +185,14 @@ pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
/// Denormalize a previously-normalized resource JSON back to ARM format.
|
||||||
|
///
|
||||||
|
/// * `normalized_json` – the normalized resource JSON
|
||||||
|
/// * `api_version` – API version string, or null to use the default alias paths
|
||||||
|
///
|
||||||
|
/// Returns the denormalized ARM JSON string.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_alias_registry_denormalize(
|
pub extern "C" fn regorus_alias_registry_denormalize(
|
||||||
registry: *const RegorusAliasRegistry,
|
registry: *mut RegorusAliasRegistry,
|
||||||
normalized_json: *const c_char,
|
normalized_json: *const c_char,
|
||||||
api_version: *const c_char,
|
api_version: *const c_char,
|
||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
@@ -279,7 +212,7 @@ pub extern "C" fn regorus_alias_registry_denormalize(
|
|||||||
|
|
||||||
let normalized = regorus::Value::from_json_str(&normalized_str)?;
|
let normalized = regorus::Value::from_json_str(&normalized_str)?;
|
||||||
|
|
||||||
let result = to_shared_ref(registry)?
|
let result = to_ref(registry)?
|
||||||
.registry
|
.registry
|
||||||
.denormalize(&normalized, api_ver.as_deref());
|
.denormalize(&normalized, api_ver.as_deref());
|
||||||
result.to_json_str()
|
result.to_json_str()
|
||||||
@@ -299,10 +232,12 @@ mod tests {
|
|||||||
use core::ffi::CStr;
|
use core::ffi::CStr;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
/// Helper: create a C string from a Rust &str.
|
||||||
fn c(s: &str) -> CString {
|
fn c(s: &str) -> CString {
|
||||||
CString::new(s).expect("CString::new failed")
|
CString::new(s).expect("CString::new failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper: assert a RegorusResult has Ok status and extract string output.
|
||||||
fn assert_ok_string(r: &RegorusResult) -> String {
|
fn assert_ok_string(r: &RegorusResult) -> String {
|
||||||
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
||||||
assert!(!r.output.is_null(), "expected non-null output");
|
assert!(!r.output.is_null(), "expected non-null output");
|
||||||
@@ -313,51 +248,12 @@ mod tests {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper: assert a RegorusResult has Ok status with integer output.
|
||||||
fn assert_ok_int(r: &RegorusResult) -> i64 {
|
fn assert_ok_int(r: &RegorusResult) -> i64 {
|
||||||
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
||||||
r.int_value
|
r.int_value
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_ok_pointer(r: &RegorusResult) -> *mut c_void {
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
|
|
||||||
assert!(matches!(
|
|
||||||
r.data_type,
|
|
||||||
crate::common::RegorusDataType::Pointer
|
|
||||||
));
|
|
||||||
assert!(!r.pointer_value.is_null());
|
|
||||||
r.pointer_value
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_registry_with_json(json: &str) -> *mut RegorusAliasRegistry {
|
|
||||||
let builder = regorus_alias_registry_builder_new();
|
|
||||||
let json = c(json);
|
|
||||||
|
|
||||||
let r = regorus_alias_registry_builder_load_json(builder, json.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let r = regorus_alias_registry_builder_build(builder);
|
|
||||||
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
regorus_alias_registry_builder_drop(builder);
|
|
||||||
registry
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_registry_with_manifest(json: &str) -> *mut RegorusAliasRegistry {
|
|
||||||
let builder = regorus_alias_registry_builder_new();
|
|
||||||
let json = c(json);
|
|
||||||
|
|
||||||
let r = regorus_alias_registry_builder_load_manifest(builder, json.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let r = regorus_alias_registry_builder_build(builder);
|
|
||||||
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
regorus_alias_registry_builder_drop(builder);
|
|
||||||
registry
|
|
||||||
}
|
|
||||||
|
|
||||||
const ALIASES: &str = r#"[{
|
const ALIASES: &str = r#"[{
|
||||||
"namespace": "Microsoft.Storage",
|
"namespace": "Microsoft.Storage",
|
||||||
"resourceTypes": [{
|
"resourceTypes": [{
|
||||||
@@ -383,21 +279,20 @@ mod tests {
|
|||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lifecycle_builder_build_and_drop() {
|
fn lifecycle_new_and_drop() {
|
||||||
let builder = regorus_alias_registry_builder_new();
|
let reg = regorus_alias_registry_new();
|
||||||
assert!(!builder.is_null());
|
assert!(!reg.is_null());
|
||||||
|
regorus_alias_registry_drop(reg);
|
||||||
let r = regorus_alias_registry_builder_build(builder);
|
|
||||||
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
regorus_alias_registry_builder_drop(builder);
|
|
||||||
regorus_alias_registry_drop(registry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_json_and_check_len() {
|
fn load_json_and_check_len() {
|
||||||
let reg = build_registry_with_json(ALIASES);
|
let reg = regorus_alias_registry_new();
|
||||||
|
let json = c(ALIASES);
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_load_json(reg, json.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
let r = regorus_alias_registry_len(reg);
|
let r = regorus_alias_registry_len(reg);
|
||||||
assert_eq!(assert_ok_int(&r), 1);
|
assert_eq!(assert_ok_int(&r), 1);
|
||||||
@@ -408,7 +303,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_manifest_and_check_len() {
|
fn load_manifest_and_check_len() {
|
||||||
let reg = build_registry_with_manifest(MANIFEST);
|
let reg = regorus_alias_registry_new();
|
||||||
|
let json = c(MANIFEST);
|
||||||
|
|
||||||
|
let r = regorus_alias_registry_load_manifest(reg, json.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
let r = regorus_alias_registry_len(reg);
|
let r = regorus_alias_registry_len(reg);
|
||||||
assert_eq!(assert_ok_int(&r), 1);
|
assert_eq!(assert_ok_int(&r), 1);
|
||||||
@@ -419,39 +319,23 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn load_invalid_json_returns_error() {
|
fn load_invalid_json_returns_error() {
|
||||||
let builder = regorus_alias_registry_builder_new();
|
let reg = regorus_alias_registry_new();
|
||||||
let bad = c("not valid json");
|
let bad = c("not valid json");
|
||||||
|
|
||||||
let r = regorus_alias_registry_builder_load_json(builder, bad.as_ptr());
|
let r = regorus_alias_registry_load_json(reg, bad.as_ptr());
|
||||||
assert_ne!(r.status, RegorusStatus::Ok);
|
assert_ne!(r.status, RegorusStatus::Ok);
|
||||||
regorus_result_drop(r);
|
regorus_result_drop(r);
|
||||||
|
|
||||||
regorus_alias_registry_builder_drop(builder);
|
regorus_alias_registry_drop(reg);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn builder_cannot_be_reused_after_build() {
|
|
||||||
let builder = regorus_alias_registry_builder_new();
|
|
||||||
let r = regorus_alias_registry_builder_build(builder);
|
|
||||||
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let aliases = c(ALIASES);
|
|
||||||
let r = regorus_alias_registry_builder_load_json(builder, aliases.as_ptr());
|
|
||||||
assert_ne!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let r = regorus_alias_registry_builder_build(builder);
|
|
||||||
assert_ne!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
regorus_alias_registry_builder_drop(builder);
|
|
||||||
regorus_alias_registry_drop(registry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalize_and_wrap_round_trip() {
|
fn normalize_and_wrap_round_trip() {
|
||||||
let reg = build_registry_with_json(ALIASES);
|
let reg = regorus_alias_registry_new();
|
||||||
|
let aliases = c(ALIASES);
|
||||||
|
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
let resource = c(r#"{
|
let resource = c(r#"{
|
||||||
"name": "acct1",
|
"name": "acct1",
|
||||||
@@ -462,6 +346,7 @@ mod tests {
|
|||||||
let ctx = c(r#"{"resourceGroup": {"name": "rg1"}}"#);
|
let ctx = c(r#"{"resourceGroup": {"name": "rg1"}}"#);
|
||||||
let params = c(r#"{"env": "prod"}"#);
|
let params = c(r#"{"env": "prod"}"#);
|
||||||
|
|
||||||
|
// Normalize
|
||||||
let r = regorus_alias_registry_normalize_and_wrap(
|
let r = regorus_alias_registry_normalize_and_wrap(
|
||||||
reg,
|
reg,
|
||||||
resource.as_ptr(),
|
resource.as_ptr(),
|
||||||
@@ -472,6 +357,7 @@ mod tests {
|
|||||||
let envelope_json = assert_ok_string(&r);
|
let envelope_json = assert_ok_string(&r);
|
||||||
regorus_result_drop(r);
|
regorus_result_drop(r);
|
||||||
|
|
||||||
|
// Parse and verify structure
|
||||||
let envelope: serde_json::Value =
|
let envelope: serde_json::Value =
|
||||||
serde_json::from_str(&envelope_json).expect("invalid JSON output");
|
serde_json::from_str(&envelope_json).expect("invalid JSON output");
|
||||||
assert!(
|
assert!(
|
||||||
@@ -487,13 +373,16 @@ mod tests {
|
|||||||
"envelope missing 'context'"
|
"envelope missing 'context'"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The normalized resource should have lowercased alias fields
|
||||||
let res = &envelope["resource"];
|
let res = &envelope["resource"];
|
||||||
assert_eq!(res["supportshttpstrafficonly"], true);
|
assert_eq!(res["supportshttpstrafficonly"], true);
|
||||||
assert_eq!(res["name"], "acct1");
|
assert_eq!(res["name"], "acct1");
|
||||||
|
|
||||||
|
// Context and parameters should be passed through
|
||||||
assert_eq!(envelope["context"]["resourceGroup"]["name"], "rg1");
|
assert_eq!(envelope["context"]["resourceGroup"]["name"], "rg1");
|
||||||
assert_eq!(envelope["parameters"]["env"], "prod");
|
assert_eq!(envelope["parameters"]["env"], "prod");
|
||||||
|
|
||||||
|
// Denormalize the resource portion
|
||||||
let resource_json = serde_json::to_string(&res).expect("serialize resource");
|
let resource_json = serde_json::to_string(&res).expect("serialize resource");
|
||||||
let norm_cstr = c(&resource_json);
|
let norm_cstr = c(&resource_json);
|
||||||
|
|
||||||
@@ -503,6 +392,7 @@ mod tests {
|
|||||||
|
|
||||||
let denorm: serde_json::Value =
|
let denorm: serde_json::Value =
|
||||||
serde_json::from_str(&denorm_json).expect("invalid denorm JSON");
|
serde_json::from_str(&denorm_json).expect("invalid denorm JSON");
|
||||||
|
// Should be back under properties with restored casing
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
denorm["properties"]["supportsHttpsTrafficOnly"], true,
|
denorm["properties"]["supportsHttpsTrafficOnly"], true,
|
||||||
"expected restored casing under properties"
|
"expected restored casing under properties"
|
||||||
@@ -513,7 +403,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn denormalize_invalid_json_returns_error() {
|
fn denormalize_invalid_json_returns_error() {
|
||||||
let reg = build_registry_with_json(ALIASES);
|
let reg = regorus_alias_registry_new();
|
||||||
|
let aliases = c(ALIASES);
|
||||||
|
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
let bad = c("not json");
|
let bad = c("not json");
|
||||||
let api = c("2023-01-01");
|
let api = c("2023-01-01");
|
||||||
@@ -526,7 +420,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalize_data_plane_manifest() {
|
fn normalize_data_plane_manifest() {
|
||||||
let reg = build_registry_with_manifest(MANIFEST);
|
let reg = regorus_alias_registry_new();
|
||||||
|
let manifest = c(MANIFEST);
|
||||||
|
let r = regorus_alias_registry_load_manifest(reg, manifest.as_ptr());
|
||||||
|
assert_eq!(r.status, RegorusStatus::Ok);
|
||||||
|
regorus_result_drop(r);
|
||||||
|
|
||||||
let resource = c(r#"{
|
let resource = c(r#"{
|
||||||
"type": "Microsoft.KeyVault.Data/vaults/certificates",
|
"type": "Microsoft.KeyVault.Data/vaults/certificates",
|
||||||
@@ -555,12 +453,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_registry_normalize() {
|
fn empty_registry_normalize() {
|
||||||
let builder = regorus_alias_registry_builder_new();
|
let reg = regorus_alias_registry_new();
|
||||||
let r = regorus_alias_registry_builder_build(builder);
|
|
||||||
let reg = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
regorus_alias_registry_builder_drop(builder);
|
|
||||||
|
|
||||||
let resource = c(r#"{"name": "test", "type": "Unknown/type", "properties": {"foo": 1}}"#);
|
let resource = c(r#"{"name": "test", "type": "Unknown/type", "properties": {"foo": 1}}"#);
|
||||||
let api = c("");
|
let api = c("");
|
||||||
let ctx = c("{}");
|
let ctx = c("{}");
|
||||||
@@ -577,6 +470,7 @@ mod tests {
|
|||||||
regorus_result_drop(r);
|
regorus_result_drop(r);
|
||||||
|
|
||||||
let envelope: serde_json::Value = serde_json::from_str(&json).expect("invalid JSON");
|
let envelope: serde_json::Value = serde_json::from_str(&json).expect("invalid JSON");
|
||||||
|
// Without aliases, properties should still be flattened
|
||||||
assert_eq!(envelope["resource"]["foo"], 1);
|
assert_eq!(envelope["resource"]["foo"], 1);
|
||||||
assert_eq!(envelope["resource"]["name"], "test");
|
assert_eq!(envelope["resource"]["name"], "test");
|
||||||
|
|
||||||
|
|||||||
@@ -236,10 +236,6 @@ pub(crate) fn to_ref<'a, T>(t: *mut T) -> Result<&'a mut T> {
|
|||||||
unsafe { t.as_mut().ok_or_else(|| anyhow!("null pointer")) }
|
unsafe { t.as_mut().ok_or_else(|| anyhow!("null pointer")) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn to_shared_ref<'a, T>(t: *const T) -> Result<&'a T> {
|
|
||||||
unsafe { t.as_ref().ok_or_else(|| anyhow!("null pointer")) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn to_regorus_result(r: Result<()>) -> RegorusResult {
|
pub(crate) fn to_regorus_result(r: Result<()>) -> RegorusResult {
|
||||||
match r {
|
match r {
|
||||||
Ok(()) => RegorusResult::ok_void(),
|
Ok(()) => RegorusResult::ok_void(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
use crate::common::{from_c_str, to_shared_ref, RegorusResult, RegorusStatus};
|
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
|
||||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||||
use crate::panic_guard::with_unwind_guard;
|
use crate::panic_guard::with_unwind_guard;
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
@@ -208,220 +208,6 @@ fn convert_c_modules_to_rust(
|
|||||||
Ok(policy_modules)
|
Ok(policy_modules)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Azure Policy JSON compilation
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Compile an Azure Policy JSON policy rule into an RVM program.
|
|
||||||
///
|
|
||||||
/// Parses the JSON `policyRule` (the `{ "if": ..., "then": ... }` object),
|
|
||||||
/// resolves aliases using the provided registry, and compiles the result
|
|
||||||
/// into an RVM [`Program`] that can be loaded into a [`RegorusRvm`].
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// * `registry` - Alias registry handle, or null.
|
|
||||||
/// * `policy_rule_json` - JSON string containing the policyRule object
|
|
||||||
///
|
|
||||||
/// # Null registry behavior
|
|
||||||
///
|
|
||||||
/// When `registry` is null, compilation proceeds **without alias resolution**.
|
|
||||||
/// Field references that correspond to Azure resource provider aliases
|
|
||||||
/// (e.g. `Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly`) will
|
|
||||||
/// be compiled as raw property paths rather than being resolved to their
|
|
||||||
/// short forms. This means:
|
|
||||||
///
|
|
||||||
/// - Policies that rely on aliases will **silently produce incorrect
|
|
||||||
/// evaluation results** because the field paths won't match the
|
|
||||||
/// normalized resource structure.
|
|
||||||
/// - **Modify / Append** effect policies will **skip the modifiability
|
|
||||||
/// validation** that normally rejects writes to non-modifiable aliases
|
|
||||||
/// at compile time.
|
|
||||||
///
|
|
||||||
/// Pass null only when the policy is known to contain no alias references
|
|
||||||
/// (e.g. simple `type` / `location` checks, or in unit-test scenarios).
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// Returns a `RegorusResult` containing a `RegorusProgram` pointer on success.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// `policy_rule_json` must be a valid null-terminated UTF-8 string.
|
|
||||||
/// If `registry` is non-null it must be a valid `RegorusAliasRegistry` pointer.
|
|
||||||
/// The caller must eventually call `regorus_program_drop` on the returned handle.
|
|
||||||
#[cfg(all(feature = "azure_policy", feature = "rvm"))]
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn regorus_compile_azure_policy_rule(
|
|
||||||
registry: *const crate::alias_registry::RegorusAliasRegistry,
|
|
||||||
policy_rule_json: *const c_char,
|
|
||||||
) -> RegorusResult {
|
|
||||||
use crate::alias_registry::RegorusAliasRegistry;
|
|
||||||
use crate::rvm::RegorusProgram;
|
|
||||||
use alloc::sync::Arc;
|
|
||||||
use regorus::languages::azure_policy::{compiler, parser};
|
|
||||||
use regorus::Rc;
|
|
||||||
use regorus::Source;
|
|
||||||
|
|
||||||
with_unwind_guard(|| {
|
|
||||||
let result = || -> Result<RegorusProgram, (RegorusStatus, alloc::string::String)> {
|
|
||||||
let json_str = from_c_str(policy_rule_json).map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::InvalidDataFormat,
|
|
||||||
format!("Invalid policy rule JSON string: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let source = Source::from_contents("policy_rule".into(), json_str).map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::InvalidDataFormat,
|
|
||||||
format!("Failed to create source: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let ast = parser::parse_policy_rule(&source).map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::InvalidPolicy,
|
|
||||||
format!("Failed to parse policy rule: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let program = if registry.is_null() {
|
|
||||||
compiler::compile_policy_rule(&ast)
|
|
||||||
} else {
|
|
||||||
let reg: &RegorusAliasRegistry = to_shared_ref(registry).map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::InvalidArgument,
|
|
||||||
format!("Invalid alias registry: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
compiler::compile_policy_rule_with_aliases(&ast, reg.inner())
|
|
||||||
};
|
|
||||||
|
|
||||||
program
|
|
||||||
.map(|p| RegorusProgram {
|
|
||||||
program: Arc::new(Rc::try_unwrap(p).unwrap_or_else(|rc| (*rc).clone())),
|
|
||||||
})
|
|
||||||
.map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::CompilationFailed,
|
|
||||||
format!("Failed to compile policy rule: {e}"),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}();
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(program) => {
|
|
||||||
RegorusResult::ok_pointer(Box::into_raw(Box::new(program)) as *mut c_void)
|
|
||||||
}
|
|
||||||
Err((status, msg)) => RegorusResult::err_with_message(status, msg),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compile a full Azure Policy definition JSON into an RVM program.
|
|
||||||
///
|
|
||||||
/// Parses the JSON policy definition (which includes `policyRule`, `parameters`,
|
|
||||||
/// `displayName`, etc.), resolves aliases using the provided registry, and
|
|
||||||
/// compiles the result into an RVM [`Program`].
|
|
||||||
///
|
|
||||||
/// The definition JSON may be in either wrapped or unwrapped form:
|
|
||||||
/// - **Wrapped**: `{ "properties": { "policyRule": ..., "parameters": ... }, "id": ... }`
|
|
||||||
/// - **Unwrapped**: `{ "policyRule": ..., "parameters": ..., "displayName": ... }`
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// * `registry` - Alias registry handle, or null.
|
|
||||||
/// * `policy_definition_json` - JSON string containing the full policy definition
|
|
||||||
///
|
|
||||||
/// # Null registry behavior
|
|
||||||
///
|
|
||||||
/// When `registry` is null, compilation proceeds **without alias resolution**.
|
|
||||||
/// Field references that correspond to Azure resource provider aliases will
|
|
||||||
/// be compiled as raw property paths rather than being resolved. This means:
|
|
||||||
///
|
|
||||||
/// - Policies that rely on aliases will **silently produce incorrect
|
|
||||||
/// evaluation results**.
|
|
||||||
/// - **Modify / Append** effect policies will **skip the modifiability
|
|
||||||
/// validation** that normally rejects writes to non-modifiable aliases
|
|
||||||
/// at compile time.
|
|
||||||
///
|
|
||||||
/// Pass null only when the policy is known to contain no alias references
|
|
||||||
/// (e.g. simple `type` / `location` checks, or in unit-test scenarios).
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// Returns a `RegorusResult` containing a `RegorusProgram` pointer on success.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// `policy_definition_json` must be a valid null-terminated UTF-8 string.
|
|
||||||
/// If `registry` is non-null it must be a valid `RegorusAliasRegistry` pointer.
|
|
||||||
/// The caller must eventually call `regorus_program_drop` on the returned handle.
|
|
||||||
#[cfg(all(feature = "azure_policy", feature = "rvm"))]
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn regorus_compile_azure_policy_definition(
|
|
||||||
registry: *const crate::alias_registry::RegorusAliasRegistry,
|
|
||||||
policy_definition_json: *const c_char,
|
|
||||||
) -> RegorusResult {
|
|
||||||
use crate::alias_registry::RegorusAliasRegistry;
|
|
||||||
use crate::rvm::RegorusProgram;
|
|
||||||
use alloc::sync::Arc;
|
|
||||||
use regorus::languages::azure_policy::{compiler, parser};
|
|
||||||
use regorus::Rc;
|
|
||||||
use regorus::Source;
|
|
||||||
|
|
||||||
with_unwind_guard(|| {
|
|
||||||
let result = || -> Result<RegorusProgram, (RegorusStatus, alloc::string::String)> {
|
|
||||||
let json_str = from_c_str(policy_definition_json).map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::InvalidDataFormat,
|
|
||||||
format!("Invalid policy definition JSON string: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let source =
|
|
||||||
Source::from_contents("policy_definition".into(), json_str).map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::InvalidDataFormat,
|
|
||||||
format!("Failed to create source: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let defn = parser::parse_policy_definition(&source).map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::InvalidPolicy,
|
|
||||||
format!("Failed to parse policy definition: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let program = if registry.is_null() {
|
|
||||||
compiler::compile_policy_definition(&defn)
|
|
||||||
} else {
|
|
||||||
let reg: &RegorusAliasRegistry = to_shared_ref(registry).map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::InvalidArgument,
|
|
||||||
format!("Invalid alias registry: {e}"),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
compiler::compile_policy_definition_with_aliases(&defn, reg.inner())
|
|
||||||
};
|
|
||||||
|
|
||||||
program
|
|
||||||
.map(|p| RegorusProgram {
|
|
||||||
program: Arc::new(Rc::try_unwrap(p).unwrap_or_else(|rc| (*rc).clone())),
|
|
||||||
})
|
|
||||||
.map_err(|e| {
|
|
||||||
(
|
|
||||||
RegorusStatus::CompilationFailed,
|
|
||||||
format!("Failed to compile policy definition: {e}"),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}();
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(program) => {
|
|
||||||
RegorusResult::ok_pointer(Box::into_raw(Box::new(program)) as *mut c_void)
|
|
||||||
}
|
|
||||||
Err((status, msg)) => RegorusResult::err_with_message(status, msg),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
fn report_module_error(index: usize, kind: &str, err: &anyhow::Error) {
|
fn report_module_error(index: usize, kind: &str, err: &anyhow::Error) {
|
||||||
eprintln!("Invalid {} at index {}: {}", kind, index, err);
|
eprintln!("Invalid {} at index {}: {}", kind, index, err);
|
||||||
@@ -429,402 +215,3 @@ fn report_module_error(index: usize, kind: &str, err: &anyhow::Error) {
|
|||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
fn report_module_error(_index: usize, _kind: &str, _err: &anyhow::Error) {}
|
fn report_module_error(_index: usize, _kind: &str, _err: &anyhow::Error) {}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Tests
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::common::regorus_result_drop;
|
|
||||||
use core::ffi::CStr;
|
|
||||||
use std::ffi::CString;
|
|
||||||
|
|
||||||
fn c(s: &str) -> CString {
|
|
||||||
CString::new(s).expect("CString::new failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_ok_pointer(r: &RegorusResult) -> *mut c_void {
|
|
||||||
assert_eq!(
|
|
||||||
r.status,
|
|
||||||
RegorusStatus::Ok,
|
|
||||||
"expected Ok, got {:?}",
|
|
||||||
r.status
|
|
||||||
);
|
|
||||||
assert!(!r.pointer_value.is_null(), "expected non-null pointer");
|
|
||||||
r.pointer_value
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "azure_policy", feature = "rvm"))]
|
|
||||||
mod azure_policy_json {
|
|
||||||
use super::*;
|
|
||||||
use crate::alias_registry::regorus_alias_registry_drop;
|
|
||||||
use crate::rvm::{
|
|
||||||
regorus_program_drop, regorus_rvm_drop, regorus_rvm_execute_entry_point_by_name,
|
|
||||||
regorus_rvm_load_program, regorus_rvm_new, regorus_rvm_set_context,
|
|
||||||
regorus_rvm_set_input, RegorusProgram,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ALIASES: &str = r#"[{
|
|
||||||
"namespace": "Microsoft.Storage",
|
|
||||||
"resourceTypes": [{
|
|
||||||
"resourceType": "storageAccounts",
|
|
||||||
"aliases": [{
|
|
||||||
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
|
|
||||||
"defaultPath": "properties.supportsHttpsTrafficOnly",
|
|
||||||
"paths": []
|
|
||||||
}, {
|
|
||||||
"name": "Microsoft.Storage/storageAccounts/minimumTlsVersion",
|
|
||||||
"defaultPath": "properties.minimumTlsVersion",
|
|
||||||
"paths": []
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
}]"#;
|
|
||||||
|
|
||||||
const SIMPLE_POLICY_RULE: &str = r#"{
|
|
||||||
"if": {
|
|
||||||
"field": "type",
|
|
||||||
"equals": "Microsoft.Storage/storageAccounts"
|
|
||||||
},
|
|
||||||
"then": { "effect": "audit" }
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
const ALIAS_POLICY_RULE: &str = r#"{
|
|
||||||
"if": {
|
|
||||||
"allOf": [
|
|
||||||
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
|
|
||||||
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"then": { "effect": "deny" }
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
const POLICY_DEFINITION: &str = r#"{
|
|
||||||
"displayName": "Require HTTPS for storage accounts",
|
|
||||||
"policyType": "Custom",
|
|
||||||
"mode": "Indexed",
|
|
||||||
"parameters": {
|
|
||||||
"effect": {
|
|
||||||
"type": "String",
|
|
||||||
"defaultValue": "deny"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"policyRule": {
|
|
||||||
"if": {
|
|
||||||
"allOf": [
|
|
||||||
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
|
|
||||||
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"then": { "effect": "[parameters('effect')]" }
|
|
||||||
}
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
/// Wrap a normalized resource JSON into the input envelope expected by
|
|
||||||
/// the compiled Azure Policy RVM program.
|
|
||||||
fn wrap_input(resource_json: &str, parameters_json: &str) -> String {
|
|
||||||
format!(r#"{{"resource": {resource_json}, "parameters": {parameters_json}}}"#)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_registry_with_json(
|
|
||||||
json: &str,
|
|
||||||
) -> *mut crate::alias_registry::RegorusAliasRegistry {
|
|
||||||
let builder = crate::alias_registry::regorus_alias_registry_builder_new();
|
|
||||||
let json_c = c(json);
|
|
||||||
let r = crate::alias_registry::regorus_alias_registry_builder_load_json(
|
|
||||||
builder,
|
|
||||||
json_c.as_ptr(),
|
|
||||||
);
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let r = crate::alias_registry::regorus_alias_registry_builder_build(builder);
|
|
||||||
let registry =
|
|
||||||
assert_ok_pointer(&r) as *mut crate::alias_registry::RegorusAliasRegistry;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
crate::alias_registry::regorus_alias_registry_builder_drop(builder);
|
|
||||||
registry
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper: compile a policy rule, execute it with input, and return the
|
|
||||||
/// result string.
|
|
||||||
unsafe fn compile_and_eval_rule(
|
|
||||||
registry: *const crate::alias_registry::RegorusAliasRegistry,
|
|
||||||
policy_rule: &str,
|
|
||||||
input_json: &str,
|
|
||||||
) -> String {
|
|
||||||
let rule_c = c(policy_rule);
|
|
||||||
let r = regorus_compile_azure_policy_rule(registry, rule_c.as_ptr());
|
|
||||||
let program_ptr = assert_ok_pointer(&r) as *mut RegorusProgram;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let vm = regorus_rvm_new();
|
|
||||||
assert!(!vm.is_null());
|
|
||||||
|
|
||||||
let r = regorus_rvm_load_program(vm, program_ptr);
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let input_c = c(input_json);
|
|
||||||
let r = regorus_rvm_set_input(vm, input_c.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let entry = c("main");
|
|
||||||
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok, "execute failed");
|
|
||||||
let output = CStr::from_ptr(r.output)
|
|
||||||
.to_str()
|
|
||||||
.expect("invalid UTF-8")
|
|
||||||
.to_string();
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
regorus_rvm_drop(vm);
|
|
||||||
regorus_program_drop(program_ptr);
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn compile_simple_rule_no_aliases() {
|
|
||||||
let rule_c = c(SIMPLE_POLICY_RULE);
|
|
||||||
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), rule_c.as_ptr());
|
|
||||||
let ptr = assert_ok_pointer(&r);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
regorus_program_drop(ptr as *mut RegorusProgram);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn compile_rule_with_aliases() {
|
|
||||||
let reg = build_registry_with_json(ALIASES);
|
|
||||||
|
|
||||||
let rule_c = c(ALIAS_POLICY_RULE);
|
|
||||||
let r = regorus_compile_azure_policy_rule(reg, rule_c.as_ptr());
|
|
||||||
let ptr = assert_ok_pointer(&r);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
regorus_program_drop(ptr as *mut RegorusProgram);
|
|
||||||
regorus_alias_registry_drop(reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn compile_and_eval_simple_rule_matching() {
|
|
||||||
let input = wrap_input(r#"{"type":"microsoft.storage/storageaccounts"}"#, "{}");
|
|
||||||
let result =
|
|
||||||
unsafe { compile_and_eval_rule(core::ptr::null_mut(), SIMPLE_POLICY_RULE, &input) };
|
|
||||||
let parsed: serde_json::Value =
|
|
||||||
serde_json::from_str(&result).expect("result should be valid JSON");
|
|
||||||
assert_eq!(
|
|
||||||
parsed["effect"], "audit",
|
|
||||||
"expected audit effect, got: {result}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn compile_and_eval_simple_rule_not_matching() {
|
|
||||||
let input = wrap_input(r#"{"type":"microsoft.compute/virtualmachines"}"#, "{}");
|
|
||||||
let result =
|
|
||||||
unsafe { compile_and_eval_rule(core::ptr::null_mut(), SIMPLE_POLICY_RULE, &input) };
|
|
||||||
// When the "if" condition doesn't match, the result should be undefined
|
|
||||||
assert!(
|
|
||||||
result.contains("undefined"),
|
|
||||||
"expected undefined for non-matching input, got: {result}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn compile_and_eval_alias_rule_deny() {
|
|
||||||
let reg = build_registry_with_json(ALIASES);
|
|
||||||
|
|
||||||
// Non-compliant resource: HTTPS not enabled (normalized form)
|
|
||||||
let input = wrap_input(
|
|
||||||
r#"{"type": "microsoft.storage/storageaccounts", "supportshttpstrafficonly": false}"#,
|
|
||||||
"{}",
|
|
||||||
);
|
|
||||||
let result = unsafe { compile_and_eval_rule(reg, ALIAS_POLICY_RULE, &input) };
|
|
||||||
let parsed: serde_json::Value = serde_json::from_str(&result).expect("valid JSON");
|
|
||||||
assert_eq!(parsed["effect"], "deny", "expected deny, got: {result}");
|
|
||||||
|
|
||||||
regorus_alias_registry_drop(reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn compile_and_eval_alias_rule_compliant() {
|
|
||||||
let reg = build_registry_with_json(ALIASES);
|
|
||||||
|
|
||||||
// Compliant resource: HTTPS enabled (normalized form)
|
|
||||||
let input = wrap_input(
|
|
||||||
r#"{"type": "microsoft.storage/storageaccounts", "supportshttpstrafficonly": true}"#,
|
|
||||||
"{}",
|
|
||||||
);
|
|
||||||
let result = unsafe { compile_and_eval_rule(reg, ALIAS_POLICY_RULE, &input) };
|
|
||||||
assert!(
|
|
||||||
result.contains("undefined"),
|
|
||||||
"expected undefined for compliant resource, got: {result}"
|
|
||||||
);
|
|
||||||
|
|
||||||
regorus_alias_registry_drop(reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn compile_definition_no_aliases() {
|
|
||||||
let defn_c = c(POLICY_DEFINITION);
|
|
||||||
let r = regorus_compile_azure_policy_definition(core::ptr::null_mut(), defn_c.as_ptr());
|
|
||||||
let ptr = assert_ok_pointer(&r);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
regorus_program_drop(ptr as *mut RegorusProgram);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn compile_definition_with_aliases_and_eval() {
|
|
||||||
let reg = build_registry_with_json(ALIASES);
|
|
||||||
|
|
||||||
let defn_c = c(POLICY_DEFINITION);
|
|
||||||
let r = regorus_compile_azure_policy_definition(reg, defn_c.as_ptr());
|
|
||||||
let program_ptr = assert_ok_pointer(&r) as *mut RegorusProgram;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
// Evaluate with a non-compliant resource (normalized form, wrapped in envelope)
|
|
||||||
unsafe {
|
|
||||||
let vm = regorus_rvm_new();
|
|
||||||
let r = regorus_rvm_load_program(vm, program_ptr);
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let input_json = wrap_input(
|
|
||||||
r#"{"type": "microsoft.storage/storageaccounts", "supportshttpstrafficonly": false}"#,
|
|
||||||
"{}",
|
|
||||||
);
|
|
||||||
let input = c(&input_json);
|
|
||||||
let r = regorus_rvm_set_input(vm, input.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let entry = c("main");
|
|
||||||
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
let result = CStr::from_ptr(r.output)
|
|
||||||
.to_str()
|
|
||||||
.expect("UTF-8")
|
|
||||||
.to_string();
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
|
|
||||||
// The default parameter value is "deny"
|
|
||||||
assert_eq!(parsed["effect"], "deny", "got: {result}");
|
|
||||||
|
|
||||||
regorus_rvm_drop(vm);
|
|
||||||
regorus_program_drop(program_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
regorus_alias_registry_drop(reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_json_returns_error() {
|
|
||||||
let bad = c("not valid json");
|
|
||||||
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), bad.as_ptr());
|
|
||||||
assert_ne!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_definition_returns_error() {
|
|
||||||
let bad = c(r#"{"not": "a policy definition"}"#);
|
|
||||||
let r = regorus_compile_azure_policy_definition(core::ptr::null_mut(), bad.as_ptr());
|
|
||||||
assert_ne!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Policy rule that uses a context function (subscription()).
|
|
||||||
const CONTEXT_POLICY_RULE: &str = r#"{
|
|
||||||
"if": {
|
|
||||||
"allOf": [
|
|
||||||
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
|
|
||||||
{ "value": "[subscription().subscriptionId]", "equals": "sub-123" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"then": { "effect": "deny" }
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn context_policy_evaluates_with_set_context() {
|
|
||||||
let rule_c = c(CONTEXT_POLICY_RULE);
|
|
||||||
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), rule_c.as_ptr());
|
|
||||||
let program = assert_ok_pointer(&r) as *mut RegorusProgram;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let vm = regorus_rvm_new();
|
|
||||||
assert!(!vm.is_null());
|
|
||||||
|
|
||||||
let r = regorus_rvm_load_program(vm, program);
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
// Set the context with subscription info
|
|
||||||
let context = c(r#"{"subscription": {"subscriptionId": "sub-123"}}"#);
|
|
||||||
let r = regorus_rvm_set_context(vm, context.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
// Set matching input
|
|
||||||
let input = c(&wrap_input(
|
|
||||||
r#"{"type": "microsoft.storage/storageaccounts"}"#,
|
|
||||||
"{}",
|
|
||||||
));
|
|
||||||
let r = regorus_rvm_set_input(vm, input.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let entry = c("main");
|
|
||||||
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
let output = unsafe { CStr::from_ptr(r.output) }.to_str().unwrap();
|
|
||||||
assert!(
|
|
||||||
output.contains("deny"),
|
|
||||||
"expected deny effect with matching context, got: {output}"
|
|
||||||
);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
regorus_rvm_drop(vm);
|
|
||||||
regorus_program_drop(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn context_policy_undefined_without_context() {
|
|
||||||
let rule_c = c(CONTEXT_POLICY_RULE);
|
|
||||||
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), rule_c.as_ptr());
|
|
||||||
let program = assert_ok_pointer(&r) as *mut RegorusProgram;
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let vm = regorus_rvm_new();
|
|
||||||
assert!(!vm.is_null());
|
|
||||||
|
|
||||||
let r = regorus_rvm_load_program(vm, program);
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
// No context set — subscription() will be undefined
|
|
||||||
let input = c(&wrap_input(
|
|
||||||
r#"{"type": "microsoft.storage/storageaccounts"}"#,
|
|
||||||
"{}",
|
|
||||||
));
|
|
||||||
let r = regorus_rvm_set_input(vm, input.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
let entry = c("main");
|
|
||||||
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
|
|
||||||
assert_eq!(r.status, RegorusStatus::Ok);
|
|
||||||
let output = unsafe { CStr::from_ptr(r.output) }.to_str().unwrap();
|
|
||||||
assert!(
|
|
||||||
output.contains("undefined"),
|
|
||||||
"expected undefined without context, got: {output}"
|
|
||||||
);
|
|
||||||
regorus_result_drop(r);
|
|
||||||
|
|
||||||
regorus_rvm_drop(vm);
|
|
||||||
regorus_program_drop(program);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub extern "C" fn regorus_compiled_policy_eval_with_input(
|
|||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let input_value = regorus::Value::from_json_str(&from_c_str(input)?)?;
|
let input_value = regorus::Value::from_json_str(&from_c_str(input)?)?;
|
||||||
let result = to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?
|
let result = to_ref(compiled_policy)?
|
||||||
.compiled_policy
|
.compiled_policy
|
||||||
.eval_with_input(input_value)?;
|
.eval_with_input(input_value)?;
|
||||||
result.to_json_str()
|
result.to_json_str()
|
||||||
@@ -65,9 +65,7 @@ pub extern "C" fn regorus_compiled_policy_get_policy_info(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let info = to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?
|
let info = to_ref(compiled_policy)?.compiled_policy.get_policy_info()?;
|
||||||
.compiled_policy
|
|
||||||
.get_policy_info()?;
|
|
||||||
serde_json::to_string(&info)
|
serde_json::to_string(&info)
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to serialize policy info: {}", e))
|
.map_err(|e| anyhow::anyhow!("Failed to serialize policy info: {}", e))
|
||||||
}();
|
}();
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
from_c_str, to_ref, to_regorus_result, to_regorus_string_result, to_shared_ref, RegorusResult,
|
from_c_str, to_ref, to_regorus_result, to_regorus_string_result, RegorusResult, RegorusStatus,
|
||||||
RegorusStatus,
|
|
||||||
};
|
};
|
||||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||||
use crate::limits::RegorusExecutionTimerConfig;
|
use crate::limits::RegorusExecutionTimerConfig;
|
||||||
@@ -194,12 +193,27 @@ pub extern "C" fn regorus_engine_new() -> *mut RegorusEngine {
|
|||||||
///
|
///
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_engine_clone(engine: *mut RegorusEngine) -> *mut RegorusEngine {
|
pub extern "C" fn regorus_engine_clone(engine: *mut RegorusEngine) -> *mut RegorusEngine {
|
||||||
match to_shared_ref(engine as *const RegorusEngine) {
|
match to_ref(engine) {
|
||||||
Ok(e) => Box::into_raw(Box::new(e.clone())),
|
Ok(e) => Box::into_raw(Box::new(e.clone())),
|
||||||
_ => ptr::null_mut(),
|
_ => ptr::null_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare a [`RegorusEngine`] for evaluation without executing a query.
|
||||||
|
///
|
||||||
|
/// This is optional. If not called, first eval performs the same setup.
|
||||||
|
/// If policy/data changes after preparation, setup is invalidated.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn regorus_engine_prepare(engine: *mut RegorusEngine) -> RegorusResult {
|
||||||
|
with_unwind_guard(|| {
|
||||||
|
to_regorus_result(|| -> Result<()> {
|
||||||
|
let engine = to_ref(engine)?;
|
||||||
|
let mut guard = engine.try_write()?;
|
||||||
|
guard.prepare()
|
||||||
|
}())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_engine_drop(engine: *mut RegorusEngine) {
|
pub extern "C" fn regorus_engine_drop(engine: *mut RegorusEngine) {
|
||||||
if let Ok(e) = to_ref(engine) {
|
if let Ok(e) = to_ref(engine) {
|
||||||
@@ -224,7 +238,7 @@ pub extern "C" fn regorus_engine_add_policy(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_string_result(|| -> Result<String> {
|
to_regorus_string_result(|| -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.add_policy(from_c_str(path)?, from_c_str(rego)?)
|
guard.add_policy(from_c_str(path)?, from_c_str(rego)?)
|
||||||
}())
|
}())
|
||||||
@@ -239,7 +253,7 @@ pub extern "C" fn regorus_engine_add_policy_from_file(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_string_result(|| -> Result<String> {
|
to_regorus_string_result(|| -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.add_policy_from_file(from_c_str(path)?)
|
guard.add_policy_from_file(from_c_str(path)?)
|
||||||
}())
|
}())
|
||||||
@@ -257,7 +271,7 @@ pub extern "C" fn regorus_engine_add_data_json(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
|
guard.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
|
||||||
}())
|
}())
|
||||||
@@ -271,7 +285,7 @@ pub extern "C" fn regorus_engine_add_data_json(
|
|||||||
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
|
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_string_result(|| -> Result<String> {
|
to_regorus_string_result(|| -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let guard = engine.try_read()?;
|
let guard = engine.try_read()?;
|
||||||
serde_json::to_string_pretty(&guard.get_packages()?).map_err(anyhow::Error::msg)
|
serde_json::to_string_pretty(&guard.get_packages()?).map_err(anyhow::Error::msg)
|
||||||
}())
|
}())
|
||||||
@@ -285,7 +299,7 @@ pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> Reg
|
|||||||
pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult {
|
pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_string_result(|| -> Result<String> {
|
to_regorus_string_result(|| -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let guard = engine.try_read()?;
|
let guard = engine.try_read()?;
|
||||||
guard.get_policies_as_json()
|
guard.get_policies_as_json()
|
||||||
}())
|
}())
|
||||||
@@ -300,7 +314,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.add_data(regorus::Value::from_json_file(from_c_str(path)?)?)
|
guard.add_data(regorus::Value::from_json_file(from_c_str(path)?)?)
|
||||||
}())
|
}())
|
||||||
@@ -314,7 +328,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
|
|||||||
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
|
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.clear_data();
|
guard.clear_data();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -333,7 +347,7 @@ pub extern "C" fn regorus_engine_set_input_json(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
|
guard.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -349,7 +363,7 @@ pub extern "C" fn regorus_engine_set_input_from_json_file(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.set_input(regorus::Value::from_json_file(from_c_str(path)?)?);
|
guard.set_input(regorus::Value::from_json_file(from_c_str(path)?)?);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -368,7 +382,7 @@ pub extern "C" fn regorus_engine_eval_query(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
let results = guard.eval_query(from_c_str(query)?, false)?;
|
let results = guard.eval_query(from_c_str(query)?, false)?;
|
||||||
Ok(serde_json::to_string_pretty(&results)?)
|
Ok(serde_json::to_string_pretty(&results)?)
|
||||||
@@ -391,7 +405,7 @@ pub extern "C" fn regorus_engine_eval_rule(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.eval_rule(from_c_str(rule)?)?.to_json_str()
|
guard.eval_rule(from_c_str(rule)?)?.to_json_str()
|
||||||
}();
|
}();
|
||||||
@@ -414,7 +428,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.set_enable_coverage(enable);
|
guard.set_enable_coverage(enable);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -430,7 +444,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
|
|||||||
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult {
|
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let guard = engine.try_read()?;
|
let guard = engine.try_read()?;
|
||||||
Ok(serde_json::to_string_pretty(&guard.get_coverage_report()?)?)
|
Ok(serde_json::to_string_pretty(&guard.get_coverage_report()?)?)
|
||||||
}();
|
}();
|
||||||
@@ -452,7 +466,7 @@ pub extern "C" fn regorus_engine_set_strict_builtin_errors(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.set_strict_builtin_errors(strict);
|
guard.set_strict_builtin_errors(strict);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -466,20 +480,18 @@ pub extern "C" fn regorus_engine_set_execution_timer_config(
|
|||||||
engine: *mut RegorusEngine,
|
engine: *mut RegorusEngine,
|
||||||
config: *const RegorusExecutionTimerConfig,
|
config: *const RegorusExecutionTimerConfig,
|
||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
to_regorus_result(|| -> Result<()> {
|
||||||
to_regorus_result(|| -> Result<()> {
|
let engine = to_ref(engine)?;
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let config = unsafe {
|
||||||
let config = unsafe {
|
config
|
||||||
config
|
.as_ref()
|
||||||
.as_ref()
|
.copied()
|
||||||
.copied()
|
.ok_or_else(|| anyhow!("execution timer config pointer is null"))?
|
||||||
.ok_or_else(|| anyhow!("execution timer config pointer is null"))?
|
};
|
||||||
};
|
let mut guard = engine.try_write()?;
|
||||||
let mut guard = engine.try_write()?;
|
guard.set_execution_timer_config(config.to_execution_timer_config()?);
|
||||||
guard.set_execution_timer_config(config.to_execution_timer_config()?);
|
Ok(())
|
||||||
Ok(())
|
}())
|
||||||
}())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -487,14 +499,12 @@ pub extern "C" fn regorus_engine_set_execution_timer_config(
|
|||||||
pub extern "C" fn regorus_engine_clear_execution_timer_config(
|
pub extern "C" fn regorus_engine_clear_execution_timer_config(
|
||||||
engine: *mut RegorusEngine,
|
engine: *mut RegorusEngine,
|
||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
to_regorus_result(|| -> Result<()> {
|
||||||
to_regorus_result(|| -> Result<()> {
|
let engine = to_ref(engine)?;
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let mut guard = engine.try_write()?;
|
||||||
let mut guard = engine.try_write()?;
|
guard.clear_execution_timer_config();
|
||||||
guard.clear_execution_timer_config();
|
Ok(())
|
||||||
Ok(())
|
}())
|
||||||
}())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the policy length limits used when loading policies.
|
/// Set the policy length limits used when loading policies.
|
||||||
@@ -505,7 +515,7 @@ pub extern "C" fn regorus_engine_set_policy_length_config(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.set_policy_length_config(config.to_policy_length_config()?);
|
guard.set_policy_length_config(config.to_policy_length_config()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -520,7 +530,7 @@ pub extern "C" fn regorus_engine_clear_policy_length_config(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.clear_policy_length_config();
|
guard.clear_policy_length_config();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -538,7 +548,7 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let guard = engine.try_read()?;
|
let guard = engine.try_read()?;
|
||||||
guard.get_coverage_report()?.to_string_pretty()
|
guard.get_coverage_report()?.to_string_pretty()
|
||||||
}();
|
}();
|
||||||
@@ -557,7 +567,7 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
|
|||||||
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult {
|
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.clear_coverage_data();
|
guard.clear_coverage_data();
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -576,7 +586,7 @@ pub extern "C" fn regorus_engine_set_gather_prints(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.set_gather_prints(enable);
|
guard.set_gather_prints(enable);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -591,7 +601,7 @@ pub extern "C" fn regorus_engine_set_gather_prints(
|
|||||||
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
|
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
Ok(serde_json::to_string_pretty(&guard.take_prints()?)?)
|
Ok(serde_json::to_string_pretty(&guard.take_prints()?)?)
|
||||||
}();
|
}();
|
||||||
@@ -610,7 +620,7 @@ pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> Rego
|
|||||||
pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> RegorusResult {
|
pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let guard = engine.try_read()?;
|
let guard = engine.try_read()?;
|
||||||
guard.get_ast_as_json()
|
guard.get_ast_as_json()
|
||||||
}();
|
}();
|
||||||
@@ -631,7 +641,7 @@ pub extern "C" fn regorus_engine_get_policy_package_names(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let guard = engine.try_read()?;
|
let guard = engine.try_read()?;
|
||||||
serde_json::to_string_pretty(&guard.get_policy_package_names()?)
|
serde_json::to_string_pretty(&guard.get_policy_package_names()?)
|
||||||
.map_err(anyhow::Error::msg)
|
.map_err(anyhow::Error::msg)
|
||||||
@@ -653,7 +663,7 @@ pub extern "C" fn regorus_engine_get_policy_parameters(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let guard = engine.try_read()?;
|
let guard = engine.try_read()?;
|
||||||
serde_json::to_string_pretty(&guard.get_policy_parameters()?)
|
serde_json::to_string_pretty(&guard.get_policy_parameters()?)
|
||||||
.map_err(anyhow::Error::msg)
|
.map_err(anyhow::Error::msg)
|
||||||
@@ -675,7 +685,7 @@ pub extern "C" fn regorus_engine_set_rego_v0(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<()> {
|
let output = || -> Result<()> {
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
guard.set_rego_v0(enable);
|
guard.set_rego_v0(enable);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -697,7 +707,7 @@ pub extern "C" fn regorus_engine_set_rego_v0(
|
|||||||
#[cfg(feature = "azure_policy")]
|
#[cfg(feature = "azure_policy")]
|
||||||
pub extern "C" fn regorus_engine_compile_for_target(engine: *mut RegorusEngine) -> RegorusResult {
|
pub extern "C" fn regorus_engine_compile_for_target(engine: *mut RegorusEngine) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let engine = match to_shared_ref(engine as *const RegorusEngine) {
|
let engine = match to_ref(engine) {
|
||||||
Ok(engine) => engine,
|
Ok(engine) => engine,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return RegorusResult::err_with_message(
|
return RegorusResult::err_with_message(
|
||||||
@@ -746,7 +756,7 @@ pub extern "C" fn regorus_engine_compile_with_entrypoint(
|
|||||||
let result = || -> Result<RegorusCompiledPolicy> {
|
let result = || -> Result<RegorusCompiledPolicy> {
|
||||||
let rule_str = from_c_str(rule)?;
|
let rule_str = from_c_str(rule)?;
|
||||||
let rule_rc: regorus::Rc<str> = rule_str.into();
|
let rule_rc: regorus::Rc<str> = rule_str.into();
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||||
Ok(RegorusCompiledPolicy { compiled_policy })
|
Ok(RegorusCompiledPolicy { compiled_policy })
|
||||||
@@ -805,7 +815,7 @@ pub extern "C" fn regorus_engine_compile_program_with_entrypoints(
|
|||||||
.ok_or_else(|| anyhow!("entry_points must contain at least one entry"))?;
|
.ok_or_else(|| anyhow!("entry_points must contain at least one entry"))?;
|
||||||
let rule_rc: regorus::Rc<str> = (*rule).into();
|
let rule_rc: regorus::Rc<str> = (*rule).into();
|
||||||
|
|
||||||
let engine = to_shared_ref(engine as *const RegorusEngine)?;
|
let engine = to_ref(engine)?;
|
||||||
let mut guard = engine.try_write()?;
|
let mut guard = engine.try_write()?;
|
||||||
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
from_c_str, to_ref, to_regorus_result, to_shared_ref, RegorusBuffer, RegorusResult,
|
from_c_str, to_ref, to_regorus_result, RegorusBuffer, RegorusResult, RegorusStatus,
|
||||||
RegorusStatus,
|
|
||||||
};
|
};
|
||||||
use crate::compile::RegorusPolicyModule;
|
use crate::compile::RegorusPolicyModule;
|
||||||
use crate::compiled_policy::RegorusCompiledPolicy;
|
use crate::compiled_policy::RegorusCompiledPolicy;
|
||||||
@@ -107,8 +106,7 @@ pub extern "C" fn regorus_program_compile_from_policy(
|
|||||||
|
|
||||||
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
|
||||||
|
|
||||||
let compiled_policy =
|
let compiled_policy = &to_ref(compiled_policy)?.compiled_policy;
|
||||||
&to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?.compiled_policy;
|
|
||||||
let program = Compiler::compile_from_policy(compiled_policy, &entry_points_ref)?;
|
let program = Compiler::compile_from_policy(compiled_policy, &entry_points_ref)?;
|
||||||
Ok(Box::into_raw(Box::new(RegorusProgram { program })))
|
Ok(Box::into_raw(Box::new(RegorusProgram { program })))
|
||||||
}();
|
}();
|
||||||
@@ -189,7 +187,7 @@ pub extern "C" fn regorus_program_new() -> *mut RegorusProgram {
|
|||||||
pub extern "C" fn regorus_program_serialize_binary(program: *mut RegorusProgram) -> RegorusResult {
|
pub extern "C" fn regorus_program_serialize_binary(program: *mut RegorusProgram) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<*mut RegorusBuffer> {
|
let output = || -> Result<*mut RegorusBuffer> {
|
||||||
let program = &to_shared_ref(program as *const RegorusProgram)?.program;
|
let program = &to_ref(program)?.program;
|
||||||
let bytes = program.serialize_binary().map_err(|e| anyhow!(e))?;
|
let bytes = program.serialize_binary().map_err(|e| anyhow!(e))?;
|
||||||
Ok(RegorusBuffer::from_vec(bytes))
|
Ok(RegorusBuffer::from_vec(bytes))
|
||||||
}();
|
}();
|
||||||
@@ -213,10 +211,7 @@ pub extern "C" fn regorus_program_deserialize_binary(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<(*mut RegorusProgram, bool)> {
|
let output = || -> Result<(*mut RegorusProgram, bool)> {
|
||||||
if data.is_null() {
|
if data.is_null() && len > 0 {
|
||||||
if len > 0 {
|
|
||||||
return Err(anyhow!("null data pointer with non-zero length"));
|
|
||||||
}
|
|
||||||
return Err(anyhow!("null data pointer"));
|
return Err(anyhow!("null data pointer"));
|
||||||
}
|
}
|
||||||
let data = unsafe { core::slice::from_raw_parts(data, len) };
|
let data = unsafe { core::slice::from_raw_parts(data, len) };
|
||||||
@@ -254,7 +249,7 @@ pub extern "C" fn regorus_program_deserialize_binary(
|
|||||||
pub extern "C" fn regorus_program_generate_listing(program: *mut RegorusProgram) -> RegorusResult {
|
pub extern "C" fn regorus_program_generate_listing(program: *mut RegorusProgram) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let program = &to_shared_ref(program as *const RegorusProgram)?.program;
|
let program = &to_ref(program)?.program;
|
||||||
Ok(generate_assembly_listing(
|
Ok(generate_assembly_listing(
|
||||||
program,
|
program,
|
||||||
&AssemblyListingConfig::default(),
|
&AssemblyListingConfig::default(),
|
||||||
@@ -275,7 +270,7 @@ pub extern "C" fn regorus_program_generate_tabular_listing(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let program = &to_shared_ref(program as *const RegorusProgram)?.program;
|
let program = &to_ref(program)?.program;
|
||||||
Ok(generate_tabular_assembly_listing(
|
Ok(generate_tabular_assembly_listing(
|
||||||
program,
|
program,
|
||||||
&AssemblyListingConfig::default(),
|
&AssemblyListingConfig::default(),
|
||||||
@@ -302,9 +297,7 @@ pub extern "C" fn regorus_rvm_new_with_policy(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<*mut RegorusRvm> {
|
let output = || -> Result<*mut RegorusRvm> {
|
||||||
let policy = to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?
|
let policy = to_ref(compiled_policy)?.compiled_policy.clone();
|
||||||
.compiled_policy
|
|
||||||
.clone();
|
|
||||||
Ok(Box::into_raw(Box::new(RegorusRvm::new(
|
Ok(Box::into_raw(Box::new(RegorusRvm::new(
|
||||||
RegoVM::new_with_policy(policy),
|
RegoVM::new_with_policy(policy),
|
||||||
))))
|
))))
|
||||||
@@ -325,11 +318,9 @@ pub extern "C" fn regorus_rvm_load_program(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
let program = to_shared_ref(program as *const RegorusProgram)?
|
let program = to_ref(program)?.program.clone();
|
||||||
.program
|
|
||||||
.clone();
|
|
||||||
guard.load_program(program);
|
guard.load_program(program);
|
||||||
Ok(())
|
Ok(())
|
||||||
}())
|
}())
|
||||||
@@ -341,7 +332,7 @@ pub extern "C" fn regorus_rvm_load_program(
|
|||||||
pub extern "C" fn regorus_rvm_set_data(vm: *mut RegorusRvm, data: *const c_char) -> RegorusResult {
|
pub extern "C" fn regorus_rvm_set_data(vm: *mut RegorusRvm, data: *const c_char) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
let data_value = Value::from_json_str(&from_c_str(data)?)?;
|
let data_value = Value::from_json_str(&from_c_str(data)?)?;
|
||||||
guard.set_data(data_value)?;
|
guard.set_data(data_value)?;
|
||||||
@@ -358,7 +349,7 @@ pub extern "C" fn regorus_rvm_set_input(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
let input_value = Value::from_json_str(&from_c_str(input)?)?;
|
let input_value = Value::from_json_str(&from_c_str(input)?)?;
|
||||||
guard.set_input(input_value);
|
guard.set_input(input_value);
|
||||||
@@ -367,33 +358,6 @@ pub extern "C" fn regorus_rvm_set_input(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the VM context document from JSON.
|
|
||||||
///
|
|
||||||
/// The context provides host-supplied ambient data (e.g. `resourceGroup()`,
|
|
||||||
/// `subscription()`) that Azure Policy functions can access via `LoadContext`
|
|
||||||
/// instructions. This must be called before `regorus_rvm_execute` when
|
|
||||||
/// evaluating policies that reference context functions.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `vm` must be a valid pointer to a `RegorusRvm` created by `regorus_rvm_new`.
|
|
||||||
/// - `context_json` must be a valid null-terminated UTF-8 string.
|
|
||||||
#[cfg(feature = "azure_policy")]
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn regorus_rvm_set_context(
|
|
||||||
vm: *mut RegorusRvm,
|
|
||||||
context_json: *const c_char,
|
|
||||||
) -> RegorusResult {
|
|
||||||
with_unwind_guard(|| {
|
|
||||||
to_regorus_result(|| -> Result<()> {
|
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
|
||||||
let mut guard = vm.try_write()?;
|
|
||||||
let context_value = Value::from_json_str(&from_c_str(context_json)?)?;
|
|
||||||
guard.set_context(context_value);
|
|
||||||
Ok(())
|
|
||||||
}())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the maximum number of instructions that can execute.
|
/// Set the maximum number of instructions that can execute.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn regorus_rvm_set_max_instructions(
|
pub extern "C" fn regorus_rvm_set_max_instructions(
|
||||||
@@ -402,7 +366,7 @@ pub extern "C" fn regorus_rvm_set_max_instructions(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
guard.set_max_instructions(max_instructions);
|
guard.set_max_instructions(max_instructions);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -418,7 +382,7 @@ pub extern "C" fn regorus_rvm_set_strict_builtin_errors(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
guard.set_strict_builtin_errors(strict);
|
guard.set_strict_builtin_errors(strict);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -431,7 +395,7 @@ pub extern "C" fn regorus_rvm_set_strict_builtin_errors(
|
|||||||
pub extern "C" fn regorus_rvm_set_execution_mode(vm: *mut RegorusRvm, mode: u8) -> RegorusResult {
|
pub extern "C" fn regorus_rvm_set_execution_mode(vm: *mut RegorusRvm, mode: u8) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
let mode = match mode {
|
let mode = match mode {
|
||||||
0 => ExecutionMode::RunToCompletion,
|
0 => ExecutionMode::RunToCompletion,
|
||||||
@@ -449,7 +413,7 @@ pub extern "C" fn regorus_rvm_set_execution_mode(vm: *mut RegorusRvm, mode: u8)
|
|||||||
pub extern "C" fn regorus_rvm_set_step_mode(vm: *mut RegorusRvm, enabled: bool) -> RegorusResult {
|
pub extern "C" fn regorus_rvm_set_step_mode(vm: *mut RegorusRvm, enabled: bool) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
guard.set_step_mode(enabled);
|
guard.set_step_mode(enabled);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -466,7 +430,7 @@ pub extern "C" fn regorus_rvm_set_execution_timer_config(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
to_regorus_result(|| -> Result<()> {
|
to_regorus_result(|| -> Result<()> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
if has_config {
|
if has_config {
|
||||||
guard.set_execution_timer_config(Some(config.to_execution_timer_config()?));
|
guard.set_execution_timer_config(Some(config.to_execution_timer_config()?));
|
||||||
@@ -483,7 +447,7 @@ pub extern "C" fn regorus_rvm_set_execution_timer_config(
|
|||||||
pub extern "C" fn regorus_rvm_execute(vm: *mut RegorusRvm) -> RegorusResult {
|
pub extern "C" fn regorus_rvm_execute(vm: *mut RegorusRvm) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
let result = guard.execute()?;
|
let result = guard.execute()?;
|
||||||
result.to_json_str()
|
result.to_json_str()
|
||||||
@@ -504,7 +468,7 @@ pub extern "C" fn regorus_rvm_execute_entry_point_by_name(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
let name = from_c_str(entry_point)?;
|
let name = from_c_str(entry_point)?;
|
||||||
let result = guard.execute_entry_point_by_name(&name)?;
|
let result = guard.execute_entry_point_by_name(&name)?;
|
||||||
@@ -526,7 +490,7 @@ pub extern "C" fn regorus_rvm_execute_entry_point_by_index(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
let result = guard.execute_entry_point_by_index(index)?;
|
let result = guard.execute_entry_point_by_index(index)?;
|
||||||
result.to_json_str()
|
result.to_json_str()
|
||||||
@@ -548,7 +512,7 @@ pub extern "C" fn regorus_rvm_resume(
|
|||||||
) -> RegorusResult {
|
) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let mut guard = vm.try_write()?;
|
let mut guard = vm.try_write()?;
|
||||||
let value = if has_value {
|
let value = if has_value {
|
||||||
Some(Value::from_json_str(&from_c_str(resume_value_json)?)?)
|
Some(Value::from_json_str(&from_c_str(resume_value_json)?)?)
|
||||||
@@ -571,7 +535,7 @@ pub extern "C" fn regorus_rvm_resume(
|
|||||||
pub extern "C" fn regorus_rvm_get_execution_state(vm: *mut RegorusRvm) -> RegorusResult {
|
pub extern "C" fn regorus_rvm_get_execution_state(vm: *mut RegorusRvm) -> RegorusResult {
|
||||||
with_unwind_guard(|| {
|
with_unwind_guard(|| {
|
||||||
let output = || -> Result<String> {
|
let output = || -> Result<String> {
|
||||||
let vm = to_shared_ref(vm as *const RegorusRvm)?;
|
let vm = to_ref(vm)?;
|
||||||
let guard = vm.try_read()?;
|
let guard = vm.try_read()?;
|
||||||
let state: ExecutionState = guard.execution_state().clone();
|
let state: ExecutionState = guard.execution_state().clone();
|
||||||
Ok(format!("{:?}", state))
|
Ok(format!("{:?}", state))
|
||||||
|
|||||||
@@ -28,6 +28,17 @@ func (e *Engine) Clone() *Engine {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Engine) Prepare() error {
|
||||||
|
result := C.regorus_engine_prepare(e.e)
|
||||||
|
defer C.regorus_result_drop(result)
|
||||||
|
|
||||||
|
if result.status != C.Ok {
|
||||||
|
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Engine) SetRegoV0(enable bool) error {
|
func (e *Engine) SetRegoV0(enable bool) error {
|
||||||
result := C.regorus_engine_set_rego_v0(e.e, C.bool(enable))
|
result := C.regorus_engine_set_rego_v0(e.e, C.bool(enable))
|
||||||
defer C.regorus_result_drop(result)
|
defer C.regorus_result_drop(result)
|
||||||
|
|||||||
612
bindings/java/Cargo.lock
generated
612
bindings/java/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "regorus-java"
|
name = "regorus-java"
|
||||||
version = "0.11.0"
|
version = "0.10.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://github.com/microsoft/regorus/bindings/java"
|
repository = "https://github.com/microsoft/regorus/bindings/java"
|
||||||
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||||
@@ -21,6 +21,6 @@ cache = ["regorus/cache"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
serde_json = "1.0.150"
|
serde_json = "1.0.112"
|
||||||
jni = "0.22.4"
|
jni = "0.22.4"
|
||||||
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeNewEngine
|
|||||||
JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeClone
|
JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeClone
|
||||||
(JNIEnv *, jclass, jlong);
|
(JNIEnv *, jclass, jlong);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: com_microsoft_regorus_Engine
|
||||||
|
* Method: nativePrepare
|
||||||
|
* Signature: (J)V
|
||||||
|
*/
|
||||||
|
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativePrepare
|
||||||
|
(JNIEnv *, jclass, jlong);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class: com_microsoft_regorus_Engine
|
* Class: com_microsoft_regorus_Engine
|
||||||
* Method: nativeAddPolicy
|
* Method: nativeAddPolicy
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<groupId>com.microsoft.regorus</groupId>
|
<groupId>com.microsoft.regorus</groupId>
|
||||||
<artifactId>regorus-java</artifactId>
|
<artifactId>regorus-java</artifactId>
|
||||||
<version>0.11.0</version>
|
<version>0.10.0</version>
|
||||||
|
|
||||||
<name>Regorus Java</name>
|
<name>Regorus Java</name>
|
||||||
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>
|
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.5.6</version>
|
<version>3.5.5</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<!-- Add debug build to Java path, so it's discoverable by JVM. This is only for tests. -->
|
<!-- Add debug build to Java path, so it's discoverable by JVM. This is only for tests. -->
|
||||||
<argLine>-Djava.library.path=${project.basedir}/target/debug:${java.library.path}</argLine>
|
<argLine>-Djava.library.path=${project.basedir}/target/debug:${java.library.path}</argLine>
|
||||||
|
|||||||
@@ -27,13 +27,30 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
|
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
|
||||||
_env: EnvUnowned,
|
env: EnvUnowned,
|
||||||
_class: JClass,
|
_class: JClass,
|
||||||
engine_ptr: jlong,
|
engine_ptr: jlong,
|
||||||
) -> jlong {
|
) -> jlong {
|
||||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
let res = throw_err(env, |_env| {
|
||||||
let c = engine.clone();
|
let engine = unsafe { &mut *get_engine_ptr(engine_ptr)? };
|
||||||
Box::into_raw(Box::new(c)) as jlong
|
let c = engine.clone();
|
||||||
|
Ok(Box::into_raw(Box::new(c)) as jlong)
|
||||||
|
});
|
||||||
|
|
||||||
|
res.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativePrepare(
|
||||||
|
env: EnvUnowned,
|
||||||
|
_class: JClass,
|
||||||
|
engine_ptr: jlong,
|
||||||
|
) {
|
||||||
|
let _ = throw_err(env, |_env| {
|
||||||
|
let engine = unsafe { &mut *get_engine_ptr(engine_ptr)? };
|
||||||
|
engine.prepare()?;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -437,6 +454,9 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
|
|||||||
_class: JClass,
|
_class: JClass,
|
||||||
engine_ptr: jlong,
|
engine_ptr: jlong,
|
||||||
) {
|
) {
|
||||||
|
if engine_ptr == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
let _engine = Box::from_raw(engine_ptr as *mut Engine);
|
let _engine = Box::from_raw(engine_ptr as *mut Engine);
|
||||||
}
|
}
|
||||||
@@ -462,7 +482,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromModul
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut modules = Vec::with_capacity(ids.len());
|
let mut modules = Vec::with_capacity(ids.len());
|
||||||
for (id, content) in ids.into_iter().zip(contents) {
|
for (id, content) in ids.into_iter().zip(contents.into_iter()) {
|
||||||
modules.push(PolicyModule {
|
modules.push(PolicyModule {
|
||||||
id: Rc::from(id.as_str()),
|
id: Rc::from(id.as_str()),
|
||||||
content: Rc::from(content.as_str()),
|
content: Rc::from(content.as_str()),
|
||||||
@@ -816,6 +836,13 @@ fn throw_err<T>(mut env: EnvUnowned, f: impl FnOnce(&mut Env) -> Result<T>) -> R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_engine_ptr(engine_ptr: jlong) -> Result<*mut Engine> {
|
||||||
|
if engine_ptr == 0 {
|
||||||
|
return Err(anyhow::anyhow!("Engine is closed"));
|
||||||
|
}
|
||||||
|
Ok(engine_ptr as *mut Engine)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_string_array(env: &mut Env, array: jobjectArray) -> Result<Vec<String>> {
|
fn get_string_array(env: &mut Env, array: jobjectArray) -> Result<Vec<String>> {
|
||||||
if array.is_null() {
|
if array.is_null() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
// if you update the native API.
|
// if you update the native API.
|
||||||
private static native long nativeNewEngine();
|
private static native long nativeNewEngine();
|
||||||
private static native long nativeClone(long enginePtr);
|
private static native long nativeClone(long enginePtr);
|
||||||
|
private static native void nativePrepare(long enginePtr);
|
||||||
private static native void nativeSetRegoV0(long enginePtr, boolean enable);
|
private static native void nativeSetRegoV0(long enginePtr, boolean enable);
|
||||||
private static native String nativeAddPolicy(long enginePtr, String path, String rego);
|
private static native String nativeAddPolicy(long enginePtr, String path, String rego);
|
||||||
private static native String nativeAddPolicyFromFile(long enginePtr, String path);
|
private static native String nativeAddPolicyFromFile(long enginePtr, String path);
|
||||||
@@ -45,7 +46,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
|
|
||||||
// Pointer to Engine allocated on Rust's heap, all native methods works on
|
// Pointer to Engine allocated on Rust's heap, all native methods works on
|
||||||
// engine expects this pointer. It is free'd in `close` method.
|
// engine expects this pointer. It is free'd in `close` method.
|
||||||
private final long enginePtr;
|
private long enginePtr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Regorus Engine.
|
* Creates a new Regorus Engine.
|
||||||
@@ -63,7 +64,15 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* Efficiently clones an Engine.
|
* Efficiently clones an Engine.
|
||||||
*/
|
*/
|
||||||
public Engine clone() {
|
public Engine clone() {
|
||||||
return new Engine(nativeClone(enginePtr));
|
return new Engine(nativeClone(requireOpen()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares internal evaluation structures without executing a query.
|
||||||
|
* Optional: if skipped, first evaluation performs the same setup.
|
||||||
|
*/
|
||||||
|
public void prepare() {
|
||||||
|
nativePrepare(requireOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,7 +82,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public void setRegoV0(boolean enable) {
|
public void setRegoV0(boolean enable) {
|
||||||
nativeSetRegoV0(enginePtr, enable);
|
nativeSetRegoV0(requireOpen(), enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,7 +94,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @return Rego package defined in the policy.
|
* @return Rego package defined in the policy.
|
||||||
*/
|
*/
|
||||||
public String addPolicy(String filename, String rego) {
|
public String addPolicy(String filename, String rego) {
|
||||||
return nativeAddPolicy(enginePtr, filename, rego);
|
return nativeAddPolicy(requireOpen(), filename, rego);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,7 +105,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @return Rego package defined in the policy.
|
* @return Rego package defined in the policy.
|
||||||
*/
|
*/
|
||||||
public String addPolicyFromFile(String path) {
|
public String addPolicyFromFile(String path) {
|
||||||
return nativeAddPolicyFromFile(enginePtr, path);
|
return nativeAddPolicyFromFile(requireOpen(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +114,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @return List of Rego packages as a JSON array of strings.
|
* @return List of Rego packages as a JSON array of strings.
|
||||||
*/
|
*/
|
||||||
public String getPackages() {
|
public String getPackages() {
|
||||||
return nativeGetPackages(enginePtr);
|
return nativeGetPackages(requireOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,14 +123,14 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @return List of Rego policies as a JSON array of sources.
|
* @return List of Rego policies as a JSON array of sources.
|
||||||
*/
|
*/
|
||||||
public String getPolicies() {
|
public String getPolicies() {
|
||||||
return nativeGetPolicies(enginePtr);
|
return nativeGetPolicies(requireOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the data document.
|
* Clears the data document.
|
||||||
*/
|
*/
|
||||||
public void clearData() {
|
public void clearData() {
|
||||||
nativeClearData(enginePtr);
|
nativeClearData(requireOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,7 +152,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @param data Inline data document.
|
* @param data Inline data document.
|
||||||
*/
|
*/
|
||||||
public void addDataJson(String data) throws RuntimeException {
|
public void addDataJson(String data) throws RuntimeException {
|
||||||
nativeAddDataJson(enginePtr, data);
|
nativeAddDataJson(requireOpen(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,7 +169,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @param path Path to JSON data document.
|
* @param path Path to JSON data document.
|
||||||
*/
|
*/
|
||||||
public void addDataJsonFromFile(String path) throws RuntimeException {
|
public void addDataJsonFromFile(String path) throws RuntimeException {
|
||||||
nativeAddDataJsonFromFile(enginePtr, path);
|
nativeAddDataJsonFromFile(requireOpen(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,7 +178,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @param input inline JSON input.
|
* @param input inline JSON input.
|
||||||
*/
|
*/
|
||||||
public void setInputJson(String input) {
|
public void setInputJson(String input) {
|
||||||
nativeSetInputJson(enginePtr, input);
|
nativeSetInputJson(requireOpen(), input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,7 +187,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @param path Path to JSON input.
|
* @param path Path to JSON input.
|
||||||
*/
|
*/
|
||||||
public void setInputJsonFromFile(String path) {
|
public void setInputJsonFromFile(String path) {
|
||||||
nativeSetInputJsonFromFile(enginePtr, path);
|
nativeSetInputJsonFromFile(requireOpen(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -189,7 +198,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @return Query results as a JSON string.
|
* @return Query results as a JSON string.
|
||||||
*/
|
*/
|
||||||
public String evalQuery(String query) {
|
public String evalQuery(String query) {
|
||||||
return nativeEvalQuery(enginePtr, query);
|
return nativeEvalQuery(requireOpen(), query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,7 +209,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @return Value of the rule as a JSON string.
|
* @return Value of the rule as a JSON string.
|
||||||
*/
|
*/
|
||||||
public String evalRule(String rule) {
|
public String evalRule(String rule) {
|
||||||
return nativeEvalRule(enginePtr, rule);
|
return nativeEvalRule(requireOpen(), rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,7 +219,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public void setEnableCoverage(boolean enable) {
|
public void setEnableCoverage(boolean enable) {
|
||||||
nativeSetEnableCoverage(enginePtr, enable);
|
nativeSetEnableCoverage(requireOpen(), enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -218,7 +227,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public void clearCoverageData() {
|
public void clearCoverageData() {
|
||||||
nativeClearCoverageData(enginePtr);
|
nativeClearCoverageData(requireOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,7 +237,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public String getCoverageReport() {
|
public String getCoverageReport() {
|
||||||
return nativeGetCoverageReport(enginePtr);
|
return nativeGetCoverageReport(requireOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,7 +247,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public String getCoverageReportPretty() {
|
public String getCoverageReportPretty() {
|
||||||
return nativeGetCoverageReportPretty(enginePtr);
|
return nativeGetCoverageReportPretty(requireOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -248,7 +257,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public void setGatherPrints(boolean b) {
|
public void setGatherPrints(boolean b) {
|
||||||
nativeSetGatherPrints(enginePtr, b);
|
nativeSetGatherPrints(requireOpen(), b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,7 +267,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public String takePrints() {
|
public String takePrints() {
|
||||||
return nativeTakePrints(enginePtr);
|
return nativeTakePrints(requireOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,24 +276,34 @@ public class Engine implements AutoCloseable, Cloneable {
|
|||||||
* @param config Policy length configuration.
|
* @param config Policy length configuration.
|
||||||
*/
|
*/
|
||||||
public void setPolicyLengthConfig(PolicyLengthConfig config) {
|
public void setPolicyLengthConfig(PolicyLengthConfig config) {
|
||||||
nativeSetPolicyLengthConfig(enginePtr, config.maxCol, config.maxFileBytes, config.maxLines);
|
nativeSetPolicyLengthConfig(requireOpen(), config.maxCol, config.maxFileBytes, config.maxLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the policy length configuration, reverting to defaults.
|
* Clear the policy length configuration, reverting to defaults.
|
||||||
*/
|
*/
|
||||||
public void clearPolicyLengthConfig() {
|
public void clearPolicyLengthConfig() {
|
||||||
nativeClearPolicyLengthConfig(enginePtr);
|
nativeClearPolicyLengthConfig(requireOpen());
|
||||||
}
|
}
|
||||||
|
|
||||||
long getPtr() {
|
long getPtr() {
|
||||||
|
return requireOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long requireOpen() {
|
||||||
|
if (enginePtr == 0) {
|
||||||
|
throw new IllegalStateException("Engine is closed");
|
||||||
|
}
|
||||||
return enginePtr;
|
return enginePtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
nativeDestroyEngine(enginePtr);
|
if (enginePtr != 0) {
|
||||||
|
nativeDestroyEngine(enginePtr);
|
||||||
|
enginePtr = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading native library from JAR is adapted from:
|
// Loading native library from JAR is adapted from:
|
||||||
|
|||||||
@@ -22,8 +22,19 @@ public class EngineTest extends TestCase
|
|||||||
"package test\nmessage = concat(\", \", [input.message, data.message])"
|
"package test\nmessage = concat(\", \", [input.message, data.message])"
|
||||||
);
|
);
|
||||||
engine.addDataJson("{\"message\":\"World!\"}");
|
engine.addDataJson("{\"message\":\"World!\"}");
|
||||||
|
engine.prepare();
|
||||||
engine.setInputJson("{\"message\":\"Hello\"}");
|
engine.setInputJson("{\"message\":\"Hello\"}");
|
||||||
resJson = engine.evalQuery("data.test.message");
|
resJson = engine.evalQuery("data.test.message");
|
||||||
|
|
||||||
|
try (Engine template = engine.clone()) {
|
||||||
|
template.setInputJson("{\"message\":\"Hi\"}");
|
||||||
|
String templateResJson = template.evalQuery("data.test.message");
|
||||||
|
Map templateRes = new Gson().fromJson(templateResJson, Map.class);
|
||||||
|
ArrayList templateResults = (ArrayList) templateRes.get("result");
|
||||||
|
ArrayList templateExpressions = (ArrayList) ((Map) templateResults.get(0)).get("expressions");
|
||||||
|
Map templateExpression = (Map) templateExpressions.get(0);
|
||||||
|
Assert.assertEquals("Hi, World!", templateExpression.get("value"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
@@ -33,4 +44,28 @@ public class EngineTest extends TestCase
|
|||||||
Map expression = (Map) expressions.get(0);
|
Map expression = (Map) expressions.get(0);
|
||||||
Assert.assertEquals("Hello, World!", expression.get("value"));
|
Assert.assertEquals("Hello, World!", expression.get("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void test_closed_engine_operations_throw()
|
||||||
|
{
|
||||||
|
Engine engine = new Engine();
|
||||||
|
engine.close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
engine.prepare();
|
||||||
|
fail("prepare should fail on closed engine");
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
engine.clone();
|
||||||
|
fail("clone should fail on closed engine");
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
engine.evalQuery("data");
|
||||||
|
fail("evalQuery should fail on closed engine");
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
621
bindings/python/Cargo.lock
generated
621
bindings/python/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "regoruspy"
|
name = "regoruspy"
|
||||||
version = "0.11.0"
|
version = "0.10.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://github.com/microsoft/regorus/bindings/python"
|
repository = "https://github.com/microsoft/regorus/bindings/python"
|
||||||
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||||
@@ -23,7 +23,7 @@ coverage = ["regorus/coverage"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
ordered-float = "5.3.0"
|
ordered-float = "5.3.0"
|
||||||
pyo3 = { version = "0.29.0", features = ["abi3-py310", "anyhow", "extension-module"] }
|
pyo3 = { version = "0.28.3", features = ["abi3-py310", "anyhow", "extension-module"] }
|
||||||
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||||
serde_json = "1.0.150"
|
serde_json = "1.0.140"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["maturin>=1.14.1,<2.0"]
|
requires = ["maturin>=1.4,<2.0"]
|
||||||
build-backend = "maturin"
|
build-backend = "maturin"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
|
|||||||
@@ -463,6 +463,13 @@ impl Engine {
|
|||||||
self.engine.take_prints()
|
self.engine.take_prints()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare internal evaluation structures without executing a query.
|
||||||
|
///
|
||||||
|
/// Optional: if skipped, first evaluation performs the same setup.
|
||||||
|
pub fn prepare(&mut self) -> Result<()> {
|
||||||
|
self.engine.prepare()
|
||||||
|
}
|
||||||
|
|
||||||
/// Clone a [`Engine`]
|
/// Clone a [`Engine`]
|
||||||
///
|
///
|
||||||
/// To avoid having to parse same policy again, the engine can be cloned
|
/// To avoid having to parse same policy again, the engine can be cloned
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ report = engine.get_coverage_report_pretty()
|
|||||||
print(report)
|
print(report)
|
||||||
|
|
||||||
# Clone engine
|
# Clone engine
|
||||||
|
engine.prepare()
|
||||||
engine1 = engine.clone()
|
engine1 = engine.clone()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
616
bindings/ruby/Cargo.lock
generated
616
bindings/ruby/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,6 @@ gem "minitest", "~> 6.0"
|
|||||||
gem "rake", "~> 13.4"
|
gem "rake", "~> 13.4"
|
||||||
gem "rake-compiler", "~> 1.3"
|
gem "rake-compiler", "~> 1.3"
|
||||||
gem "rake-compiler-dock", "~> 1.12"
|
gem "rake-compiler-dock", "~> 1.12"
|
||||||
gem "rubocop", "~> 1.88", require: false
|
gem "rubocop", "~> 1.86", require: false
|
||||||
gem "rubocop-minitest", "~> 0.40.0", require: false
|
gem "rubocop-minitest", "~> 0.39.1", require: false
|
||||||
gem "rubocop-rake", "~> 0.7.1", require: false
|
gem "rubocop-rake", "~> 0.7.1", require: false
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ GEM
|
|||||||
specs:
|
specs:
|
||||||
ast (2.4.3)
|
ast (2.4.3)
|
||||||
drb (2.2.3)
|
drb (2.2.3)
|
||||||
json (2.21.1)
|
json (2.19.4)
|
||||||
language_server-protocol (3.17.0.6)
|
language_server-protocol (3.17.0.5)
|
||||||
lint_roller (1.1.0)
|
lint_roller (1.1.0)
|
||||||
minitest (6.0.6)
|
minitest (6.0.5)
|
||||||
drb (~> 2.0)
|
drb (~> 2.0)
|
||||||
prism (~> 1.5)
|
prism (~> 1.5)
|
||||||
parallel (2.1.0)
|
parallel (2.1.0)
|
||||||
parser (3.3.12.0)
|
parser (3.3.11.1)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
prism (1.9.0)
|
prism (1.9.0)
|
||||||
@@ -26,10 +26,10 @@ GEM
|
|||||||
rake-compiler (1.3.1)
|
rake-compiler (1.3.1)
|
||||||
rake
|
rake
|
||||||
rake-compiler-dock (1.12.0)
|
rake-compiler-dock (1.12.0)
|
||||||
rb_sys (0.9.128)
|
rb_sys (0.9.127)
|
||||||
rake-compiler-dock (= 1.12.0)
|
rake-compiler-dock (= 1.12.0)
|
||||||
regexp_parser (2.12.0)
|
regexp_parser (2.12.0)
|
||||||
rubocop (1.88.2)
|
rubocop (1.86.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
lint_roller (~> 1.1.0)
|
lint_roller (~> 1.1.0)
|
||||||
@@ -40,10 +40,10 @@ GEM
|
|||||||
rubocop-ast (>= 1.49.0, < 2.0)
|
rubocop-ast (>= 1.49.0, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 4.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.50.0)
|
rubocop-ast (1.49.1)
|
||||||
parser (>= 3.3.7.2)
|
parser (>= 3.3.7.2)
|
||||||
prism (~> 1.7)
|
prism (~> 1.7)
|
||||||
rubocop-minitest (0.40.0)
|
rubocop-minitest (0.39.1)
|
||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rubocop (>= 1.75.0, < 2.0)
|
rubocop (>= 1.75.0, < 2.0)
|
||||||
rubocop-ast (>= 1.38.0, < 2.0)
|
rubocop-ast (>= 1.38.0, < 2.0)
|
||||||
@@ -65,8 +65,8 @@ DEPENDENCIES
|
|||||||
rake-compiler (~> 1.3)
|
rake-compiler (~> 1.3)
|
||||||
rake-compiler-dock (~> 1.12)
|
rake-compiler-dock (~> 1.12)
|
||||||
regorusrb!
|
regorusrb!
|
||||||
rubocop (~> 1.88)
|
rubocop (~> 1.86)
|
||||||
rubocop-minitest (~> 0.40.0)
|
rubocop-minitest (~> 0.39.1)
|
||||||
rubocop-rake (~> 0.7.1)
|
rubocop-rake (~> 0.7.1)
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "regorusrb"
|
name = "regorusrb"
|
||||||
version = "0.11.0"
|
version = "0.10.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||||
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
|
||||||
|
|||||||
@@ -115,6 +115,13 @@ impl Engine {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare(&self) -> Result<(), Error> {
|
||||||
|
self.engine
|
||||||
|
.borrow_mut()
|
||||||
|
.prepare()
|
||||||
|
.map_err(|e| Error::new(runtime_error(), format!("Failed to prepare engine: {e}")))
|
||||||
|
}
|
||||||
|
|
||||||
fn get_packages(&self) -> Result<Vec<String>, Error> {
|
fn get_packages(&self) -> Result<Vec<String>, Error> {
|
||||||
self.engine
|
self.engine
|
||||||
.borrow()
|
.borrow()
|
||||||
@@ -373,6 +380,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|||||||
method!(Engine::add_data_from_json_file, 1),
|
method!(Engine::add_data_from_json_file, 1),
|
||||||
)?;
|
)?;
|
||||||
engine_class.define_method("clear_data", method!(Engine::clear_data, 0))?;
|
engine_class.define_method("clear_data", method!(Engine::clear_data, 0))?;
|
||||||
|
engine_class.define_method("prepare", method!(Engine::prepare, 0))?;
|
||||||
|
|
||||||
// input operations
|
// input operations
|
||||||
engine_class.define_method("set_input", method!(Engine::set_input, 1))?;
|
engine_class.define_method("set_input", method!(Engine::set_input, 1))?;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Regorus
|
module Regorus
|
||||||
VERSION = "0.11.0"
|
VERSION = "0.10.0"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ class TestRegorus < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_engine_cloning
|
def test_engine_cloning
|
||||||
|
@engine.prepare
|
||||||
cloned_engine = @engine.clone
|
cloned_engine = @engine.clone
|
||||||
|
|
||||||
assert_instance_of ::Regorus::Engine, cloned_engine
|
assert_instance_of ::Regorus::Engine, cloned_engine
|
||||||
|
|||||||
624
bindings/wasm/Cargo.lock
generated
624
bindings/wasm/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "regorusjs"
|
name = "regorusjs"
|
||||||
version = "0.11.0"
|
version = "0.10.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://github.com/microsoft/regorus/bindings/wasm"
|
repository = "https://github.com/microsoft/regorus/bindings/wasm"
|
||||||
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||||
@@ -42,7 +42,7 @@ coverage = ["regorus/coverage"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.150"
|
serde_json = "1.0.140"
|
||||||
wasm-bindgen = "0.2.100"
|
wasm-bindgen = "0.2.100"
|
||||||
serde-wasm-bindgen = "0.6"
|
serde-wasm-bindgen = "0.6"
|
||||||
# Specify uuid as a mandatory dependency so as to enable `js` feature which is now required
|
# Specify uuid as a mandatory dependency so as to enable `js` feature which is now required
|
||||||
@@ -55,7 +55,7 @@ getrandom03 = { package = "getrandom", version = "0.3.1", features = ["std", "wa
|
|||||||
getrandom = { version = "0.4.2", features = ["wasm_js"] }
|
getrandom = { version = "0.4.2", features = ["wasm_js"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3.72"
|
wasm-bindgen-test = "0.3.71"
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }
|
||||||
|
|||||||
@@ -21,3 +21,9 @@ Run `cargo xtask build-wasm` to invoke wasm-pack with sensible defaults, or `car
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
See [test.js](https://github.com/microsoft/regorus/blob/main/bindings/wasm/test.js) for example usage.
|
See [test.js](https://github.com/microsoft/regorus/blob/main/bindings/wasm/test.js) for example usage.
|
||||||
|
|
||||||
|
For best performance with large policies, call `engine.prepare()` after loading
|
||||||
|
policy/data, then use `engine.clone()` to create per-request engines. If
|
||||||
|
`prepare()` is skipped, the first `eval*` call performs the same one-time
|
||||||
|
setup. Adding/changing policy or data after `prepare()` invalidates the
|
||||||
|
prepared state.
|
||||||
|
|||||||
@@ -138,6 +138,17 @@ impl Engine {
|
|||||||
self.engine.set_rego_v0(enable)
|
self.engine.set_rego_v0(enable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clone this engine.
|
||||||
|
///
|
||||||
|
/// Useful for creating per-request engines after loading policy/data once.
|
||||||
|
///
|
||||||
|
/// Clone is designed to avoid reparsing policy text and reloading immutable
|
||||||
|
/// policy structures. Mutable evaluation state is copied for isolation.
|
||||||
|
#[wasm_bindgen(js_name = "clone")]
|
||||||
|
pub fn cloneEngine(&self) -> Engine {
|
||||||
|
Clone::clone(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a policy
|
/// Add a policy
|
||||||
///
|
///
|
||||||
/// The policy is parsed into AST.
|
/// The policy is parsed into AST.
|
||||||
@@ -158,6 +169,20 @@ impl Engine {
|
|||||||
self.engine.add_data(data).map_err(error_to_jsvalue)
|
self.engine.add_data(data).map_err(error_to_jsvalue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare the engine for evaluation.
|
||||||
|
///
|
||||||
|
/// The first evaluation on an unprepared engine performs one-time setup.
|
||||||
|
/// Calling `prepare()` performs that setup eagerly.
|
||||||
|
///
|
||||||
|
/// This is optional for correctness. If omitted, the first `eval*` call
|
||||||
|
/// implicitly performs preparation.
|
||||||
|
///
|
||||||
|
/// If policies/data are modified after `prepare()`, preparation is
|
||||||
|
/// invalidated and must be performed again (explicitly or via first eval).
|
||||||
|
pub fn prepare(&mut self) -> Result<(), JsValue> {
|
||||||
|
self.engine.prepare().map_err(error_to_jsvalue)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the list of packages defined by loaded policies.
|
/// Get the list of packages defined by loaded policies.
|
||||||
///
|
///
|
||||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
|
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
|
||||||
@@ -487,6 +512,9 @@ mod tests {
|
|||||||
)?;
|
)?;
|
||||||
assert_eq!(pkg, "data.test");
|
assert_eq!(pkg, "data.test");
|
||||||
|
|
||||||
|
// Prepare before first evaluation.
|
||||||
|
engine.prepare()?;
|
||||||
|
|
||||||
let results = engine.evalQuery("data".to_string())?;
|
let results = engine.evalQuery("data".to_string())?;
|
||||||
let r = regorus::Value::from_json_str(&results).map_err(error_to_jsvalue)?;
|
let r = regorus::Value::from_json_str(&results).map_err(error_to_jsvalue)?;
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ engine.addDataJson(`
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// Prepare internal evaluation structures once.
|
||||||
|
engine.prepare();
|
||||||
|
|
||||||
|
// Clone a prepared template engine for reuse.
|
||||||
|
var template = engine.clone();
|
||||||
|
engine = template.clone();
|
||||||
|
|
||||||
// Set policy input
|
// Set policy input
|
||||||
engine.setInputJson(`
|
engine.setInputJson(`
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -254,13 +254,7 @@ include formatted state snapshots where possible.
|
|||||||
7. **Host await**: In run-to-completion mode, `HostAwait` consumes a response
|
7. **Host await**: In run-to-completion mode, `HostAwait` consumes a response
|
||||||
from `host_await_responses`. Suspendable mode yields control with a
|
from `host_await_responses`. Suspendable mode yields control with a
|
||||||
`SuspendReason::HostAwait { dest, argument, identifier }` that the host must
|
`SuspendReason::HostAwait { dest, argument, identifier }` that the host must
|
||||||
service. The compiler supports two ways to emit `HostAwait`:
|
service.
|
||||||
- **Explicit**: `__builtin_host_await(payload, identifier)` — raw 2-argument
|
|
||||||
form.
|
|
||||||
- **Registered**: `compile_from_policy_with_host_await` accepts a list of
|
|
||||||
`(name, arg_count)` pairs. Calls to registered names are compiled as
|
|
||||||
`HostAwait` with the function name as the identifier literal. Registered
|
|
||||||
names take precedence over user-defined functions and standard builtins.
|
|
||||||
8. **Completion**: `Return` wraps the selected register value into
|
8. **Completion**: `Return` wraps the selected register value into
|
||||||
`InstructionOutcome::Return`, unwinding frames until the entry frame is
|
`InstructionOutcome::Return`, unwinding frames until the entry frame is
|
||||||
cleared. `RuleReturn` is a specialised variant used by rule execution
|
cleared. `RuleReturn` is a specialised variant used by rule execution
|
||||||
|
|||||||
@@ -177,75 +177,6 @@ Parameter tables:
|
|||||||
- Suspendable: emits `InstructionOutcome::Suspend` with `SuspendReason::HostAwait`.
|
- Suspendable: emits `InstructionOutcome::Suspend` with `SuspendReason::HostAwait`.
|
||||||
The host must resume with a value that will be written into `dest`.
|
The host must resume with a value that will be written into `dest`.
|
||||||
|
|
||||||
### Registered host-await builtins
|
|
||||||
|
|
||||||
The compiler can be configured with a list of function names that map directly
|
|
||||||
to `HostAwait` instructions. This allows policy authors to write natural
|
|
||||||
function calls (e.g. `lookup(input.account_id)`) instead of the raw
|
|
||||||
`__builtin_host_await(payload, identifier)` builtin.
|
|
||||||
|
|
||||||
Registration is done at compile time via `Compiler::compile_from_policy_with_host_await`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let builtins = [("lookup", 1), ("persist", 1)];
|
|
||||||
let program = Compiler::compile_from_policy_with_host_await(
|
|
||||||
&compiled_policy, &entry_points, &builtins,
|
|
||||||
)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
Each registered name is a `(name, arg_count)` pair. When the compiler
|
|
||||||
encounters a call to a registered name, it emits a `HostAwait` instruction
|
|
||||||
with:
|
|
||||||
- `arg` = the first argument register
|
|
||||||
- `id` = a register loaded with a string literal containing the function name
|
|
||||||
|
|
||||||
Both the explicit `__builtin_host_await(arg, id)` call and a registered
|
|
||||||
builtin call produce the **same `HostAwait` bytecode instruction**. The only
|
|
||||||
difference is how the `id` register is populated: explicit calls take it from
|
|
||||||
the second user-supplied argument, while registered calls auto-generate a
|
|
||||||
`Load` instruction for the function name string. The VM cannot distinguish
|
|
||||||
between the two at runtime.
|
|
||||||
|
|
||||||
**Resolution order** in `determine_call_target()`:
|
|
||||||
1. `__builtin_host_await` (magic 2-argument form)
|
|
||||||
2. Registered host-await builtins (matched by **bare** function name only)
|
|
||||||
3. User-defined functions (matched by package-qualified path)
|
|
||||||
4. Standard builtins (matched by bare function name)
|
|
||||||
|
|
||||||
Registered names shadow both user-defined functions and standard builtins.
|
|
||||||
This means `time.parse_duration_ns` can be overridden to route through the
|
|
||||||
host instead of the built-in Rust implementation.
|
|
||||||
|
|
||||||
**Only unqualified calls are intercepted.** Registration matches a call by
|
|
||||||
the name *as written in the policy*. A bare call — `lookup(x)` — is
|
|
||||||
intercepted and compiled to a `HostAwait`. A package-qualified call —
|
|
||||||
`data.pkg.lookup(x)` — is **not** intercepted; it is resolved normally, as
|
|
||||||
if the name were never registered.
|
|
||||||
|
|
||||||
```rego
|
|
||||||
# "lookup" is registered as a host-await builtin.
|
|
||||||
|
|
||||||
package other
|
|
||||||
import rego.v1
|
|
||||||
lookup(k) := k # an ordinary rule that happens to share the name
|
|
||||||
|
|
||||||
package demo
|
|
||||||
import rego.v1
|
|
||||||
a := lookup(input.k) # intercepted -> HostAwait
|
|
||||||
b := data.other.lookup(input.k) # NOT intercepted -> calls other.lookup
|
|
||||||
```
|
|
||||||
|
|
||||||
The qualified form is resolved exactly as it would be without registration:
|
|
||||||
if a rule exists at that path it is called, otherwise compilation fails with
|
|
||||||
`Unknown function`. (A standard builtin like `count` has no qualified form at
|
|
||||||
all, so `data.pkg.count(x)` is always an `Unknown function` error, registered
|
|
||||||
or not.)
|
|
||||||
|
|
||||||
**Argument handling**: The `HostAwait` instruction carries a single `arg`
|
|
||||||
register. Registered builtins must use `arg_count: 1`; the compiler rejects
|
|
||||||
`arg_count > 1` at registration time. To pass multiple values, use object
|
|
||||||
packing: `lookup({"user": x, "resource": y})`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Halt instruction
|
## Halt instruction
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
# Object
|
|
||||||
|
|
||||||
Opaque container for `Value::Object`'s key→value storage, enabling
|
|
||||||
alternative backends without call-site changes.
|
|
||||||
|
|
||||||
## Design
|
|
||||||
|
|
||||||
`Object` wraps the storage for a key→value collection of `Value`s and
|
|
||||||
provides a curated set of methods (`get`, `insert`, `remove`, `iter`,
|
|
||||||
`iter_sorted`, `cursor`, serde). The backing store is private; callers
|
|
||||||
never see or pattern-match on it, so the representation can change
|
|
||||||
without rippling through call sites.
|
|
||||||
|
|
||||||
Multiple backends can coexist at runtime. Because the backing store is
|
|
||||||
private, different `Object` instances in the same process can use
|
|
||||||
different implementations — e.g., a lazy DB-backed object for `input`,
|
|
||||||
inline small-map objects for SARIF location records, and a regular
|
|
||||||
sorted map elsewhere — all interoperating through the same opaque
|
|
||||||
type. This is stronger than the typical Cargo-feature-selected backend
|
|
||||||
seen in precedent crates.
|
|
||||||
|
|
||||||
Iteration is split intentionally. `iter()` makes no ordering promise,
|
|
||||||
which lets backends that don't keep entries sorted skip any sort work.
|
|
||||||
`iter_sorted()` returns entries in `Value` order and is what
|
|
||||||
serialization and `Ord` rely on for deterministic output. Cursor types
|
|
||||||
add resumable, incremental traversal for the RVM iteration state
|
|
||||||
without leaking iterator internals.
|
|
||||||
|
|
||||||
`Ord` and `PartialOrd` are defined against `iter_sorted()` rather than
|
|
||||||
derived from the storage. Two `Object`s built on different backends —
|
|
||||||
or with different insertion histories — compare equal whenever their
|
|
||||||
sorted entries match, so changing the backend never changes observable
|
|
||||||
comparison results.
|
|
||||||
|
|
||||||
## Precedents
|
|
||||||
|
|
||||||
Other crates that hide storage behind a stable API so the implementation
|
|
||||||
can change without breaking callers:
|
|
||||||
|
|
||||||
- **`serde_json::Map`** — opaque newtype allowing cargo-feature based
|
|
||||||
swap between `BTreeMap` (canonical order) and `IndexMap` (insertion
|
|
||||||
order).
|
|
||||||
- **`toml::Table`** — opaque newtype allowing cargo-feature based swap
|
|
||||||
between `BTreeMap` and `IndexMap`.
|
|
||||||
- **`simdjson` DOM** — opaque tree that lazily materializes nodes on
|
|
||||||
access instead of parsing the whole document up front.
|
|
||||||
|
|
||||||
## Use cases
|
|
||||||
|
|
||||||
- **SARIF small-object pressure** — SARIF reports contain millions of
|
|
||||||
small objects (location records, rule references, message arguments),
|
|
||||||
most with 2-5 keys. A small-map-optimized backend (inline storage
|
|
||||||
for ≤N entries, heap above) eliminates per-object BTreeMap allocation
|
|
||||||
for the common case.
|
|
||||||
|
|
||||||
- **Kubernetes admission policies** — large, deeply-nested resource
|
|
||||||
objects (Pod specs, CRDs) where policies typically touch a handful
|
|
||||||
of paths. A lazy-materializing backend (`LazyObjectProvider` over
|
|
||||||
the incoming JSON) parses only the accessed subtrees.
|
|
||||||
|
|
||||||
- **Azure Policy aliases** — ARM exposes the same logical property
|
|
||||||
under multiple aliases (e.g. paths like
|
|
||||||
`Microsoft.Compute/virtualMachines/storageProfile.osDisk.managedDisk.id`).
|
|
||||||
An alias-aware backend resolves lookups across canonical and alias
|
|
||||||
forms without rewriting every policy.
|
|
||||||
|
|
||||||
- **Azure Policy case-insensitive compare** — ARM property names are
|
|
||||||
case-preserving but case-insensitive on lookup (`tags.Environment`
|
|
||||||
and `tags.environment` resolve identically). A case-insensitive
|
|
||||||
backend centralizes this once at the storage layer instead of at
|
|
||||||
every comparison site.
|
|
||||||
|
|
||||||
- **External data sources** — `input` or `data` backed by a database
|
|
||||||
query, CBOR slice, REST endpoint, or other streaming source via a
|
|
||||||
`LazyObjectProvider`. Entries materialize on demand; the policy
|
|
||||||
only pays for what it touches.
|
|
||||||
|
|
||||||
- **Eval-time temporaries** — objects constructed during evaluation
|
|
||||||
(comprehensions, intermediate rule results) on a bumpalo arena.
|
|
||||||
The whole arena drops at query end with zero per-entry free cost.
|
|
||||||
|
|
||||||
- **Host-language interop** — Python dicts or JS objects accessed via
|
|
||||||
FFI callbacks from the embedding application, without copying into
|
|
||||||
Rust on every binding boundary.
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
# Set
|
|
||||||
|
|
||||||
Opaque container for `Value::Set`'s element storage, enabling alternative
|
|
||||||
backends without call-site changes. Pairs with [`Object`](object.md) under
|
|
||||||
a shared design philosophy.
|
|
||||||
|
|
||||||
## Design
|
|
||||||
|
|
||||||
`Set` wraps a `BTreeSet<Value>` today but exposes only a curated method
|
|
||||||
surface (`contains`, `insert`, `remove`, `iter`, `iter_sorted`, `cursor`,
|
|
||||||
`is_subset`, `intersection`, `union`, `difference`, serde). The inner set is
|
|
||||||
private — callers cannot pattern-match it or hand out references to the
|
|
||||||
backing store, so the backend can change without churn at the ~400 call
|
|
||||||
sites that name `Set`.
|
|
||||||
|
|
||||||
Two iteration methods reflect a real distinction: `iter()` makes no
|
|
||||||
ordering promise (lets future hash/lazy backends skip sorting work);
|
|
||||||
`iter_sorted()` guarantees deterministic order (used by serialization and
|
|
||||||
`Ord`). Cursor types support incremental traversal needed by the RVM
|
|
||||||
iteration state without exposing iterator internals.
|
|
||||||
|
|
||||||
`Ord` is hand-written against `iter_sorted` rather than derived, so two
|
|
||||||
backends that store elements differently still compare equal when their
|
|
||||||
sorted contents match.
|
|
||||||
|
|
||||||
## Scenarios enabled
|
|
||||||
|
|
||||||
- **Hash-backed storage** — `FxHashSet`-backed inner turns O(log n)
|
|
||||||
membership checks into O(1); swap in for policies where elements aren't
|
|
||||||
compared ordinally.
|
|
||||||
- **Lazy/streaming** — wrap a `LazySetProvider` (DB query, CBOR slice,
|
|
||||||
REST endpoint) and materialize elements on demand.
|
|
||||||
- **Arena allocation** — bumpalo-backed inner for eval-time temporaries;
|
|
||||||
drop the whole arena at query end with zero per-element free cost.
|
|
||||||
- **FFI-backed** — host-language collections (Python set, JS Set) without
|
|
||||||
copying into Rust.
|
|
||||||
- **Bloom-filter pre-check** — front a large backing set with a Bloom
|
|
||||||
filter for fast negative-membership tests on read-mostly allowlists.
|
|
||||||
|
|
||||||
## Known use cases
|
|
||||||
|
|
||||||
- **Azure Policy allowed-values lists** — large allowlists (allowed
|
|
||||||
regions, allowed SKUs, allowed image publishers) compared against
|
|
||||||
single resource values. Hash-backed Set turns O(log n) membership
|
|
||||||
checks into O(1).
|
|
||||||
- **SARIF rule deduplication** — collapsing duplicate rule references
|
|
||||||
across thousands of result records. Set-of-objects with structural
|
|
||||||
hashing avoids the BTreeSet sort cost on every insert.
|
|
||||||
- **RBAC role membership** — checking whether a principal belongs to any
|
|
||||||
of dozens of role groups. Hash-backed Set scales to thousands of
|
|
||||||
members with constant-time membership.
|
|
||||||
- **Azure Policy denied-resource-type sets** — exclusion lists used by
|
|
||||||
deny-effect policies; same hash-backed pattern as allowed-values.
|
|
||||||
|
|
||||||
## Precedents
|
|
||||||
|
|
||||||
- **`indexmap::IndexSet`** — opaque newtype that pairs hash lookup with
|
|
||||||
insertion-order iteration; precedent for "Set with alternative
|
|
||||||
ordering semantics behind a stable surface."
|
|
||||||
- **`hashbrown::HashSet`** — backs Rust's `std::collections::HashSet`
|
|
||||||
and demonstrates a fully swappable backend behind a stable API.
|
|
||||||
- **`roaring::RoaringBitmap`** — bitmap-backed integer set. Not
|
|
||||||
applicable to `Value` keys directly, but a precedent for the broader
|
|
||||||
idea of "Set with alternative storage representations chosen by
|
|
||||||
workload shape."
|
|
||||||
- **`serde_json`** — note that `serde_json` has no Set equivalent: its
|
|
||||||
Value enum collapses sets into arrays. Regorus's first-class Set with
|
|
||||||
storage abstraction is therefore unusually well-positioned among JSON
|
|
||||||
value libraries.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
Cursor types are `pub` (referenced by public `IterationState`) but not
|
|
||||||
re-exported at the crate root. The crate-internal `Set`/`Map`/`MapEntry`
|
|
||||||
aliases for `BTreeSet`/`BTreeMap` in `lib.rs` were renamed to
|
|
||||||
`MapSet`/`Map`/`MapEntry` when this type landed, to free the `Set` name
|
|
||||||
for the new public type. Future Array and String abstractions follow the
|
|
||||||
same shape — see `docs/value/array.md` and `docs/value/string.md` when
|
|
||||||
they land.
|
|
||||||
@@ -23,7 +23,8 @@ 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::{Rc, Source, Value};
|
use regorus::Source;
|
||||||
|
use regorus::Value;
|
||||||
|
|
||||||
/// Evaluate an Azure Policy definition against a resource.
|
/// Evaluate an Azure Policy definition against a resource.
|
||||||
///
|
///
|
||||||
@@ -59,8 +60,11 @@ 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 registry = Rc::new(registry);
|
let program = compiler::compile_policy_definition_with_aliases(
|
||||||
let program = compiler::compile_policy_definition_with_aliases(&defn, Rc::clone(®istry))?;
|
&defn,
|
||||||
|
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.
|
||||||
@@ -134,7 +138,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().keys() {
|
for (alias_name, _) in registry.alias_map() {
|
||||||
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;
|
||||||
@@ -144,7 +148,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().keys() {
|
for (alias_name, _) in registry.alias_map() {
|
||||||
println!(" {alias_name}");
|
println!(" {alias_name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
name = "regorus-mimalloc"
|
name = "regorus-mimalloc"
|
||||||
description = "Vendored mimalloc allocator for regorus"
|
description = "Vendored mimalloc allocator for regorus"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
version = "2.2.7"
|
version = "2.2.6"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/microsoft/regorus"
|
repository = "https://github.com/microsoft/regorus"
|
||||||
|
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ pub fn resolve_path(root: &Value, path: &str) -> Value {
|
|||||||
match ¤t {
|
match ¤t {
|
||||||
Value::Object(map) => {
|
Value::Object(map) => {
|
||||||
let mut next = None;
|
let mut next = None;
|
||||||
for (key, value) in map.iter_sorted() {
|
for (key, value) in map.iter() {
|
||||||
if let Value::String(ref key_str) = *key {
|
if let Value::String(ref key_str) = *key {
|
||||||
if strings::keys::eq(key_str, &segment) {
|
if strings::keys::eq(key_str, &segment) {
|
||||||
next = Some(value.clone());
|
next = Some(value.clone());
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
use crate::ast::{Expr, Ref};
|
use crate::ast::{Expr, Ref};
|
||||||
use crate::builtins;
|
use crate::builtins;
|
||||||
use crate::lexer::Span;
|
use crate::lexer::Span;
|
||||||
use crate::value::Object;
|
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use crate::Rc;
|
use crate::Rc;
|
||||||
|
|
||||||
|
use alloc::collections::BTreeMap;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ fn fn_intersection(
|
|||||||
// Intersection of objects: keep key-value pairs from the first
|
// Intersection of objects: keep key-value pairs from the first
|
||||||
// object only when the key exists in every other object AND
|
// object only when the key exists in every other object AND
|
||||||
// the value is equal across all of them.
|
// the value is equal across all of them.
|
||||||
let mut result: Object = first.as_ref().clone();
|
let mut result: BTreeMap<Value, Value> = first.as_ref().clone();
|
||||||
for arg in rest {
|
for arg in rest {
|
||||||
let Value::Object(ref other) = *arg else {
|
let Value::Object(ref other) = *arg else {
|
||||||
return Ok(Value::Undefined);
|
return Ok(Value::Undefined);
|
||||||
@@ -114,7 +114,7 @@ fn fn_union(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool)
|
|||||||
Value::Object(_) => {
|
Value::Object(_) => {
|
||||||
// Union of objects: recursive merge. Nested objects are merged
|
// Union of objects: recursive merge. Nested objects are merged
|
||||||
// recursively; all other types (including arrays) use last-writer-wins.
|
// recursively; all other types (including arrays) use last-writer-wins.
|
||||||
let mut result = Object::new();
|
let mut result = BTreeMap::<Value, Value>::new();
|
||||||
for arg in args {
|
for arg in args {
|
||||||
let Value::Object(ref obj) = *arg else {
|
let Value::Object(ref obj) = *arg else {
|
||||||
return Ok(Value::Undefined);
|
return Ok(Value::Undefined);
|
||||||
@@ -264,7 +264,7 @@ fn fn_create_object(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut map = Object::new();
|
let mut map = BTreeMap::<Value, Value>::new();
|
||||||
|
|
||||||
for pair in args.chunks(2) {
|
for pair in args.chunks(2) {
|
||||||
#[allow(clippy::pattern_type_mismatch)]
|
#[allow(clippy::pattern_type_mismatch)]
|
||||||
@@ -280,9 +280,9 @@ fn fn_create_object(
|
|||||||
|
|
||||||
/// Recursively merge two objects. Nested objects are merged; everything
|
/// Recursively merge two objects. Nested objects are merged; everything
|
||||||
/// else (including arrays) uses the value from `incoming`.
|
/// else (including arrays) uses the value from `incoming`.
|
||||||
fn merge_objects(base: &Object, overlay: &Object) -> Value {
|
fn merge_objects(base: &BTreeMap<Value, Value>, overlay: &BTreeMap<Value, Value>) -> Value {
|
||||||
let mut result = base.clone();
|
let mut result = base.clone();
|
||||||
for (k, v) in overlay.iter() {
|
for (k, v) in overlay {
|
||||||
#[allow(clippy::needless_borrowed_reference)]
|
#[allow(clippy::needless_borrowed_reference)]
|
||||||
let merged = match (result.get(k), v) {
|
let merged = match (result.get(k), v) {
|
||||||
(Some(&Value::Object(ref prev)), &Value::Object(ref next)) => merge_objects(prev, next),
|
(Some(&Value::Object(ref prev)), &Value::Object(ref next)) => merge_objects(prev, next),
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
use crate::ast::{Expr, Ref};
|
use crate::ast::{Expr, Ref};
|
||||||
use crate::builtins;
|
use crate::builtins;
|
||||||
use crate::lexer::Span;
|
use crate::lexer::Span;
|
||||||
use crate::value::Object;
|
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use crate::Rc;
|
use crate::Rc;
|
||||||
|
|
||||||
|
use alloc::collections::BTreeMap;
|
||||||
use alloc::string::{String, ToString as _};
|
use alloc::string::{String, ToString as _};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -84,8 +84,8 @@ fn fn_items(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool)
|
|||||||
return Ok(Value::Undefined);
|
return Ok(Value::Undefined);
|
||||||
};
|
};
|
||||||
let mut result = Vec::with_capacity(obj.len());
|
let mut result = Vec::with_capacity(obj.len());
|
||||||
for (k, v) in obj.iter_sorted() {
|
for (k, v) in obj.as_ref() {
|
||||||
let mut entry = Object::new();
|
let mut entry = BTreeMap::<Value, Value>::new();
|
||||||
entry.insert(Value::from("key"), k.clone());
|
entry.insert(Value::from("key"), k.clone());
|
||||||
entry.insert(Value::from("value"), v.clone());
|
entry.insert(Value::from("value"), v.clone());
|
||||||
result.push(Value::Object(Rc::new(entry)));
|
result.push(Value::Object(Rc::new(entry)));
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ fn urlquery_encode_object(
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut pairs = url.query_pairs_mut();
|
let mut pairs = url.query_pairs_mut();
|
||||||
for (key, value) in obj.iter_sorted() {
|
for (key, value) in obj.iter() {
|
||||||
let key = ensure_string(name, ¶ms[0], key)?;
|
let key = ensure_string(name, ¶ms[0], key)?;
|
||||||
match value {
|
match value {
|
||||||
Value::String(v) => {
|
Value::String(v) => {
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ use crate::ast::{Expr, Ref};
|
|||||||
use crate::builtins;
|
use crate::builtins;
|
||||||
use crate::builtins::utils::{enforce_limit, ensure_args_count, ensure_object};
|
use crate::builtins::utils::{enforce_limit, ensure_args_count, ensure_object};
|
||||||
use crate::lexer::Span;
|
use crate::lexer::Span;
|
||||||
use crate::value::Object;
|
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use alloc::collections::BTreeSet;
|
use alloc::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
@@ -81,7 +80,7 @@ fn reachable(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn visit(
|
fn visit(
|
||||||
graph: &Object,
|
graph: &BTreeMap<Value, Value>,
|
||||||
visited: &mut BTreeSet<Value>,
|
visited: &mut BTreeSet<Value>,
|
||||||
node: &Value,
|
node: &Value,
|
||||||
path: &mut Vec<Value>,
|
path: &mut Vec<Value>,
|
||||||
@@ -212,7 +211,7 @@ fn walk_visit(path: &mut Vec<Value>, value: &Value, paths: &mut Vec<Value>) -> R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Object(obj) => {
|
Value::Object(obj) => {
|
||||||
for (key, value) in obj.iter_sorted() {
|
for (key, value) in obj.iter() {
|
||||||
path.push(key.clone());
|
path.push(key.clone());
|
||||||
// Guard path stack growth while traversing object entries.
|
// Guard path stack growth while traversing object entries.
|
||||||
enforce_limit()?;
|
enforce_limit()?;
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ fn merge_filters(
|
|||||||
let vref = match f {
|
let vref = match f {
|
||||||
Value::Object(obj) => {
|
Value::Object(obj) => {
|
||||||
let obj = Rc::make_mut(obj);
|
let obj = Rc::make_mut(obj);
|
||||||
let entry = obj.get_or_insert_with(p.clone(), Value::new_object);
|
let entry = obj.entry(p.clone()).or_insert_with(Value::new_object);
|
||||||
// Guard filter map growth when creating nested objects.
|
// Guard filter map growth when creating nested objects.
|
||||||
enforce_limit()?;
|
enforce_limit()?;
|
||||||
entry
|
entry
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ fn to_string(v: &Value, unescape: bool) -> String {
|
|||||||
}
|
}
|
||||||
Value::Object(o) => {
|
Value::Object(o) => {
|
||||||
"{".to_owned()
|
"{".to_owned()
|
||||||
+ &o.iter_sorted()
|
+ &o.iter()
|
||||||
.map(|(k, v)| to_string(k, true) + ": " + &to_string(v, true))
|
.map(|(k, v)| to_string(k, true) + ": " + &to_string(v, true))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
@@ -568,7 +568,7 @@ fn replace_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -
|
|||||||
let mut s = ensure_string(name, ¶ms[1], &args[1])?;
|
let mut s = ensure_string(name, ¶ms[1], &args[1])?;
|
||||||
|
|
||||||
let span = params[0].span();
|
let span = params[0].span();
|
||||||
for item in obj.as_ref().iter_sorted() {
|
for item in obj.as_ref().iter() {
|
||||||
match item {
|
match item {
|
||||||
(Value::String(k), Value::String(v)) => {
|
(Value::String(k), Value::String(v)) => {
|
||||||
s = s.replace(k.as_ref(), v.as_ref()).into();
|
s = s.replace(k.as_ref(), v.as_ref()).into();
|
||||||
|
|||||||
@@ -5,12 +5,11 @@
|
|||||||
use crate::ast::{Expr, Ref};
|
use crate::ast::{Expr, Ref};
|
||||||
use crate::lexer::Span;
|
use crate::lexer::Span;
|
||||||
use crate::number::Number;
|
use crate::number::Number;
|
||||||
use crate::value::Object;
|
|
||||||
use crate::Rc;
|
use crate::Rc;
|
||||||
use crate::Value;
|
use crate::Value;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use alloc::collections::BTreeSet;
|
use alloc::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
@@ -169,7 +168,7 @@ pub fn ensure_set(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<BTreeSet<Value>>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensure_object(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<Object>> {
|
pub fn ensure_object(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<BTreeMap<Value, Value>>> {
|
||||||
Ok(match v {
|
Ok(match v {
|
||||||
Value::Object(o) => o,
|
Value::Object(o) => o,
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ pub(crate) struct CompiledPolicyData {
|
|||||||
pub(crate) default_rules: Map<String, Vec<DefaultRuleInfo>>,
|
pub(crate) default_rules: Map<String, Vec<DefaultRuleInfo>>,
|
||||||
pub(crate) imports: BTreeMap<String, Ref<Expr>>,
|
pub(crate) imports: BTreeMap<String, Ref<Expr>>,
|
||||||
pub(crate) functions: FunctionTable,
|
pub(crate) functions: FunctionTable,
|
||||||
pub(crate) rule_paths: MapSet<String>,
|
pub(crate) rule_paths: Set<String>,
|
||||||
#[cfg(feature = "azure_policy")]
|
#[cfg(feature = "azure_policy")]
|
||||||
pub(crate) target_info: Option<TargetInfo>,
|
pub(crate) target_info: Option<TargetInfo>,
|
||||||
#[cfg(feature = "azure_policy")]
|
#[cfg(feature = "azure_policy")]
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ fn order_element_pairs<T: VariableBindingContext>(
|
|||||||
|
|
||||||
if ready {
|
if ready {
|
||||||
let (value_expr, plan, _deps, binds) = remaining.remove(idx);
|
let (value_expr, plan, _deps, binds) = remaining.remove(idx);
|
||||||
scheduled.extend(binds);
|
scheduled.extend(binds.into_iter());
|
||||||
ordered.push((value_expr, plan));
|
ordered.push((value_expr, plan));
|
||||||
progress = true;
|
progress = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
102
src/engine.rs
102
src/engine.rs
@@ -434,13 +434,7 @@ impl Engine {
|
|||||||
|
|
||||||
/// Add data document.
|
/// Add data document.
|
||||||
///
|
///
|
||||||
/// The specified data document is deep-merged into the existing data document. Nested
|
/// The specified data document is merged into existing data document.
|
||||||
/// objects are merged recursively (matching OPA's data-document merge), so adding
|
|
||||||
/// `{ "a": { "x": 1 } }` and then `{ "a": { "y": 2 } }` yields `{ "a": { "x": 1, "y": 2 } }`.
|
|
||||||
/// A conflict — the same path holding two different values — is an error.
|
|
||||||
///
|
|
||||||
/// The merge is atomic: if any conflict is detected (including one deep in a nested
|
|
||||||
/// document), the call fails and the existing data document is left unchanged.
|
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use regorus::*;
|
/// # use regorus::*;
|
||||||
@@ -459,13 +453,9 @@ impl Engine {
|
|||||||
/// // Merge { "z" : 3 }. Conflict error.
|
/// // Merge { "z" : 3 }. Conflict error.
|
||||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 3 }"#)?).is_err());
|
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 3 }"#)?).is_err());
|
||||||
///
|
///
|
||||||
/// // Nested objects are deep-merged. Merge { "y" : { "a" : 10 } } then { "y" : { "b" : 20 } }.
|
|
||||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "y" : { "a" : 10 } }"#)?).is_ok());
|
|
||||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "y" : { "b" : 20 } }"#)?).is_ok());
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// engine.eval_query("data".to_string(), false)?.result[0].expressions[0].value,
|
/// engine.eval_query("data".to_string(), false)?.result[0].expressions[0].value,
|
||||||
/// Value::from_json_str(r#"{ "x": 1, "y": { "a": 10, "b": 20 }, "z": 2}"#)?
|
/// Value::from_json_str(r#"{ "x": 1, "y": {}, "z": 2}"#)?
|
||||||
/// );
|
/// );
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
@@ -474,29 +464,8 @@ impl Engine {
|
|||||||
if data.as_object().is_err() {
|
if data.as_object().is_err() {
|
||||||
bail!("data must be object");
|
bail!("data must be object");
|
||||||
}
|
}
|
||||||
|
self.prepared = false;
|
||||||
// add_data is all-or-nothing; the atomic strategy differs by build because the failure
|
self.interpreter.get_init_data_mut().merge(data)
|
||||||
// modes do: a conflict (same path, differing values) is possible everywhere, an
|
|
||||||
// allocator-limit failure mid-merge only under `allocator-memory-limits`.
|
|
||||||
#[cfg(not(feature = "allocator-memory-limits"))]
|
|
||||||
{
|
|
||||||
// Conflict is the only failure mode; `check_mergeable` catches it up front without
|
|
||||||
// allocating, so validate then deep-merge in place (zero-copy fast path).
|
|
||||||
self.interpreter.get_init_data().check_mergeable(&data)?;
|
|
||||||
self.prepared = false;
|
|
||||||
self.interpreter.get_init_data_mut().deep_merge(data)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "allocator-memory-limits")]
|
|
||||||
{
|
|
||||||
// A limit failure can strike mid-merge and can't be predicted, so merge into a
|
|
||||||
// candidate and commit only on success. `Value` is copy-on-write, so only touched
|
|
||||||
// subtrees are cloned.
|
|
||||||
let mut candidate = self.interpreter.get_init_data().clone();
|
|
||||||
candidate.deep_merge(data)?;
|
|
||||||
*self.interpreter.get_init_data_mut() = candidate;
|
|
||||||
self.prepared = false;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the data document.
|
/// Get the data document.
|
||||||
@@ -536,6 +505,47 @@ impl Engine {
|
|||||||
self.add_data(Value::from_json_str(data_json)?)
|
self.add_data(Value::from_json_str(data_json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare the engine for evaluation without executing a query or rule.
|
||||||
|
///
|
||||||
|
/// The first evaluation on an unprepared engine performs one-time setup
|
||||||
|
/// (analysis, scheduling, imports/rules processing, and initialization of
|
||||||
|
/// internal evaluation structures). Calling this method performs that work
|
||||||
|
/// eagerly so a later call to [`Engine::eval_rule`] / [`Engine::eval_query`]
|
||||||
|
/// does not pay that startup cost.
|
||||||
|
///
|
||||||
|
/// This method is optional for correctness. If omitted, the first
|
||||||
|
/// evaluation will implicitly prepare the engine.
|
||||||
|
///
|
||||||
|
/// Preparation is invalidated when policy/data that affects evaluation is
|
||||||
|
/// changed (for example: [`Engine::add_policy`], [`Engine::add_policy_from_file`],
|
||||||
|
/// [`Engine::add_data`], [`Engine::clear_data`]). In those cases, the next
|
||||||
|
/// evaluation (or another explicit call to `prepare`) performs setup again.
|
||||||
|
///
|
||||||
|
/// This is especially useful before cloning template engines used for
|
||||||
|
/// repeated evaluations.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use regorus::*;
|
||||||
|
/// # fn main() -> anyhow::Result<()> {
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
/// engine.add_policy("test.rego".to_string(), r#"
|
||||||
|
/// package test
|
||||||
|
/// import rego.v1
|
||||||
|
/// allow if input.user == "alice"
|
||||||
|
/// "#.to_string())?;
|
||||||
|
///
|
||||||
|
/// engine.prepare()?;
|
||||||
|
/// let mut cloned = engine.clone();
|
||||||
|
///
|
||||||
|
/// cloned.set_input_json(r#"{"user":"alice"}"#)?;
|
||||||
|
/// assert_eq!(cloned.eval_rule("data.test.allow".to_string())?, Value::from(true));
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn prepare(&mut self) -> Result<()> {
|
||||||
|
self.prepare_for_eval(false, false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set whether builtins should raise errors strictly or not.
|
/// Set whether builtins should raise errors strictly or not.
|
||||||
///
|
///
|
||||||
/// Regorus differs from OPA in that by default builtins will
|
/// Regorus differs from OPA in that by default builtins will
|
||||||
@@ -1115,9 +1125,10 @@ impl Engine {
|
|||||||
limits::enforce_memory_limit().map_err(|err| anyhow!(err))?;
|
limits::enforce_memory_limit().map_err(|err| anyhow!(err))?;
|
||||||
|
|
||||||
self.interpreter.set_traces(enable_tracing);
|
self.interpreter.set_traces(enable_tracing);
|
||||||
|
let newly_prepared = !self.prepared;
|
||||||
|
|
||||||
// if the data/policies have changed or the interpreter has never been prepared
|
// if the data/policies have changed or the interpreter has never been prepared
|
||||||
if !self.prepared {
|
if newly_prepared {
|
||||||
// Analyze the modules and determine how statements must be scheduled.
|
// Analyze the modules and determine how statements must be scheduled.
|
||||||
let analyzer = Analyzer::new();
|
let analyzer = Analyzer::new();
|
||||||
let schedule = Rc::new(analyzer.analyze(&self.modules)?);
|
let schedule = Rc::new(analyzer.analyze(&self.modules)?);
|
||||||
@@ -1147,23 +1158,28 @@ impl Engine {
|
|||||||
|
|
||||||
// Set schedule after hoisting completes
|
// Set schedule after hoisting completes
|
||||||
self.interpreter.set_schedule(Some(schedule));
|
self.interpreter.set_schedule(Some(schedule));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "azure_policy")]
|
#[cfg(feature = "azure_policy")]
|
||||||
|
{
|
||||||
if for_target {
|
if for_target {
|
||||||
// Resolve and validate target specifications across all modules
|
// Resolve and validate target specifications across all modules.
|
||||||
|
// This must run for target-aware compilation even if generic prepare()
|
||||||
|
// was already called.
|
||||||
crate::interpreter::target::resolve::resolve_and_apply_target(
|
crate::interpreter::target::resolve::resolve_and_apply_target(
|
||||||
&mut self.interpreter,
|
&mut self.interpreter,
|
||||||
)?;
|
)?;
|
||||||
// Infer resource types
|
// Infer resource types
|
||||||
crate::interpreter::target::infer::infer_resource_type(&mut self.interpreter)?;
|
crate::interpreter::target::infer::infer_resource_type(&mut self.interpreter)?;
|
||||||
}
|
} else if newly_prepared {
|
||||||
|
// Check if any module specifies a target and warn if so.
|
||||||
if !for_target {
|
|
||||||
// Check if any module specifies a target and warn if so
|
|
||||||
#[cfg(feature = "azure_policy")]
|
|
||||||
self.warn_if_targets_present();
|
self.warn_if_targets_present();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "azure_policy"))]
|
||||||
|
let _ = for_target;
|
||||||
|
|
||||||
|
if newly_prepared {
|
||||||
self.prepared = true;
|
self.prepared = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ use crate::{Expression, Extension, Location, QueryResult, QueryResults};
|
|||||||
use crate::query::traversal::traverse;
|
use crate::query::traversal::traverse;
|
||||||
|
|
||||||
use crate::Rc;
|
use crate::Rc;
|
||||||
|
use alloc::collections::btree_map::Entry as BTreeMapEntry;
|
||||||
use alloc::collections::{BTreeMap, BTreeSet};
|
use alloc::collections::{BTreeMap, BTreeSet};
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use core::ops::Bound::*;
|
use core::ops::Bound::*;
|
||||||
@@ -60,17 +61,6 @@ enum FunctionModifier {
|
|||||||
Value(Value),
|
Value(Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How [`Interpreter::update_data`] merges a rule's value into the data document.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum RuleValueMerge {
|
|
||||||
/// Shallow-merge keeping disjoint keys, so rules sharing a path prefix scaffold into one
|
|
||||||
/// object (`a.foo` + `a.bar` → one `a`) instead of conflicting.
|
|
||||||
Combine,
|
|
||||||
/// Complete-rule semantics: existing value must be absent or exactly equal, else conflict.
|
|
||||||
/// Used for zero-arg function outputs (`f() := …`), which OPA treats like complete rules.
|
|
||||||
Strict,
|
|
||||||
}
|
|
||||||
|
|
||||||
type RuleValues = BTreeMap<Vec<Value>, (Value, Ref<Expr>)>;
|
type RuleValues = BTreeMap<Vec<Value>, (Value, Ref<Expr>)>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -1258,34 +1248,7 @@ impl Interpreter {
|
|||||||
// Apply with modifiers.
|
// Apply with modifiers.
|
||||||
for wm in &stmt.with_mods {
|
for wm in &stmt.with_mods {
|
||||||
let path = Parser::get_path_ref_components(&wm.refr)?;
|
let path = Parser::get_path_ref_components(&wm.refr)?;
|
||||||
let mut path: Vec<String> = path.iter().map(|s| s.text().to_string()).collect();
|
let path: Vec<&str> = path.iter().map(|s| s.text()).collect();
|
||||||
|
|
||||||
// Matching OPA, a leading import alias is rewritten before
|
|
||||||
// any lookups: functions register as overrides below,
|
|
||||||
// anything else becomes a data override. Only the alias
|
|
||||||
// component is replaced so bracketed keys containing dots
|
|
||||||
// survive the rewrite.
|
|
||||||
let rewritten: Option<Vec<String>> = match path.split_first() {
|
|
||||||
Some((head, rest)) if head.as_str() != "data" => {
|
|
||||||
self.lookup_import(head).and_then(|import_expr| {
|
|
||||||
// Use the import target's parsed components, not
|
|
||||||
// its dot-joined string, so bracketed keys
|
|
||||||
// containing dots survive in the import path too.
|
|
||||||
let comps = Parser::get_path_ref_components(import_expr).ok()?;
|
|
||||||
Some(
|
|
||||||
comps
|
|
||||||
.iter()
|
|
||||||
.map(|s| s.text().to_string())
|
|
||||||
.chain(rest.iter().cloned())
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
if let Some(new_path) = rewritten {
|
|
||||||
path = new_path;
|
|
||||||
}
|
|
||||||
let mut target = path.join(".");
|
let mut target = path.join(".");
|
||||||
|
|
||||||
let mut target_is_function = self.lookup_function_by_name(&target).is_some()
|
let mut target_is_function = self.lookup_function_by_name(&target).is_some()
|
||||||
@@ -1324,17 +1287,11 @@ impl Interpreter {
|
|||||||
if self.lookup_function_by_name(&function_path).is_none() {
|
if self.lookup_function_by_name(&function_path).is_none() {
|
||||||
// Lookup without current module path prefixed.
|
// Lookup without current module path prefixed.
|
||||||
function_path = get_path_string(&wm.r#as, None)?;
|
function_path = get_path_string(&wm.r#as, None)?;
|
||||||
if self.lookup_function_by_name(&function_path).is_none() {
|
if self.lookup_function_by_name(&function_path).is_none()
|
||||||
// Resolve an aliased replacement before builtins.
|
&& !Self::is_builtin(wm.r#as.span(), &function_path)
|
||||||
let resolved = self
|
{
|
||||||
.resolve_fcn_path_through_imports(&function_path)
|
// bail!(wm.r#as.span().error("could not evaluate expression"));
|
||||||
.filter(|r| self.compiled_policy.functions.contains_key(r));
|
skip_exec = true;
|
||||||
if let Some(resolved) = resolved {
|
|
||||||
function_path = resolved;
|
|
||||||
} else if !Self::is_builtin(wm.r#as.span(), &function_path) {
|
|
||||||
// bail!(wm.r#as.span().error("could not evaluate expression"));
|
|
||||||
skip_exec = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.with_functions
|
self.with_functions
|
||||||
@@ -1355,10 +1312,10 @@ impl Interpreter {
|
|||||||
*obj = Value::new_object();
|
*obj = Value::new_object();
|
||||||
}
|
}
|
||||||
|
|
||||||
obj = obj.as_object_mut()?.get_or_insert_with(
|
obj = obj
|
||||||
Value::String(p.to_string().into()),
|
.as_object_mut()?
|
||||||
Value::new_object,
|
.entry(Value::String(p.to_string().into()))
|
||||||
);
|
.or_insert(Value::new_object());
|
||||||
}
|
}
|
||||||
*obj = value;
|
*obj = value;
|
||||||
// Mark modified rules as processed.
|
// Mark modified rules as processed.
|
||||||
@@ -1725,7 +1682,8 @@ impl Interpreter {
|
|||||||
let set = obj
|
let set = obj
|
||||||
.as_object_mut()
|
.as_object_mut()
|
||||||
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
|
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
|
||||||
.get_or_insert_with(p, Value::new_set)
|
.entry(p)
|
||||||
|
.or_insert(Value::new_set())
|
||||||
.as_set_mut()
|
.as_set_mut()
|
||||||
.map_err(|_| anyhow!(span.error("previous value is not a set")))?;
|
.map_err(|_| anyhow!(span.error("previous value is not a set")))?;
|
||||||
set.append(value.as_set_mut()?);
|
set.append(value.as_set_mut()?);
|
||||||
@@ -1733,13 +1691,20 @@ impl Interpreter {
|
|||||||
let obj = obj
|
let obj = obj
|
||||||
.as_object_mut()
|
.as_object_mut()
|
||||||
.map_err(|_| anyhow!(span.error("previous value is not an object")))?;
|
.map_err(|_| anyhow!(span.error("previous value is not an object")))?;
|
||||||
if value == Value::Undefined {
|
match obj.entry(p) {
|
||||||
// TODO: clean this assumption between Undefined vs Object.
|
BTreeMapEntry::Vacant(v) => {
|
||||||
obj.get_or_insert_with(p, Value::new_object);
|
if value != Value::Undefined {
|
||||||
} else {
|
v.insert(value);
|
||||||
let existing = obj.get_or_insert_with(p, || value.clone());
|
} else {
|
||||||
if *existing != value {
|
// TODO: clean this assumption between Undefined vs Object.
|
||||||
bail!(span.error("complete rules should not produce multiple outputs"))
|
v.insert(Value::new_object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BTreeMapEntry::Occupied(o) => {
|
||||||
|
if o.get() != &value && value != Value::Undefined {
|
||||||
|
bail!(span
|
||||||
|
.error("complete rules should not produce multiple outputs"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1748,7 +1713,8 @@ impl Interpreter {
|
|||||||
obj = obj
|
obj = obj
|
||||||
.as_object_mut()
|
.as_object_mut()
|
||||||
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
|
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
|
||||||
.get_or_insert_with(p, Value::new_object);
|
.entry(p)
|
||||||
|
.or_insert(Value::new_object());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1816,7 +1782,6 @@ 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 {
|
||||||
@@ -1856,7 +1821,8 @@ impl Interpreter {
|
|||||||
let set = ctx_mut
|
let set = ctx_mut
|
||||||
.rule_value
|
.rule_value
|
||||||
.as_object_mut()?
|
.as_object_mut()?
|
||||||
.get_or_insert_with(Value::from_array(comps), Value::new_set);
|
.entry(Value::from_array(comps))
|
||||||
|
.or_insert(Value::new_set());
|
||||||
if output != Value::Undefined {
|
if output != Value::Undefined {
|
||||||
set.as_set_mut()?.insert(output);
|
set.as_set_mut()?.insert(output);
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
@@ -1865,13 +1831,20 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Non-set rule.
|
// Non-set rule.
|
||||||
let key = Value::from_array(comps);
|
match ctx_mut
|
||||||
let obj_mut = ctx_mut.rule_value.as_object_mut()?;
|
.rule_value
|
||||||
let existing = obj_mut.get_or_insert_with(key, || output.clone());
|
.as_object_mut()?
|
||||||
if *existing != output {
|
.entry(Value::from_array(comps))
|
||||||
bail!(rule_ref
|
{
|
||||||
|
BTreeMapEntry::Vacant(v) => {
|
||||||
|
v.insert(output);
|
||||||
|
}
|
||||||
|
BTreeMapEntry::Occupied(o) if o.get() != &output => bail!(rule_ref
|
||||||
.span()
|
.span()
|
||||||
.error("rules must not produce multiple outputs"));
|
.error("rules must not produce multiple outputs")),
|
||||||
|
_ => {
|
||||||
|
// Rule produced same value.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
@@ -2404,72 +2377,6 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the import of the current module with the given alias, e.g.
|
|
||||||
/// the `data.a.b` import expression for `b` after `import data.a.b`.
|
|
||||||
fn lookup_import(&self, alias: &str) -> Option<&Ref<Expr>> {
|
|
||||||
if self.compiled_policy.imports.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let import_key = format!("{}.{}", self.current_module_path, alias);
|
|
||||||
self.compiled_policy.imports.get(&import_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look up the dot-joined target path of an import of the current module
|
|
||||||
/// with the given alias, e.g. `data.a.b` for `b` after `import data.a.b`.
|
|
||||||
fn lookup_import_alias(&self, alias: &str) -> Option<String> {
|
|
||||||
get_path_string(self.lookup_import(alias)?, None).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rewrite a path whose leading component is an import alias of the
|
|
||||||
/// current module to the import's target, e.g. `b.f` to `data.a.b.f`
|
|
||||||
/// after `import data.a.b`.
|
|
||||||
fn rewrite_path_through_imports(&self, path: &str) -> Option<String> {
|
|
||||||
if path.starts_with("data.") {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (alias, rest) = match path.split_once('.') {
|
|
||||||
Some((alias, rest)) => (alias, Some(rest)),
|
|
||||||
None => (path, None),
|
|
||||||
};
|
|
||||||
let target = self.lookup_import_alias(alias)?;
|
|
||||||
Some(match rest {
|
|
||||||
Some(rest) => format!("{target}.{rest}"),
|
|
||||||
None => target,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rewrite an import-aliased call path to its target, e.g. `b.f(1)` to
|
|
||||||
/// `data.a.b.f` after `import data.a.b`. Resolves only when the target is
|
|
||||||
/// a known function or default function, so an alias whose target defines
|
|
||||||
/// the called function shadows a like-named builtin namespace, while other
|
|
||||||
/// spellings keep their prior meaning (e.g. a builtin call). OPA instead
|
|
||||||
/// rewrites aliases unconditionally and rejects calls to a missing target
|
|
||||||
/// at compile time.
|
|
||||||
fn resolve_fcn_path_through_imports(&self, path: &str) -> Option<String> {
|
|
||||||
let candidate = self.rewrite_path_through_imports(path)?;
|
|
||||||
(self.compiled_policy.functions.contains_key(&candidate)
|
|
||||||
|| self.is_default_function(&candidate))
|
|
||||||
.then_some(candidate)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// True if `path` is the exact path of a `default` function rule.
|
|
||||||
/// `default_rules` also indexes every prefix of a rule path, so it cannot
|
|
||||||
/// be consulted alone: `rule_paths` holds only exact rule paths, and the
|
|
||||||
/// non-empty argument list distinguishes functions from value rules.
|
|
||||||
fn is_default_function(&self, path: &str) -> bool {
|
|
||||||
self.compiled_policy.rule_paths.contains(path)
|
|
||||||
&& self
|
|
||||||
.compiled_policy
|
|
||||||
.default_rules
|
|
||||||
.get(path)
|
|
||||||
.is_some_and(|rules| {
|
|
||||||
rules.iter().any(|(rule, _)| {
|
|
||||||
matches!(rule.as_ref(), Rule::Default { args, .. } if !args.is_empty())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_builtin_call(
|
fn eval_builtin_call(
|
||||||
&mut self,
|
&mut self,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
@@ -2563,7 +2470,7 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
Value::Object(map) => {
|
Value::Object(map) => {
|
||||||
s.push('{');
|
s.push('{');
|
||||||
for (idx, (k, entry_value)) in map.iter_sorted().enumerate() {
|
for (idx, (k, entry_value)) in map.iter().enumerate() {
|
||||||
if idx > 0 {
|
if idx > 0 {
|
||||||
s.push_str(", ");
|
s.push_str(", ");
|
||||||
}
|
}
|
||||||
@@ -2641,13 +2548,6 @@ impl Interpreter {
|
|||||||
param_values.push(self.eval_expr(p)?);
|
param_values.push(self.eval_expr(p)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve a leading import alias before the `with` override and builtin
|
|
||||||
// lookups, so an override keyed by the full path reaches aliased calls
|
|
||||||
// and the alias shadows a like-named builtin namespace (matching OPA).
|
|
||||||
let fcn_path = self
|
|
||||||
.resolve_fcn_path_through_imports(&fcn_path)
|
|
||||||
.unwrap_or(fcn_path);
|
|
||||||
|
|
||||||
let orig_fcn_path = fcn_path.clone();
|
let orig_fcn_path = fcn_path.clone();
|
||||||
|
|
||||||
let mut with_functions_saved = None;
|
let mut with_functions_saved = None;
|
||||||
@@ -2824,12 +2724,7 @@ impl Interpreter {
|
|||||||
let value = match self.eval_rule_bodies(ctx, span, bodies) {
|
let value = match self.eval_rule_bodies(ctx, span, bodies) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// If the rule produces an error, save the error. Restore
|
// If the rule produces an error, save the error.
|
||||||
// the caller's module even so: leaving the callee's module
|
|
||||||
// in place would make the rest of the caller's body
|
|
||||||
// resolve paths through the wrong module's imports when
|
|
||||||
// the error is swallowed below in non-strict mode.
|
|
||||||
self.set_current_module(prev_module)?;
|
|
||||||
errors.push(e);
|
errors.push(e);
|
||||||
self.scopes = scopes;
|
self.scopes = scopes;
|
||||||
continue;
|
continue;
|
||||||
@@ -3530,23 +3425,6 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Materialize a complete-rule value: the existing value must be absent or *exactly equal*
|
|
||||||
/// to `new`, else it is a conflict.
|
|
||||||
///
|
|
||||||
/// Unlike the shallow [`Self::merge_rule_value`], differing outputs conflict instead of
|
|
||||||
/// combining — `f() := {"a": 1}` and `f() := {"b": 2}` conflict — matching OPA's semantics
|
|
||||||
/// for zero-arg functions.
|
|
||||||
fn merge_rule_value_strict(span: &Span, value: &mut Value, new: Value) -> Result<()> {
|
|
||||||
if *value == Value::Undefined {
|
|
||||||
*value = new;
|
|
||||||
Ok(())
|
|
||||||
} else if *value == new {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(span.error("rules should not produce multiple outputs."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result<String> {
|
pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result<String> {
|
||||||
let mut comps = vec![];
|
let mut comps = vec![];
|
||||||
let mut expr_opt = Some(refr);
|
let mut expr_opt = Some(refr);
|
||||||
@@ -3802,7 +3680,6 @@ impl Interpreter {
|
|||||||
_refr: &Expr,
|
_refr: &Expr,
|
||||||
path: &[&str],
|
path: &[&str],
|
||||||
value: Value,
|
value: Value,
|
||||||
merge: RuleValueMerge,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if value == Value::Undefined {
|
if value == Value::Undefined {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -3810,10 +3687,7 @@ impl Interpreter {
|
|||||||
// Ensure that path is created.
|
// Ensure that path is created.
|
||||||
let vref = Self::make_or_get_value_mut(&mut self.data, path)?;
|
let vref = Self::make_or_get_value_mut(&mut self.data, path)?;
|
||||||
if Self::get_value_chained(self.init_data.clone(), path) == Value::Undefined {
|
if Self::get_value_chained(self.init_data.clone(), path) == Value::Undefined {
|
||||||
match merge {
|
Self::merge_rule_value(span, vref, value)
|
||||||
RuleValueMerge::Strict => Self::merge_rule_value_strict(span, vref, value),
|
|
||||||
RuleValueMerge::Combine => Self::merge_rule_value(span, vref, value),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Retain specified value.
|
// Retain specified value.
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -3921,13 +3795,7 @@ impl Interpreter {
|
|||||||
// `a` is created as an empty object.
|
// `a` is created as an empty object.
|
||||||
if let Some((_, prefix)) = path.split_last() {
|
if let Some((_, prefix)) = path.split_last() {
|
||||||
if !prefix.is_empty() {
|
if !prefix.is_empty() {
|
||||||
self.update_data(
|
self.update_data(span, refr, prefix, Value::new_object())?;
|
||||||
span,
|
|
||||||
refr,
|
|
||||||
prefix,
|
|
||||||
Value::new_object(),
|
|
||||||
RuleValueMerge::Combine,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3939,13 +3807,7 @@ impl Interpreter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let value = self.eval_rule_bodies(ctx, span, rule_body)?;
|
let value = self.eval_rule_bodies(ctx, span, rule_body)?;
|
||||||
self.update_data(
|
self.update_data(refr.span(), refr, &path[..], value)?;
|
||||||
refr.span(),
|
|
||||||
refr,
|
|
||||||
&path[..],
|
|
||||||
value,
|
|
||||||
RuleValueMerge::Strict,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4192,7 +4054,6 @@ impl Interpreter {
|
|||||||
rule_refr,
|
rule_refr,
|
||||||
&prefix_path,
|
&prefix_path,
|
||||||
Value::new_object(),
|
Value::new_object(),
|
||||||
RuleValueMerge::Combine,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,10 +213,10 @@ pub fn denormalize_with_aliases(
|
|||||||
// Phase 4: Attach properties to result.
|
// Phase 4: Attach properties to result.
|
||||||
if !properties.is_empty() {
|
if !properties.is_empty() {
|
||||||
if let Some(Value::Object(existing_rc)) = result.get_mut("properties") {
|
if let Some(Value::Object(existing_rc)) = result.get_mut("properties") {
|
||||||
// Merge directly into the Object, avoiding full ObjMap round-trip.
|
// Merge directly into the BTreeMap, avoiding full ObjMap round-trip.
|
||||||
let existing = Rc::make_mut(existing_rc);
|
let existing = Rc::make_mut(existing_rc);
|
||||||
for (k, v) in properties {
|
for (k, v) in properties {
|
||||||
existing.get_or_insert_with(Value::String(k), || v);
|
existing.entry(Value::String(k)).or_insert(v);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
obj_insert(&mut result, "properties", make_value(properties));
|
obj_insert(&mut result, "properties", make_value(properties));
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user