Compare commits

...

26 Commits

Author SHA1 Message Date
Anand Krishnamoorthi
f98865fc98 chore(release): release regorus v0.11.0 (#766)
Release the core `regorus` crate as v0.11.0 (up from v0.10.1) and align
every language binding to the same version.

This release carries an API-breaking change (flagged by
cargo-semver-checks), so it takes a minor bump under the 0.x SemVer
convention.

Highlights since v0.10.1:

- fix(rvm): assert every-quantifier results so failing cases don't pass
  (#765)
- fix: deep-merge nested data documents in Engine::add_data (#760)
- feat(compiler): support registered host-await builtins for natural
  function call syntax (#667)
- feat(value): introduce Set/Object storage abstractions (#740, #735,
  #736)
- security: reject data nested beyond 128 levels to avoid stack overflow

Version updates:

- Core crate (Cargo.toml/Cargo.lock) 0.10.1 -> 0.11.0
- Bindings aligned via `cargo xtask bindings`: ffi, java, python, wasm,
  ruby, csharp (manifests, lockfiles, pom.xml, Directory.Packages.props,
  version.rb)
- CHANGELOG.md updated with the 0.11.0 section
2026-07-21 17:06:44 -05:00
Anand Krishnamoorthi
6ef5e74eb2 fix(rvm): assert every-quantifier results so failing cases don't pass (#765)
The RVM was silently succeeding on `every` quantifiers (and loops nested
inside an `every` body) that should have failed. In each case the loop
computed a pass/fail into a register that the surrounding query then
ignored, so the RVM disagreed with the interpreter.

Four related fixes:

- compile_every_quantifier: guard the loop result so a failing `every`
  body makes the rule undefined instead of always succeeding.

- resolve_iteration_state: `every` over a non-iterable scalar (number,
  string, bool, null, undefined) is now undefined, not vacuously true.
  Only genuinely empty collections stay true; any/forEach are untouched.

- a `some ... in` inside an `every` body now guards its loop result, so
  a `some` that matches nothing fails the current iteration. Top-level
  rule bodies still rely on context yields and are unaffected.

- a hoisted index iteration (`some i` / `arr[i]`) inside an `every` body
  gets the same guard.

Also drop `every` from OPA_TODO_FOLDERS so the interpreter-vs-RVM
differential suite covers it, add an OPA_UNSKIP_FOLDERS env override for
auditing other still-skipped folders, and add regression cases for every
variant above.
2026-07-21 15:43:42 -05:00
Mark Birger
9a486c79bf fix: Deep-merge nested data documents in Engine::add_data (#760)
* Deep-merge nested data documents in Engine::add_data

add_data previously performed a shallow merge: adding a nested object under a key that already existed either replaced the whole subtree or errored on a spurious conflict, instead of merging the trees. This makes Engine::add_data (and the shared Value::merge) recurse into nested objects so keys from both sides are preserved, matching OPA's data-document merge semantics. Nested sets are unioned as a regorus extension (OPA data is JSON and has no sets). Genuine leaf conflicts (same path, two different scalar values) still error; equal values remain a no-op, which the shared rule-evaluation path relies on. Adds tests for object deep-merge, set union, leaf/type conflicts, and interaction with the 'with data.x' modifier.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs(value): clarify Value::merge conflict wording

Copilot review on #760 noted the doc comment called non-mergeable variants 'non-container values', which is misleading since arrays are containers yet still conflict unless equal. Reword to describe a conflict as any differing pair that is not both objects or both sets (e.g. unequal scalars or arrays).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* perf(value): avoid deep-cloning RHS set during merge union

When unioning sets in Value::merge, the RHS set is often shared: the object arm recurses via existing.merge(v.clone()), which bumps the incoming set's Rc refcount. The old Rc::make_mut(new) then structurally deep-cloned the entire RHS BTreeSet just to drain it via append and immediately discard the copy.

Move the elements out when the RHS set is uniquely owned, and otherwise clone only the per-element Rc handles into the destination. The union result is identical (BTreeSet dedups), but no throwaway set is allocated on the nested-merge path exercised by add_data deep-merge.

Addresses a Copilot review comment on #760.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(engine): make add_data atomic on merge conflict

Now that Value::merge recurses, a conflict in a later nested key was reported only after earlier keys of the same document had already been written into the live init_data, leaving the engine partially mutated on a rejected add_data.

Add a read-only Value::check_mergeable that mirrors merge's conflict rule (objects deep-merge, sets union, equal values no-op, anything else conflicts) and run it in add_data before merging. On conflict nothing is mutated, so add_data is all-or-nothing. The check allocates nothing and never copies the data spine, preserving merge's in-place uniquely-owned fast path (no candidate copy of the data document).

Adds regression tests for a partial object-leaf conflict and a partial set-union conflict. Reported by a maintainer on #760.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* test(engine): add array atomicity regression for add_data

Arrays are atomic leaves, so a differing array at a shared path is a
conflict. The new key sorts before the conflicting array key, so a naive
in-place merge would leak the new key before hitting the conflict. This
test locks in that add_data rejects the whole call and leaves data
untouched.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix: make add_data atomic under allocator memory limits

On llocator-memory-limits builds, Value::merge runs the limit check
*after* inserting each key, so an add_data whose merge trips the limit
mid-way left the data document partially mutated. check_mergeable only
models semantic conflicts, not limit failures, so the validate-then-merge
precheck couldn't cover this failure mode.

Use a build-split strategy in dd_data:
- default builds: keep the zero-copy validate-then-merge fast path
  (a conflict is the only way the merge can fail).
- allocator-memory-limits builds: merge into a candidate copy and commit
  only on success, making both conflict and limit failures transactional.
  Value is Rc/copy-on-write, so only touched subtrees are cloned.

check_mergeable is now cfg-gated to the default build to avoid dead code.

Tests (allocator-memory-limits build): add a partial-merge atomicity test
(limit trips mid-merge, data must be untouched) and a candidate-copy
conflict-atomicity test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix: separate strict rule-output merge from data-document deep-merge

#760 made Value::merge recursive so Engine::add_data deep-merges nested
data documents. But that same method also backs rule materialization,
where recursion is wrong: two rule definitions producing different
outputs for one path must conflict (OPA complete-rule semantics), not
silently combine.

Split the two behaviors:
- Value::merge is strict and shallow again (as pre-#760): a key on both
  sides must be equal or it conflicts; used for rule outputs.
- Value::deep_merge is the recursive data-document merge behind add_data;
  check_mergeable validates it up front without allocating, so the
  default build merges in place instead of cloning a candidate.

Also fix zero-arg functions (f() := ...): route their materialization
through strict equality via a new RuleValueMerge selector, so disjoint
outputs ({a:1} vs {b:2}) conflict as OPA does while prefix scaffolding
(a.foo + a.bar) still combines.

Add a 14-case interpreter conformance matrix (multiple_outputs.yaml)
covering functions, static/dynamic partial objects, and ref-heads,
matched against OPA v1.2.0.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* perf(value): make deep_merge acquire mutable access lazily

deep_merge's object arm called Rc::make_mut on the target map up front,
cloning a shared map's spine even when the merge changed nothing (a
no-op subset re-add) or conflicted before any mutation. Decide each
incoming key from a read-only probe (skip / insert / recurse / conflict)
and take Rc::make_mut only when a key actually mutates, so no-op and
conflict merges leave shared maps untouched.

Behavior is unchanged: the equality short-circuit that previously ran
inside the recursive call now runs in the probe, and conflicts bail with
the same message. Add value tests asserting Rc::ptr_eq is preserved
across no-op subset, equal-nested-object, and first-key-conflict merges.

OPA conformance unchanged (3021 pass / 651 fail, byte-identical).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* feat(value): bound deep_merge recursion depth to prevent stack-overflow DoS

deep_merge and check_mergeable recursed unbounded on object/set nesting.
A Value built without serde_json's parse-time recursion limit (the Python
and Ruby native bindings, or programmatic construction) could therefore
drive add_data into a stack overflow -- an uncatchable abort that poisons
every engine in an FFI process.

Thread a depth counter through both functions and bail past MAX_MERGE_DEPTH
(128, matching serde_json's default) so over-deep data fails with a clean
Err. In the default build check_mergeable trips first, keeping add_data
atomic; the guard in deep_merge covers the allocator-memory-limits build
and any disjoint-then-overlapping merge.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs(changelog): note strict zero-arg function conflict and add_data depth limit

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Mark Birger <markbirger@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-21 15:18:00 -05:00
dependabot[bot]
9838b25fb7 build(deps): bump the rust-dependencies group across 5 directories with 11 updates (#764)
* build(deps): bump the rust-dependencies group across 5 directories with 11 updates

Bumps the rust-dependencies group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.102` | `1.0.103` |
| [num-bigint](https://github.com/rust-num/num-bigint) | `0.4.6` | `0.5.1` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.12.1` | `0.12.2` |
| [globset](https://github.com/BurntSushi/ripgrep) | `0.4.18` | `0.4.19` |
| [regex](https://github.com/rust-lang/regex) | `1.12.4` | `1.13.1` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.4` | `1.24.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.46.6` | `0.47.0` |
| [rand](https://github.com/rust-random/rand) | `0.10.1` | `0.10.2` |
| [lru](https://github.com/jeromefroe/lru-rs) | `0.18.0` | `0.18.1` |
| [clap](https://github.com/clap-rs/clap) | `4.6.1` | `4.6.2` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.25.12+spec-1.1.0` | `0.25.13+spec-1.1.0` |

Bumps the rust-dependencies group with 10 updates in the /bindings/ffi directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.102` | `1.0.103` |
| [num-bigint](https://github.com/rust-num/num-bigint) | `0.4.6` | `0.5.1` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.12.1` | `0.12.2` |
| [globset](https://github.com/BurntSushi/ripgrep) | `0.4.18` | `0.4.19` |
| [regex](https://github.com/rust-lang/regex) | `1.12.4` | `1.13.1` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.4` | `1.24.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.46.6` | `0.47.0` |
| [rand](https://github.com/rust-random/rand) | `0.10.1` | `0.10.2` |
| [lru](https://github.com/jeromefroe/lru-rs) | `0.18.0` | `0.18.1` |
| [clap](https://github.com/clap-rs/clap) | `4.6.1` | `4.6.2` |

Bumps the rust-dependencies group with 9 updates in the /bindings/java directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.102` | `1.0.103` |
| [num-bigint](https://github.com/rust-num/num-bigint) | `0.4.6` | `0.5.1` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.12.1` | `0.12.2` |
| [globset](https://github.com/BurntSushi/ripgrep) | `0.4.18` | `0.4.19` |
| [regex](https://github.com/rust-lang/regex) | `1.12.4` | `1.13.1` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.4` | `1.24.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.46.6` | `0.47.0` |
| [rand](https://github.com/rust-random/rand) | `0.10.1` | `0.10.2` |
| [lru](https://github.com/jeromefroe/lru-rs) | `0.18.0` | `0.18.1` |

Bumps the rust-dependencies group with 9 updates in the /bindings/python directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.102` | `1.0.103` |
| [num-bigint](https://github.com/rust-num/num-bigint) | `0.4.6` | `0.5.1` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.12.1` | `0.12.2` |
| [globset](https://github.com/BurntSushi/ripgrep) | `0.4.18` | `0.4.19` |
| [regex](https://github.com/rust-lang/regex) | `1.12.4` | `1.13.1` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.4` | `1.24.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.46.6` | `0.47.0` |
| [rand](https://github.com/rust-random/rand) | `0.10.1` | `0.10.2` |
| [lru](https://github.com/jeromefroe/lru-rs) | `0.18.0` | `0.18.1` |

Bumps the rust-dependencies group with 9 updates in the /bindings/wasm directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.102` | `1.0.103` |
| [num-bigint](https://github.com/rust-num/num-bigint) | `0.4.6` | `0.5.1` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.12.1` | `0.12.2` |
| [globset](https://github.com/BurntSushi/ripgrep) | `0.4.18` | `0.4.19` |
| [regex](https://github.com/rust-lang/regex) | `1.12.4` | `1.13.1` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.4` | `1.24.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.46.6` | `0.47.0` |
| [rand](https://github.com/rust-random/rand) | `0.10.1` | `0.10.2` |
| [lru](https://github.com/jeromefroe/lru-rs) | `0.18.0` | `0.18.1` |



Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `clap` from 4.6.1 to 4.6.2
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.1...clap_complete-v4.6.2)

Updates `toml_edit` from 0.25.12+spec-1.1.0 to 0.25.13+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.25.12...v0.25.13)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `clap` from 4.6.1 to 4.6.2
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.1...clap_complete-v4.6.2)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `clap` from 4.6.1 to 4.6.2
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.1...clap_complete-v4.6.2)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `clap` from 4.6.1 to 4.6.2
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.6.1...clap_complete-v4.6.2)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

Updates `uuid` from 1.23.4 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.24.0)

Updates `anyhow` from 1.0.102 to 1.0.103
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.102...1.0.103)

Updates `num-bigint` from 0.4.6 to 0.5.1
- [Changelog](https://github.com/rust-num/num-bigint/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-num/num-bigint/compare/num-bigint-0.4.6...num-bigint-0.5.1)

Updates `spin` from 0.12.1 to 0.12.2
- [Changelog](https://github.com/zesterer/spin-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mvdnes/spin-rs/commits)

Updates `globset` from 0.4.18 to 0.4.19
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.18...globset-0.4.19)

Updates `regex` from 1.12.4 to 1.13.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.1)

Updates `jsonschema` from 0.46.6 to 0.47.0
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.6...ruby-v0.47.0)

Updates `rand` from 0.10.1 to 0.10.2
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.1...0.10.2)

Updates `lru` from 0.18.0 to 0.18.1
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.18.0...0.18.1)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: num-bigint
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: globset
  dependency-version: 0.4.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: clap
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: toml_edit
  dependency-version: 0.25.13+spec-1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: num-bigint
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: globset
  dependency-version: 0.4.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: clap
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: num-bigint
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: globset
  dependency-version: 0.4.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: num-bigint
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: globset
  dependency-version: 0.4.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: num-bigint
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: globset
  dependency-version: 0.4.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: num-bigint
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: globset
  dependency-version: 0.4.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: clap
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.103
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: num-bigint
  dependency-version: 0.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.2
  dependency-type: direct:production
  update-type: version-update:semve...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): refresh Cargo lockfiles

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-21 12:39:19 -05:00
Mark Birger
f0acc64195 feat(compiler): support registered host-await builtins for natural function call syntax (#667)
* feat(compiler): support registered host-await builtins

Allow hosts to register function names at compile time so that calls to
those names emit HostAwait instructions directly, enabling natural syntax
like fetch(x) instead of __builtin_host_await(x, "fetch").

- Add host_await_builtins map and register_host_await_builtin() to Compiler
- Validate arg_count == 1 and reject reserved __builtin_host_await name
- Extend determine_call_target() resolution: explicit > registered > user > builtin
- Both explicit and registered paths emit identical HostAwait bytecode
- Add compile_from_policy_with_host_await() entry point in rules.rs
- Extended test harness with HostAwaitBuiltinSpec and args assertion
- 9 YAML test cases: suspend/resume, run-to-completion, multiple names,
  queue, shadowing, object packing, arg_count rejection, reserved name
  rejection, standard builtin override
- Documentation: instruction-set.md, architecture.md

* Update src/languages/rego/compiler/function_calls.rs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Mark Birger <birgerm@yandex.ru>

* fix(compiler): address PR #667 review feedback on host-await registration

- Compiler::register_host_await_builtin now rejects duplicate, empty,
  and whitespace-only names. Previously a duplicate registration would
  silently overwrite the existing entry, which could mask the host's
  own registration mistakes.
- YAML test cases added: empty registration list as no-op, duplicate
  name rejection, empty/whitespace name rejection, out-param (a, out)
  calling syntax with a single-arg registered builtin, and mixed
  __builtin_host_await + registered builtins in the same policy
  consuming from their respective identifier queues.
- Test harness: replace assert_eq! on HostAwait argument mismatch with
  anyhow::Error so mismatches propagate through the case reporter
  instead of panicking and skipping the harness's normal error path.
- YAML comment fix: "Registration panics" -> "Registration fails with
  an error" (registration returns Err, never panics).

Addresses anakrish + Copilot inline review comments on PR #667.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* compiler: split CallTarget::HostAwait into explicit and registered variants

Addresses PR #667 review item #8: at the emit site in
`compile_function_call`, the discrimination between explicit
`__builtin_host_await(arg, id)` and a registered host-awaitable
builtin was being recovered by string-comparing `original_fcn_path`
against `"__builtin_host_await"`. The information was already known
in `determine_call_target` and was being thrown away.

Replace the single `CallTarget::HostAwait` variant with two:

* `ExplicitHostAwait` (unit) — the two-argument call form. The
  identifier register comes from the user's second argument.
* `RegisteredHostAwait { identifier: String }` — the one-argument
  call form for registered builtins. The identifier is the registered
  name and is captured in the variant at recognition time, so the
  emit site never re-derives it from the function path.

This removes the magic-string comparison at the emit site (the source
of truth is now `determine_call_target`) and makes both match sites
in `compile_function_call` exhaustive over the two forms — adding a
third host-await form in the future would force a compile error at
every match site instead of silently falling through.

Arities are now hardcoded in the `expected_args` extraction
(`Some(2)` for explicit, `Some(1)` for registered) rather than
carried in the variant; registered builtins are constrained to
`arg_count == 1` at registration time, so there is no per-call
variability to carry.

Bytecode output is unchanged; the full RVM test suite (97 cases) and
the registered_host_await suite (15 cases) pass without modification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(compiler): clarify registered host-await intercepts unqualified calls only

PR #667 review (Medium): the docs implied registered host-await names
shadow user functions and builtins unconditionally, but
determine_call_target matches only the bare original_fcn_path. A
package-qualified call such as data.demo.resolve(x) is therefore not
intercepted -- it resolves through the normal path like any other call.

Rather than expand registration to qualified paths (which would let a
registered name leak into every package exposing a same-named rule),
document the unqualified-only behavior and pin it with tests.

- register_host_await_builtin: doc now states only the unqualified call
  form is intercepted; qualified calls resolve normally.
- determine_call_target: inline comment explaining the deliberate
  original_fcn_path-only match.
- docs/rvm/instruction-set.md: describe qualified-call resolution,
  including that builtins have no qualified form.
- tests: cross-package and same-package qualified calls resolve to the
  rule; bare-name shadowing of a standard builtin; Unknown-function
  outcome when no rule exists at the qualified path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(tests): compare host-await argument without re-running process_value

PR #667 review (Low): the suspendable test harness compared the
host-await argument via process_value(argument), but argument is already
a runtime Value. process_value is a YAML-fixture decoder -- it rewrites
"#undefined" to Undefined, {set!: [...]} to a set, and errors on a
runtime Value::Set. Re-running it on the runtime argument could coerce a
legitimate payload into a fixture sentinel (passing for the wrong
reason) or error outright on sets.

Compare the runtime argument directly against the expected value, which
is already decoded once at YAML load time.

Add a regression case (registered_builtin_suspendable_set_argument) that
passes a set payload: it fails under the old double-processing
("unexpected set in value read from json/yaml") and passes with the fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(tests): reject `args:` payload expectations in run-to-completion mode

PR #667 review (Low): a run-to-completion host-await response could carry
an `args:` payload expectation, but RTC execution pre-loads responses and
never surfaces the call argument to the harness, so the expectation was
parsed and silently dropped. A case with `args: "WRONG"` passed as long as
the result matched -- asserting a payload that was never checked.

Reject `args:` for run-to-completion fixtures at load time, directing the
author to suspendable mode where arguments are validated. Also only build
the run-to-completion response vector when the case actually runs in RTC
mode, so a suspendable case using the shared host_await_responses field
with `args:` is not wrongly rejected.

Route the fixture-load error through the same want_error handling used for
compilation errors, and add registered_builtin_run_to_completion_rejects_args
which now fails loudly instead of passing silently.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(compiler): reject host-await builtin names with surrounding whitespace

PR #667 review (Low): register_host_await_builtin rejected all-whitespace
names via name.trim().is_empty(), but accepted padded names like " lookup"
or "lookup ". Those were inserted into host_await_builtins, but Rego
function-call paths produce the trimmed identifier, so a padded
registration could never match -- a silent dead registration.

Reject any name that is not already trimmed (name != name.trim()) in
addition to empty names, and update the error message accordingly. Add
test cases for leading and trailing whitespace.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Signed-off-by: Mark Birger <birgerm@yandex.ru>
Co-authored-by: Mark Birger <markbirger@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-29 11:17:25 -05:00
dependabot[bot]
166ea727b8 build(deps): bump the per-dependency group across 1 directory with 3 updates (#728)
Bumps the per-dependency group with 3 updates in the /bindings/ruby directory: [minitest](https://github.com/minitest/minitest), [rubocop](https://github.com/rubocop/rubocop) and [rb_sys](https://github.com/oxidize-rb/rb-sys).


Updates `minitest` from 6.0.5 to 6.0.6
- [Changelog](https://github.com/minitest/minitest/blob/master/History.rdoc)
- [Commits](https://github.com/minitest/minitest/compare/v6.0.5...v6.0.6)

Updates `rubocop` from 1.86.1 to 1.88.0
- [Release notes](https://github.com/rubocop/rubocop/releases)
- [Changelog](https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop/compare/v1.86.1...v1.88.0)

Updates `rb_sys` from 0.9.127 to 0.9.128
- [Release notes](https://github.com/oxidize-rb/rb-sys/releases)
- [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.127...v0.9.128)

---
updated-dependencies:
- dependency-name: minitest
  dependency-version: 6.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: per-dependency
- dependency-name: rb_sys
  dependency-version: 0.9.128
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: per-dependency
- dependency-name: rubocop
  dependency-version: 1.86.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: per-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-26 15:40:34 -05:00
dependabot[bot]
4c45ebfb61 build(deps): update maturin requirement (#683)
Updates the requirements on [maturin](https://github.com/pyo3/maturin) to permit the latest version.

Updates `maturin` to 1.14.1
- [Release notes](https://github.com/pyo3/maturin/releases)
- [Changelog](https://github.com/PyO3/maturin/blob/main/Changelog.md)
- [Commits](https://github.com/pyo3/maturin/compare/v1.4.0...v1.14.1)

---
updated-dependencies:
- dependency-name: maturin
  dependency-version: 1.13.1
  dependency-type: direct:development
  dependency-group: per-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-26 13:57:44 -05:00
Anand Krishnamoorthi
41e1303213 feat(value): introduce Set storage abstraction (#740)
Add an opaque `Set` newtype paralleling `Object`, living under
`src/value/set/` with the same module structure (`mod.rs` /
`iter.rs` / `serde.rs`). `Set` wraps `BTreeSet<Value>` today but
exposes only a curated surface: `contains`, `insert`, `remove`,
`iter`, `iter_sorted`, `cursor` (resumable), `is_subset`,
`intersection`, `difference`, serde, and a hand-written `Ord`.
The cursor types are re-exported behind the `rvm` feature so the
follow-up `IterationState::Set` swap can land additively.

To free the `Set` name for the new public type, the crate-internal
`BTreeSet as Set` / `HashSet as Set` aliases in `lib.rs` are
renamed to `MapSet`. All in-tree consumers of the old alias are
updated in lockstep.

`Value::Set` is unchanged in this commit (still wraps
`Rc<BTreeSet<Value>>`); the payload swap and call-site migration
ship in the next PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-26 13:56:15 -05:00
Copilot
9b42239327 Expand keyword-in-ref coverage for complex parser edge cases (interpreter + RVM) (#744)
* Initial plan

* Add keywords_in_refs: allow reserved keywords as dot-notation field names

* Address review feedback: improve parse_ref_field doc comment and clean up test comment

* Add complex keyword-in-ref test cases

* Polish keyword-ref test expectations and validate coverage

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-26 13:55:28 -05:00
dependabot[bot]
9b6ad0bdac build(deps): bump org.apache.maven.plugins:maven-surefire-plugin (#732)
Bumps the per-dependency group with 1 update in the /bindings/java directory: [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire).


Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.5.5 to 3.5.6
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.5...surefire-3.5.6)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-version: 3.5.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: per-dependency
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-26 10:40:39 -05:00
dependabot[bot]
c394725e41 build(deps): bump the rust-dependencies group across 5 directories with 4 updates (#754)
* build(deps): bump the rust-dependencies group across 5 directories with 4 updates

Bumps the rust-dependencies group with 3 updates in the / directory: spin, [uuid](https://github.com/uuid-rs/uuid) and [jsonschema](https://github.com/Stranger6667/jsonschema).
Bumps the rust-dependencies group with 3 updates in the /bindings/ffi directory: spin, [uuid](https://github.com/uuid-rs/uuid) and [jsonschema](https://github.com/Stranger6667/jsonschema).
Bumps the rust-dependencies group with 3 updates in the /bindings/java directory: spin, [uuid](https://github.com/uuid-rs/uuid) and [jsonschema](https://github.com/Stranger6667/jsonschema).
Bumps the rust-dependencies group with 3 updates in the /bindings/python directory: spin, [uuid](https://github.com/uuid-rs/uuid) and [jsonschema](https://github.com/Stranger6667/jsonschema).
Bumps the rust-dependencies group with 4 updates in the /bindings/wasm directory: spin, [uuid](https://github.com/uuid-rs/uuid), [jsonschema](https://github.com/Stranger6667/jsonschema) and [wasm-bindgen-test](https://github.com/wasm-bindgen/wasm-bindgen).


Updates `spin` from 0.12.0 to 0.12.1

Updates `uuid` from 1.23.3 to 1.23.4
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.3...v1.23.4)

Updates `jsonschema` from 0.46.5 to 0.46.6
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.5...cli-v0.46.6)

Updates `spin` from 0.12.0 to 0.12.1

Updates `uuid` from 1.23.3 to 1.23.4
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.3...v1.23.4)

Updates `jsonschema` from 0.46.5 to 0.46.6
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.5...cli-v0.46.6)

Updates `spin` from 0.12.0 to 0.12.1

Updates `uuid` from 1.23.3 to 1.23.4
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.3...v1.23.4)

Updates `jsonschema` from 0.46.5 to 0.46.6
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.5...cli-v0.46.6)

Updates `spin` from 0.12.0 to 0.12.1

Updates `uuid` from 1.23.3 to 1.23.4
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.3...v1.23.4)

Updates `jsonschema` from 0.46.5 to 0.46.6
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.5...cli-v0.46.6)

Updates `spin` from 0.12.0 to 0.12.1

Updates `uuid` from 1.23.3 to 1.23.4
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.3...v1.23.4)

Updates `jsonschema` from 0.46.5 to 0.46.6
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/cli-v0.46.5...cli-v0.46.6)

Updates `wasm-bindgen-test` from 0.3.75 to 0.3.76
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: spin
  dependency-version: 0.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: wasm-bindgen-test
  dependency-version: 0.3.76
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): refresh Cargo lockfiles

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-26 10:34:57 -05:00
Copilot
4b8874be9c bindings/python: bump PyO3 to 0.29.0 to remediate GHSA-36hh-v3qg-5jq4 (#752)
* Initial plan

* Bump pyo3 to 0.29.0 in python binding

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-24 12:32:03 -05:00
dependabot[bot]
096c693155 build(deps): bump the rust-dependencies group across 5 directories with 6 updates (#750)
* build(deps): bump the rust-dependencies group across 5 directories with 6 updates

Bumps the rust-dependencies group with 3 updates in the / directory: [regex](https://github.com/rust-lang/regex), [uuid](https://github.com/uuid-rs/uuid) and [chrono](https://github.com/chronotope/chrono).
Bumps the rust-dependencies group with 4 updates in the /bindings/ffi directory: [regex](https://github.com/rust-lang/regex), [uuid](https://github.com/uuid-rs/uuid), [chrono](https://github.com/chronotope/chrono) and [cbindgen](https://github.com/mozilla/cbindgen).
Bumps the rust-dependencies group with 3 updates in the /bindings/java directory: [regex](https://github.com/rust-lang/regex), [uuid](https://github.com/uuid-rs/uuid) and [chrono](https://github.com/chronotope/chrono).
Bumps the rust-dependencies group with 4 updates in the /bindings/python directory: [regex](https://github.com/rust-lang/regex), [uuid](https://github.com/uuid-rs/uuid), [chrono](https://github.com/chronotope/chrono) and [pyo3](https://github.com/pyo3/pyo3).
Bumps the rust-dependencies group with 4 updates in the /bindings/wasm directory: [regex](https://github.com/rust-lang/regex), [uuid](https://github.com/uuid-rs/uuid), [chrono](https://github.com/chronotope/chrono) and [wasm-bindgen-test](https://github.com/wasm-bindgen/wasm-bindgen).


Updates `regex` from 1.12.3 to 1.12.4
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.3...1.12.4)

Updates `uuid` from 1.23.1 to 1.23.3
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.1...v1.23.3)

Updates `chrono` from 0.4.44 to 0.4.45
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.44...v0.4.45)

Updates `regex` from 1.12.3 to 1.12.4
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.3...1.12.4)

Updates `uuid` from 1.23.1 to 1.23.3
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.1...v1.23.3)

Updates `chrono` from 0.4.44 to 0.4.45
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.44...v0.4.45)

Updates `cbindgen` from 0.29.3 to 0.29.4
- [Release notes](https://github.com/mozilla/cbindgen/releases)
- [Changelog](https://github.com/mozilla/cbindgen/blob/main/CHANGES)
- [Commits](https://github.com/mozilla/cbindgen/compare/0.29.3...0.29.4)

Updates `regex` from 1.12.3 to 1.12.4
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.3...1.12.4)

Updates `uuid` from 1.23.1 to 1.23.3
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.1...v1.23.3)

Updates `chrono` from 0.4.44 to 0.4.45
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.44...v0.4.45)

Updates `regex` from 1.12.3 to 1.12.4
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.3...1.12.4)

Updates `uuid` from 1.23.1 to 1.23.3
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.1...v1.23.3)

Updates `chrono` from 0.4.44 to 0.4.45
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.44...v0.4.45)

Updates `pyo3` from 0.28.3 to 0.29.0
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.28.3...v0.29.0)

Updates `regex` from 1.12.3 to 1.12.4
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.12.3...1.12.4)

Updates `uuid` from 1.23.1 to 1.23.3
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.1...v1.23.3)

Updates `chrono` from 0.4.44 to 0.4.45
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.44...v0.4.45)

Updates `wasm-bindgen-test` from 0.3.72 to 0.3.75
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 1.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: cbindgen
  dependency-version: 0.29.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: pyo3
  dependency-version: 0.29.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.23.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: wasm-bindgen-test
  dependency-version: 0.3.75
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): refresh Cargo lockfiles

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-23 11:11:31 -05:00
Anand Krishnamoorthi
ed6ae465b0 refactor(value): migrate Value::Object to Object storage abstraction (#736)
Builds on #57. Swap Value::Object's payload from Rc<BTreeMap<Value, Value>>
to Rc<Object> and migrate all call sites to the Object API.

as_object / as_object_mut keep their names but return &Object / &mut Object.
The mutable accessor handles Rc::make_mut internally, so callers no longer
do it themselves. Object grows into_value() and From<Object> for Value.
Value's serializer now delegates to Object::serialize, dropping a duplicate
non-string-key stringification path.

RVM IterationState::Object is rewritten around ObjectCursor: O(log n)
steps over a shared Rc<Object>, no eager pair snapshot. Snapshot
independence is preserved by Rc copy-on-write; setup_next_iteration
advances the cursor inline and advance() becomes a no-op for this variant.
A new iteration_state_object_is_snapshot_independent_of_source test
covers CoW against a mutated alias.

Value::Set still wraps Rc<BTreeSet<Value>>; the matching Set abstraction
and its swap ship in follow-up PRs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 18:04:37 -05:00
Mark Birger
bd90453dd3 tests/opa: normalize path separators in folder filter on Windows (#742)
`run_opa_tests` builds `path_dir_str` from `path.strip_prefix(...).to_string_lossy()`,
which on Windows yields strings with backslash separators (e.g.
`v0\aggregates`). The folder filter then does an exact-string
comparison against the CLI arguments:

    let run_test = folders.is_empty()
        || folders.iter().any(|f| &path_dir_str == f);

CLI arguments use forward slashes (`v0/aggregates`), so on Windows
the comparison never matches, no tests are selected, and the function
bails with `"no matching tests found"`. This blocks the
`cargo xtask pre-push` hook for any Windows contributor.

Normalize `path_dir_str` to use forward slashes at construction
time. Reproduces before the fix as `cargo test ... --test opa --
v1/aggregates` exiting 1 with `no matching tests found`; after the
fix the same command runs 72 cases and the full hook command runs
2861 / 0 across 188 folders.

The duplicate platform check at the `is_rego_v0_test` site
(`path_dir_str.starts_with("v0/") || path_dir.starts_with("v0\\")`)
is left intact to keep the change minimal — the backslash branch
becomes redundant but is harmless.

Co-authored-by: Mark Birger <markbirger@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 16:00:24 -05:00
Anand Krishnamoorthi
11940ddb04 Introduce Object storage abstraction (#735)
Add an opaque Object type for the key→value storage backing
Value::Object. It exposes a small set of methods (get, insert, remove,
iter, iter_sorted, cursor, serde) and keeps the backing store private,
so future representations -- inline small-map, hash-backed, lazy,
arena, FFI-callback -- can plug in without touching the call sites
that name this type.

Nothing in the engine uses Object yet. Value::Object still wraps
Rc<BTreeMap<Value, Value>>; the payload swap and call-site migration
come in the next PR. Object stands on its own unit tests in the
meantime.

docs/value/object.md walks through the design, the precedents it
follows (serde_json::Map, toml::Table, simdjson DOM), and the
concrete workloads the abstraction is meant to unlock.

A matching Set abstraction follows in a separate PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-04 12:31:15 -05:00
Anand Krishnamoorthi
5b7010ba16 chore(rvm): add debug-mode invariant assertions (#737)
Encode VM stack/context/register lifecycle invariants as
debug_assert!s. Zero cost in release; surfaces violations during
debug-mode tests and CI.

Invariants covered:
- reset_execution_state postcondition: all stacks empty, registers
  resized to base and Undefined, rule_cache reset, pc/executed
  counters zeroed, builtins_cache cleared, execution_state Ready.
- Per-opcode invariant check (assert_vm_invariants) invoked at the
  top of run_stackless_loop and jump_to iterations: state is
  Ready/Running, registers non-empty, rule_cache sized to program,
  execution stack bounded by a debug-only sanity ceiling
  (DEBUG_MAX_EXECUTION_STACK_DEPTH = 4096; not a production limit).
- resume() precondition: execution_state is Suspended.
- execute_suspendable_entry precondition: clean state (callers reset
  immediately before).
- Rule finalize: call_rule_stack pop matches the finalized rule_index.
- IterationState::advance: Single iterator not advanced past
  consumption, Array index not at usize::MAX before saturating_add.

All assertions are gated by #[cfg(debug_assertions)] (directly or via
debug_assert!) so release builds are unaffected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-04 12:28:47 -05:00
dependabot[bot]
ba7d29b134 build(deps): bump the rust-dependencies group across 5 directories with 5 updates (#734)
* build(deps): bump the rust-dependencies group across 5 directories with 5 updates

Bumps the rust-dependencies group with 4 updates in the / directory: [serde_json](https://github.com/serde-rs/json), spin, [dashmap](https://github.com/xacrimon/dashmap) and [toml_edit](https://github.com/toml-rs/toml).
Bumps the rust-dependencies group with 3 updates in the /bindings/ffi directory: [serde_json](https://github.com/serde-rs/json), spin and [dashmap](https://github.com/xacrimon/dashmap).
Bumps the rust-dependencies group with 2 updates in the /bindings/java directory: [serde_json](https://github.com/serde-rs/json) and spin.
Bumps the rust-dependencies group with 2 updates in the /bindings/python directory: [serde_json](https://github.com/serde-rs/json) and spin.
Bumps the rust-dependencies group with 3 updates in the /bindings/wasm directory: [serde_json](https://github.com/serde-rs/json), spin and [wasm-bindgen-test](https://github.com/wasm-bindgen/wasm-bindgen).


Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `dashmap` from 6.1.0 to 6.2.1
- [Release notes](https://github.com/xacrimon/dashmap/releases)
- [Commits](https://github.com/xacrimon/dashmap/compare/v6.1.0...v6.2.1)

Updates `toml_edit` from 0.25.11+spec-1.1.0 to 0.25.12+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.25.11...v0.25.12)

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `dashmap` from 6.1.0 to 6.2.1
- [Release notes](https://github.com/xacrimon/dashmap/releases)
- [Commits](https://github.com/xacrimon/dashmap/compare/v6.1.0...v6.2.1)

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `dashmap` from 6.1.0 to 6.2.1
- [Release notes](https://github.com/xacrimon/dashmap/releases)
- [Commits](https://github.com/xacrimon/dashmap/compare/v6.1.0...v6.2.1)

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `dashmap` from 6.1.0 to 6.2.1
- [Release notes](https://github.com/xacrimon/dashmap/releases)
- [Commits](https://github.com/xacrimon/dashmap/compare/v6.1.0...v6.2.1)

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `spin` from 0.10.0 to 0.12.0

Updates `serde_json` from 1.0.149 to 1.0.150
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

Updates `wasm-bindgen-test` from 0.3.71 to 0.3.72
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

Updates `spin` from 0.10.0 to 0.12.0

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: dashmap
  dependency-version: 6.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: toml_edit
  dependency-version: 0.25.12+spec-1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: dashmap
  dependency-version: 6.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: dashmap
  dependency-version: 6.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: dashmap
  dependency-version: 6.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: wasm-bindgen-test
  dependency-version: 0.3.72
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): refresh Cargo lockfiles

* build(deps): refresh Cargo lockfiles

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-28 14:58:40 -05:00
Anand Krishnamoorthi
acf7f7a25e chore: release (#731)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-22 15:50:53 -05:00
Anand Krishnamoorthi
86b4a279fa fix(ffi): eliminate aliasing UB + add Azure Policy JSON compilation FFI (#727)
* fix(ffi): eliminate aliasing UB via to_shared_ref migration

Add to_shared_ref() helper that creates &T (shared reference) from raw
pointers instead of &mut T. This eliminates undefined behavior caused by
violating Rust's aliasing invariant when C# SafeHandle permits concurrent
FFI calls on the same handle.

With &mut T, the compiler may assume exclusive (noalias) access and
reorder or elide reads/writes — a miscompilation risk when another thread
holds a reference to the same object. Switching to &T removes that
assumption; actual mutation is mediated by the interior RwLock inside
Handle<T>, which is the sole synchronization mechanism.

Migrated sites:
- rvm.rs: 20 non-drop call sites
- engine.rs: 30 non-drop call sites + with_unwind_guard for timer fns
- compiled_policy.rs: 2 call sites
- Fix null-data UB in regorus_program_deserialize_binary

Drop paths retain to_ref() where exclusive access is guaranteed by the
caller contract (preventing use-after-free).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(ffi): add Azure Policy JSON compilation FFI and C# bindings

- AliasRegistry builder pattern: RegorusAliasRegistryBuilder (mutable,
  single-threaded) + RegorusAliasRegistry (immutable, Arc-wrapped)
- Azure Policy JSON compilation: regorus_compile_azure_policy_rule and
  regorus_compile_azure_policy_definition with alias registry support
- regorus_rvm_set_context for host-supplied ambient data
- C# AliasRegistryBuilder and AliasRegistry classes with convenience
  factories (FromJson, FromManifest, Empty)
- C# AzurePolicyCompiler static class for policy rule/definition compilation
- Compile functions take *const RegorusAliasRegistry (read-only via
  to_shared_ref for concurrent compilation safety)
- Fix pre-existing clippy warnings across multiple crates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-22 12:50:16 -05:00
Anand Krishnamoorthi
5467cd9e69 refactor(azure_policy): reduce AliasRegistry allocations via Rc sharing (#725)
Replace the compiler's two cloned BTreeMap fields (alias_map and
alias_modifiable) with a single Option<Rc<AliasRegistry>>. This
eliminates cloning two 73K-entry maps on every compilation by sharing
the registry through a reference-counted pointer.

Additional improvements:
- alias_map()/alias_modifiable_map() return &BTreeMap (zero-copy)
- Safe .get() indexing in ingest_alias_entries and build_object_from_keys
- Null-tolerant deserialization for AliasEntry.paths (~97% of real
  Azure catalog entries emit "paths": null)

All changes are within the azure_policy feature gate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-18 17:55:34 -05:00
Copilot
dae3052781 fix(interpreter,rvm): correct partial object rule iteration and classification (#718)
Partial object rules with dynamic keys (e.g. `violations[k] if { ... }`)
only produced a single entry instead of collecting all bindings. Two
independent bugs caused this:

1. Interpreter: the early-return optimization in eval_output_expr_in_loop
   checked whether the rule_ref was constant but never verified whether
   the key expression was also constant. A variable key like `k` was
   treated as constant output, causing the loop to exit after the first
   iteration. Fixed by gating early-return on key_expr constness.

2. RVM: compute_rule_type incorrectly classified `p[k] if { ... }` as
   PartialSet instead of PartialObject. OPA v1 semantics define this
   form as a partial object (key -> true). Fixed the classification and
   added compiler error guards for patterns the RVM codegen cannot yet
   handle (constant keys, nested bracket keys), ensuring graceful
   fallback to the interpreter.

The OPA test harness now skips RVM validation per-case when partial
object compiler errors are raised, rather than blanket-skipping entire
folders. This preserves RVM coverage for unrelated tests in the same
folders.

Closes #712

Co-authored-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-05-18 15:14:08 -05:00
Anand Krishnamoorthi
3111bf58f2 perf(normalizer): use Rc<str> interning to reduce alias resolution allocations (#726)
Replace per-alias heap allocations with reference-counted string interning
throughout the normalizer's alias resolution pipeline:

- Store alias ARM path segments as Vec<Rc<str>> instead of Vec<String>,
  enabling zero-alloc BTreeMap lookups via Rc::clone (refcount bump)
  rather than Value::from() (heap allocation per lookup)

- Pre-compute lowercased short name (short_name_lc: Rc<str>) at
  registry-load time, enabling allocation-free insertion for the
  common case of non-dotted, non-collision alias short names

- Add rc_lowercase() fast-path that skips allocation when the input
  string is already lowercase ASCII (common for ARM property names)

- Update set_nested_lowercased/set_nested_inner/set_nested_in_btree to
  thread Rc<str> through intermediate object construction, avoiding
  String temporaries at every nesting level

- Replace to_ascii_lowercase().starts_with() in is_root_field_collision
  with a zero-alloc byte-level comparison

Measured 27-49% improvement in normalization time across a range of
Azure Policy alias-heavy resource types (293-608 aliases per type).

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-05-18 15:13:11 -05:00
Anand Krishnamoorthi
be3fde7706 fix(copilot): robust diff computation for cloud agent environments (#709)
The cloud agent checks out a branch like copilot/review-pr-NNN which
may not have upstream/main or origin/main refs available for merge-base.

Changes:
- Use gh pr diff as primary method (always works in PR context)
- Fall back to git merge-base for local non-PR usage
- Remove path filters (*.rs *.toml examples/) — review full diff
- Remove head -2000 truncation — let agents see everything
- Explicitly fetch origin/main in copilot-setup-steps.yml as backup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-18 15:12:29 -05:00
dependabot[bot]
d2c483e93e build(deps): bump the rust-dependencies group across 5 directories with 2 updates (#724)
* build(deps): bump the rust-dependencies group across 5 directories with 2 updates

Bumps the rust-dependencies group with 2 updates in the / directory: [hashbrown](https://github.com/rust-lang/hashbrown) and [jsonschema](https://github.com/Stranger6667/jsonschema).
Bumps the rust-dependencies group with 2 updates in the /bindings/ffi directory: [hashbrown](https://github.com/rust-lang/hashbrown) and [jsonschema](https://github.com/Stranger6667/jsonschema).
Bumps the rust-dependencies group with 2 updates in the /bindings/java directory: [hashbrown](https://github.com/rust-lang/hashbrown) and [jsonschema](https://github.com/Stranger6667/jsonschema).
Bumps the rust-dependencies group with 2 updates in the /bindings/python directory: [hashbrown](https://github.com/rust-lang/hashbrown) and [jsonschema](https://github.com/Stranger6667/jsonschema).
Bumps the rust-dependencies group with 2 updates in the /bindings/wasm directory: [hashbrown](https://github.com/rust-lang/hashbrown) and [jsonschema](https://github.com/Stranger6667/jsonschema).


Updates `hashbrown` from 0.17.0 to 0.17.1
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.17.0...v0.17.1)

Updates `jsonschema` from 0.46.4 to 0.46.5
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.4...ruby-v0.46.5)

Updates `hashbrown` from 0.17.0 to 0.17.1
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.17.0...v0.17.1)

Updates `jsonschema` from 0.46.4 to 0.46.5
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.4...ruby-v0.46.5)

Updates `hashbrown` from 0.17.0 to 0.17.1
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.17.0...v0.17.1)

Updates `jsonschema` from 0.46.4 to 0.46.5
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.4...ruby-v0.46.5)

Updates `hashbrown` from 0.17.0 to 0.17.1
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.17.0...v0.17.1)

Updates `jsonschema` from 0.46.4 to 0.46.5
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.4...ruby-v0.46.5)

Updates `hashbrown` from 0.17.0 to 0.17.1
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.17.0...v0.17.1)

Updates `jsonschema` from 0.46.4 to 0.46.5
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.46.4...ruby-v0.46.5)

---
updated-dependencies:
- dependency-name: hashbrown
  dependency-version: 0.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: hashbrown
  dependency-version: 0.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: hashbrown
  dependency-version: 0.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: hashbrown
  dependency-version: 0.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: hashbrown
  dependency-version: 0.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): refresh Cargo lockfiles

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-14 10:37:20 -05:00
dependabot[bot]
093e50f0a1 build(deps): bump the rust-dependencies group across 5 directories with 4 updates (#717)
* build(deps): bump the rust-dependencies group across 5 directories with 4 updates

Bumps the rust-dependencies group with 3 updates in the / directory: [hashbrown](https://github.com/rust-lang/hashbrown), [jsonschema](https://github.com/Stranger6667/jsonschema) and [lru](https://github.com/jeromefroe/lru-rs).
Bumps the rust-dependencies group with 3 updates in the /bindings/ffi directory: [hashbrown](https://github.com/rust-lang/hashbrown), [jsonschema](https://github.com/Stranger6667/jsonschema) and [lru](https://github.com/jeromefroe/lru-rs).
Bumps the rust-dependencies group with 3 updates in the /bindings/java directory: [hashbrown](https://github.com/rust-lang/hashbrown), [jsonschema](https://github.com/Stranger6667/jsonschema) and [lru](https://github.com/jeromefroe/lru-rs).
Bumps the rust-dependencies group with 3 updates in the /bindings/python directory: [hashbrown](https://github.com/rust-lang/hashbrown), [jsonschema](https://github.com/Stranger6667/jsonschema) and [lru](https://github.com/jeromefroe/lru-rs).
Bumps the rust-dependencies group with 4 updates in the /bindings/wasm directory: [hashbrown](https://github.com/rust-lang/hashbrown), [jsonschema](https://github.com/Stranger6667/jsonschema), [lru](https://github.com/jeromefroe/lru-rs) and [wasm-bindgen-test](https://github.com/wasm-bindgen/wasm-bindgen).


Updates `hashbrown` from 0.16.1 to 0.17.0
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.16.1...v0.17.0)

Updates `jsonschema` from 0.45.1 to 0.46.4
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.45.1...ruby-v0.46.4)

Updates `lru` from 0.16.4 to 0.18.0
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.16.4...0.18.0)

Updates `hashbrown` from 0.16.1 to 0.17.0
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.16.1...v0.17.0)

Updates `jsonschema` from 0.45.1 to 0.46.4
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.45.1...ruby-v0.46.4)

Updates `lru` from 0.16.4 to 0.18.0
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.16.4...0.18.0)

Updates `hashbrown` from 0.16.1 to 0.17.0
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.16.1...v0.17.0)

Updates `jsonschema` from 0.45.1 to 0.46.4
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.45.1...ruby-v0.46.4)

Updates `lru` from 0.16.4 to 0.18.0
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.16.4...0.18.0)

Updates `hashbrown` from 0.16.1 to 0.17.0
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.16.1...v0.17.0)

Updates `jsonschema` from 0.45.1 to 0.46.4
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.45.1...ruby-v0.46.4)

Updates `lru` from 0.16.4 to 0.18.0
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.16.4...0.18.0)

Updates `hashbrown` from 0.16.1 to 0.17.0
- [Release notes](https://github.com/rust-lang/hashbrown/releases)
- [Changelog](https://github.com/rust-lang/hashbrown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.16.1...v0.17.0)

Updates `jsonschema` from 0.45.1 to 0.46.4
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.45.1...ruby-v0.46.4)

Updates `lru` from 0.16.4 to 0.18.0
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.16.4...0.18.0)

Updates `wasm-bindgen-test` from 0.3.70 to 0.3.71
- [Release notes](https://github.com/wasm-bindgen/wasm-bindgen/releases)
- [Changelog](https://github.com/wasm-bindgen/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wasm-bindgen/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: hashbrown
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: hashbrown
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: hashbrown
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: hashbrown
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: hashbrown
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.46.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: lru
  dependency-version: 0.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: wasm-bindgen-test
  dependency-version: 0.3.71
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): refresh Cargo lockfiles

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-07 06:42:34 -05:00
119 changed files with 9668 additions and 2755 deletions

View File

@@ -8,3 +8,5 @@ steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # full history needed for git diff against main
- run: git fetch origin main:refs/remotes/origin/main
name: Ensure origin/main ref is available for diff computation

View File

@@ -25,15 +25,21 @@ Key constraints (details in copilot-instructions.md):
## Step 1: Get the Diff
```bash
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|| git merge-base origin/main HEAD 2>/dev/null)
if [ -z "$BASE" ]; then
echo "ERROR: Cannot find upstream/main or origin/main. Cannot determine review scope."
exit 1
# Primary: use gh pr diff (works in cloud agent + any PR context).
# Fallback: git merge-base for local non-PR usage.
if gh pr diff --name-only >/dev/null 2>&1; then
echo "---STAT---"
gh pr diff --name-only
echo "---DIFF---"
gh pr diff
else
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|| git merge-base origin/main HEAD 2>/dev/null \
|| git merge-base main HEAD 2>/dev/null)
echo "Reviewing changes since: $BASE"
git diff "$BASE"..HEAD --stat
git diff "$BASE"..HEAD
fi
echo "Reviewing changes since: $BASE"
git diff "$BASE"..HEAD --stat
git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
```
If the diff is empty, stop and report: "No changes found to review."
@@ -196,3 +202,9 @@ one pass. If any were skipped, note them and briefly assess.
### Summary
X findings (N critical, N high, N medium, N low). One sentence overall assessment.
### Output
After generating the report above, write the COMPLETE report to `/tmp/code-review-report.md`
using the `create` tool or shell. This ensures the full report is preserved even if
display output is truncated.

View File

@@ -40,22 +40,25 @@ Use `read_agent` with `wait: true` to wait for each background agent.
## Step 1: Get the Diff and Build Inventory
```bash
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|| git merge-base origin/main HEAD 2>/dev/null)
if [ -z "$BASE" ]; then
echo "ERROR: Cannot find upstream/main or origin/main."
exit 1
# Primary: use gh pr diff (works in cloud agent + any PR context).
# Fallback: git merge-base for local non-PR usage.
if gh pr diff --name-only >/dev/null 2>&1; then
echo "---STAT---"
gh pr diff --name-only
echo "---DIFF---"
gh pr diff
else
BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
|| git merge-base origin/main HEAD 2>/dev/null \
|| git merge-base main HEAD 2>/dev/null)
echo "Reviewing changes since: $BASE"
git diff "$BASE"..HEAD --stat
git diff "$BASE"..HEAD
fi
echo "Reviewing changes since: $BASE"
git diff "$BASE"..HEAD --stat
git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/' | head -2000
```
If the diff is empty, stop and report: "No changes found to review."
**Scope rule:** Focus on code files (`*.rs`, `*.toml`, examples). Do NOT pass
docs/config diffs to agents.
**Build a risk-classified inventory.** List every changed function, struct,
impl, trait, pub item, and significant code block. Number them and tag with
risk predicates:
@@ -106,8 +109,10 @@ Use `model: "gpt-5.4"` in the task tool call (provides model diversity).
> Get the diff:
> ```
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
> || git merge-base origin/main HEAD 2>/dev/null)
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
> || git merge-base origin/main HEAD 2>/dev/null \
> || git merge-base main HEAD 2>/dev/null)
> # If no merge-base, use: gh pr diff
> git diff "$BASE"..HEAD # or: gh pr diff
> ```
>
> Key regorus constraints:
@@ -161,8 +166,10 @@ Use `model: "claude-opus-4.6"` in the task tool call.
> Get the diff AND read full source files for context:
> ```
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
> || git merge-base origin/main HEAD 2>/dev/null)
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
> || git merge-base origin/main HEAD 2>/dev/null \
> || git merge-base main HEAD 2>/dev/null)
> # If no merge-base, use: gh pr diff
> git diff "$BASE"..HEAD # or: gh pr diff
> ```
> Then use `view` to read the full source files that were changed.
>
@@ -219,8 +226,10 @@ Use the default model (no `model` parameter).
> Get the diff:
> ```
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
> || git merge-base origin/main HEAD 2>/dev/null)
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
> || git merge-base origin/main HEAD 2>/dev/null \
> || git merge-base main HEAD 2>/dev/null)
> # If no merge-base, use: gh pr diff
> git diff "$BASE"..HEAD # or: gh pr diff
> ```
> Use `view` to read surrounding context.
>
@@ -439,8 +448,10 @@ Launch **1 general-purpose agent in background mode**.
> Get the diff:
> ```
> BASE=$(git merge-base upstream/main HEAD 2>/dev/null \
> || git merge-base origin/main HEAD 2>/dev/null)
> git diff "$BASE"..HEAD -- '*.rs' '*.toml' 'examples/'
> || git merge-base origin/main HEAD 2>/dev/null \
> || git merge-base main HEAD 2>/dev/null)
> # If no merge-base, use: gh pr diff
> git diff "$BASE"..HEAD # or: gh pr diff
> ```
> Use `view` to read full source files.
>
@@ -481,8 +492,8 @@ Launch **1 general-purpose agent in background mode**.
## Step 5: Synthesize and Report
**IMPORTANT:** This is the primary output. Everything above was preparation.
Keep the report COMPACT — one finding per block, no filler prose.
**CRITICAL:** Write the report to `/tmp/deep-review-report.md` FIRST, then display it.
Use a shell command to write the file before any other output in this step.
Apply verdicts from the adversarial verifier:
- **CONFIRMED**: keep at stated severity
@@ -522,3 +533,9 @@ would catch it. If not, name the minimal test that should exist.
X findings (N critical, N high, N medium, N low). Y "likely" findings.
Z dropped (one-line reasons).
Risk assessment in one sentence.
---
**Remember:** The report above MUST be written to `/tmp/deep-review-report.md` at the
START of Step 5 (before displaying it). Use shell: `cat > /tmp/deep-review-report.md << 'REPORT_EOF'`
... report content ... `REPORT_EOF`

View File

@@ -6,6 +6,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [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
### Added

535
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ members = [
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.10.0"
version = "0.11.0"
edition = "2021"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
repository = "https://github.com/microsoft/regorus"
@@ -98,23 +98,23 @@ rand = ["dep:rand"]
[dependencies]
anyhow = { version = "1.0.102", default-features = false }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
hashbrown = { version = "0.16", default-features = false, features = ["default-hasher"], optional = true }
serde_json = { version = "1.0.150", default-features = false, features = ["alloc"] }
hashbrown = { version = "0.17", default-features = false, features = ["default-hasher"], optional = true }
lazy_static = { version = "1.4.0", default-features = false }
thiserror = { version = "2.0", default-features = false }
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
num-bigint = { version = "0.4", default-features = false }
num-bigint = { version = "0.5", default-features = false }
num-traits = { version = "0.2", default-features = false }
parking_lot = { version = "0.12", optional = true }
spin = { version = "0.10.0", default-features = false, features = ["mutex", "spin_mutex"] }
spin = { version = "0.12.0", default-features = false, features = ["mutex", "spin_mutex"] }
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
regex = {version = "1.12.3", optional = true, default-features = false }
semver = {version = "1.0.28", optional = true, default-features = false }
url = { version = "2.5.4", optional = true }
uuid = { version = "1.22.0", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.45.1", default-features = false, optional = true }
jsonschema = { version = "0.47.0", default-features = false, optional = true }
chrono = { version = "0.4.44", optional = true }
chrono-tz = { version = "0.10.1", optional = true }
ipnet = { version = "2.12.0", optional = true, default-features = false }
@@ -127,8 +127,8 @@ rand = { version = "0.10.0", default-features = false, features = ["thread_rng"]
# Causes the project to link with the Spectre-mitigated CRT and libs.
msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true }
dashmap = { version = "6.1", default-features = false, optional = true }
lru = { version = "0.16", default-features = false, optional = true }
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.6", optional = true }
lru = { version = "0.18", default-features = false, optional = true }
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.7", optional = true }
# rvm related deps
indexmap = { version = "2.13.1", default-features = false, features = ["serde"], optional = true }

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<RegorusPackageVersion>0.10.0</RegorusPackageVersion>
<RegorusPackageVersion>0.11.0</RegorusPackageVersion>
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>

View File

@@ -150,3 +150,76 @@ const string ContextJson = """
var allowed = RbacEngine.EvaluateCondition(Condition, ContextJson);
Console.WriteLine($"RBAC condition allowed: {allowed}");
```
## Azure Policy JSON Evaluation
Compile and evaluate Azure Policy JSON `policyRule` definitions directly — no Rego translation required.
The `AzurePolicyCompiler` compiles JSON policy rules into RVM programs that can be executed with the `Rvm` engine.
```csharp
using Regorus;
// 1. Load alias definitions for the resource provider
const string AliasesJson = """
[{
"namespace": "Microsoft.Storage",
"resourceTypes": [{
"resourceType": "storageAccounts",
"aliases": [{
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"defaultPath": "properties.supportsHttpsTrafficOnly",
"paths": []
}]
}]
}]
""";
using var registry = AliasRegistry.FromJson(AliasesJson);
// 2. Compile a JSON policy rule (the native Azure Policy language)
const string PolicyRule = """
{
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
]
},
"then": { "effect": "deny" }
}
""";
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, PolicyRule);
// 3. Normalize an ARM resource and evaluate
var armResource = """
{
"type": "Microsoft.Storage/storageAccounts",
"name": "mystorage",
"properties": { "supportsHttpsTrafficOnly": false }
}
""";
var envelope = registry.NormalizeAndWrap(armResource);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(envelope!);
var result = vm.ExecuteEntryPoint("main");
// result: {"effect": "deny"} for non-compliant, "<undefined>" for compliant
Console.WriteLine($"Policy result: {result}");
```
**Context-dependent policies:** If your policy uses context functions like
`subscription()`, `resourceGroup()`, or `requestContext()`, you must also set
the VM context separately:
```csharp
// The context JSON from NormalizeAndWrap is in the input envelope,
// but must also be provided to the VM's ambient context:
vm.SetContextJson(contextJson);
```
You can also compile full policy definitions (with parameters) using
`AzurePolicyCompiler.CompilePolicyDefinition()`. See
`bindings/csharp/Regorus.Tests/AzurePolicyCompilerTests.cs` for comprehensive examples.

View File

@@ -43,31 +43,28 @@ public class AliasRegistryTests
[TestMethod]
public void Create_and_dispose_succeeds()
{
using var registry = new AliasRegistry();
using var registry = AliasRegistry.Empty();
Assert.AreEqual(0, registry.Length);
}
[TestMethod]
public void LoadJson_populates_registry()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
Assert.AreEqual(1, registry.Length);
}
[TestMethod]
public void LoadManifest_populates_registry()
{
using var registry = new AliasRegistry();
registry.LoadManifest(ManifestJson);
using var registry = AliasRegistry.FromManifest(ManifestJson);
Assert.AreEqual(1, registry.Length);
}
[TestMethod]
public void NormalizeAndWrap_produces_envelope()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
@@ -93,8 +90,7 @@ public class AliasRegistryTests
[TestMethod]
public void NormalizeAndWrap_with_context_and_parameters()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
@@ -115,8 +111,7 @@ public class AliasRegistryTests
[TestMethod]
public void Denormalize_restores_properties()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
var normalized = @"{
""name"": ""acct1"",
@@ -137,8 +132,7 @@ public class AliasRegistryTests
[TestMethod]
public void Round_trip_normalize_then_denormalize()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
using var registry = AliasRegistry.FromJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
@@ -166,8 +160,7 @@ public class AliasRegistryTests
[TestMethod]
public void DataPlane_manifest_normalize()
{
using var registry = new AliasRegistry();
registry.LoadManifest(ManifestJson);
using var registry = AliasRegistry.FromManifest(ManifestJson);
var resource = @"{
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
@@ -185,7 +178,7 @@ public class AliasRegistryTests
[ExpectedException(typeof(InvalidOperationException))]
public void LoadJson_invalid_throws()
{
using var registry = new AliasRegistry();
registry.LoadJson("not valid json");
using var builder = new AliasRegistryBuilder();
builder.LoadJson("not valid json");
}
}

View File

@@ -0,0 +1,436 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text.Json.Nodes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
namespace Regorus.Tests;
/// <summary>
/// Tests for <see cref="AzurePolicyCompiler"/> — compiling Azure Policy JSON
/// policyRule and policyDefinition into RVM programs and evaluating them.
/// </summary>
[TestClass]
public class AzurePolicyCompilerTests
{
// -----------------------------------------------------------------------
// Test data
// -----------------------------------------------------------------------
private const string StorageAliasesJson = @"[{
""namespace"": ""Microsoft.Storage"",
""resourceTypes"": [{
""resourceType"": ""storageAccounts"",
""capabilities"": ""SupportsTags, SupportsLocation"",
""aliases"": [
{
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
""paths"": []
},
{
""name"": ""Microsoft.Storage/storageAccounts/minimumTlsVersion"",
""defaultPath"": ""properties.minimumTlsVersion"",
""paths"": []
}
]
}]
}]";
/// <summary>Simple policy rule that checks the resource type.</summary>
private const string SimpleAuditRule = @"{
""if"": {
""field"": ""type"",
""equals"": ""Microsoft.Storage/storageAccounts""
},
""then"": { ""effect"": ""audit"" }
}";
/// <summary>Policy rule that uses an alias to check HTTPS-only.</summary>
private const string HttpsDenyRule = @"{
""if"": {
""allOf"": [
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
]
},
""then"": { ""effect"": ""deny"" }
}";
/// <summary>Full policy definition with parameters.</summary>
private const string PolicyDefinitionWithParams = @"{
""displayName"": ""Require HTTPS for storage accounts"",
""policyType"": ""Custom"",
""mode"": ""Indexed"",
""parameters"": {
""effect"": {
""type"": ""String"",
""defaultValue"": ""deny""
}
},
""policyRule"": {
""if"": {
""allOf"": [
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
]
},
""then"": { ""effect"": ""[parameters('effect')]"" }
}
}";
// -----------------------------------------------------------------------
// Helper
// -----------------------------------------------------------------------
/// <summary>
/// Wrap a normalized resource JSON and parameters into the input envelope
/// expected by compiled Azure Policy RVM programs.
/// </summary>
private static string WrapInput(string resourceJson, string parametersJson = "{}")
{
return $@"{{""resource"": {resourceJson}, ""parameters"": {parametersJson}}}";
}
/// <summary>
/// Compile a policy rule, load it into an RVM, set input, and execute.
/// Returns the result string from <c>ExecuteEntryPoint("main")</c>.
/// </summary>
private static string? CompileAndEval(
AliasRegistry? registry,
string policyRuleJson,
string inputJson)
{
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, policyRuleJson);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(inputJson);
return vm.ExecuteEntryPoint("main");
}
// -----------------------------------------------------------------------
// CompilePolicyRule tests
// -----------------------------------------------------------------------
[TestMethod]
public void CompilePolicyRule_no_aliases_succeeds()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
Assert.IsNotNull(program);
}
[TestMethod]
public void CompilePolicyRule_with_aliases_succeeds()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
Assert.IsNotNull(program);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void CompilePolicyRule_null_json_throws()
{
AzurePolicyCompiler.CompilePolicyRule(null, null!);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void CompilePolicyRule_invalid_json_throws()
{
AzurePolicyCompiler.CompilePolicyRule(null, "not valid json");
}
// -----------------------------------------------------------------------
// CompilePolicyDefinition tests
// -----------------------------------------------------------------------
[TestMethod]
public void CompilePolicyDefinition_no_aliases_succeeds()
{
using var program = AzurePolicyCompiler.CompilePolicyDefinition(null, PolicyDefinitionWithParams);
Assert.IsNotNull(program);
}
[TestMethod]
public void CompilePolicyDefinition_with_aliases_succeeds()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
using var program = AzurePolicyCompiler.CompilePolicyDefinition(registry, PolicyDefinitionWithParams);
Assert.IsNotNull(program);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void CompilePolicyDefinition_null_json_throws()
{
AzurePolicyCompiler.CompilePolicyDefinition(null, null!);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void CompilePolicyDefinition_invalid_json_throws()
{
AzurePolicyCompiler.CompilePolicyDefinition(null, @"{""not"": ""a definition""}");
}
// -----------------------------------------------------------------------
// End-to-end evaluation tests
// -----------------------------------------------------------------------
[TestMethod]
public void Eval_simple_rule_matching_resource_returns_effect()
{
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts""}");
var result = CompileAndEval(null, SimpleAuditRule, input);
Assert.IsNotNull(result, "expected a result for matching resource");
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("audit", doc["effect"]?.GetValue<string>(),
$"expected 'audit' effect, got: {result}");
}
[TestMethod]
public void Eval_simple_rule_non_matching_resource_returns_undefined()
{
var input = WrapInput(
@"{""type"": ""microsoft.compute/virtualmachines""}");
var result = CompileAndEval(null, SimpleAuditRule, input);
Assert.IsNotNull(result);
StringAssert.Contains(result!, "undefined",
"expected undefined for non-matching resource type");
}
[TestMethod]
public void Eval_alias_rule_non_compliant_returns_deny()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
// Non-compliant: HTTPS not enabled (normalized/lowercased form)
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": false}");
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
$"expected 'deny' for non-compliant resource, got: {result}");
}
[TestMethod]
public void Eval_alias_rule_compliant_returns_undefined()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
// Compliant: HTTPS enabled
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": true}");
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
StringAssert.Contains(result!, "undefined",
"expected undefined for compliant resource");
}
[TestMethod]
public void Eval_definition_with_default_parameters()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
using var program = AzurePolicyCompiler.CompilePolicyDefinition(
registry, PolicyDefinitionWithParams);
using var vm = new Rvm();
vm.LoadProgram(program);
// Non-compliant resource
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts"", ""supportshttpstrafficonly"": false}");
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
// Default parameter value is "deny"
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
$"expected default 'deny' effect, got: {result}");
}
[TestMethod]
public void Eval_with_normalized_arm_resource_end_to_end()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
// Simulate the full production flow:
// 1. Start with an ARM resource
var armResource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""mystorage"",
""location"": ""eastus"",
""properties"": {
""supportsHttpsTrafficOnly"": false,
""minimumTlsVersion"": ""TLS1_0""
}
}";
// 2. Normalize via AliasRegistry
var normalizedEnvelope = registry.NormalizeAndWrap(
armResource,
apiVersion: null,
contextJson: "{}",
parametersJson: "{}");
Assert.IsNotNull(normalizedEnvelope);
// 3. Compile the policy rule
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
// 4. Execute
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(normalizedEnvelope!);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
$"expected 'deny' for non-HTTPS storage account, got: {result}");
}
[TestMethod]
public void Eval_normalized_compliant_resource_end_to_end()
{
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var armResource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""secureastorage"",
""location"": ""westus"",
""properties"": {
""supportsHttpsTrafficOnly"": true,
""minimumTlsVersion"": ""TLS1_2""
}
}";
var normalizedEnvelope = registry.NormalizeAndWrap(
armResource,
apiVersion: null,
contextJson: "{}",
parametersJson: "{}");
Assert.IsNotNull(normalizedEnvelope);
using var program = AzurePolicyCompiler.CompilePolicyRule(registry, HttpsDenyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetInputJson(normalizedEnvelope!);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
StringAssert.Contains(result!, "undefined",
"expected undefined for compliant HTTPS storage account");
}
[TestMethod]
public void Program_can_be_serialized_and_reloaded()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
// Serialize to binary
var binary = program.SerializeBinary();
Assert.IsTrue(binary.Length > 0, "serialized program should not be empty");
// Deserialize and run
using var restored = Program.DeserializeBinary(binary, out var isPartial);
Assert.IsFalse(isPartial, "program should not be partial");
using var vm = new Rvm();
vm.LoadProgram(restored);
var input = WrapInput(@"{""type"": ""microsoft.storage/storageaccounts""}");
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("audit", doc["effect"]?.GetValue<string>());
}
[TestMethod]
public void Program_generates_listing()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, SimpleAuditRule);
var listing = program.GenerateListing();
Assert.IsFalse(string.IsNullOrWhiteSpace(listing),
"generated listing should not be empty");
}
// -----------------------------------------------------------------------
// Context-dependent policy tests
// -----------------------------------------------------------------------
/// Policy rule that uses subscription() context function.
private const string ContextPolicyRule = @"{
""if"": {
""allOf"": [
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
{ ""value"": ""[subscription().subscriptionId]"", ""equals"": ""sub-123"" }
]
},
""then"": { ""effect"": ""deny"" }
}";
[TestMethod]
public void Eval_context_policy_with_set_context_returns_effect()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, ContextPolicyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetContextJson(@"{""subscription"": {""subscriptionId"": ""sub-123""}}");
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts""}");
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
var doc = JsonNode.Parse(result!)!;
Assert.AreEqual("deny", doc["effect"]?.GetValue<string>(),
$"expected 'deny' with matching context, got: {result}");
}
[TestMethod]
public void Eval_context_policy_without_context_returns_undefined()
{
using var program = AzurePolicyCompiler.CompilePolicyRule(null, ContextPolicyRule);
using var vm = new Rvm();
vm.LoadProgram(program);
// No context set — subscription() will be undefined
var input = WrapInput(
@"{""type"": ""microsoft.storage/storageaccounts""}");
vm.SetInputJson(input);
var result = vm.ExecuteEntryPoint("main");
Assert.IsNotNull(result);
StringAssert.Contains(result!, "undefined",
"expected undefined without context set");
}
}

View File

@@ -62,8 +62,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_NormalizeAndWrap_produces_input_envelope()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var result = registry.NormalizeAndWrap(
StorageResourceJson,
@@ -84,8 +83,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_NormalizeAndWrap_flattens_properties()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var result = registry.NormalizeAndWrap(StorageResourceJson);
Assert.IsNotNull(result);
@@ -107,8 +105,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_NormalizeAndWrap_preserves_type_field()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var result = registry.NormalizeAndWrap(StorageResourceJson);
var doc = JsonNode.Parse(result!);
@@ -125,8 +122,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_NormalizeAndWrap_includes_parameters()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
var parametersJson = @"{ ""effect"": ""Deny"" }";
var result = registry.NormalizeAndWrap(
@@ -143,8 +139,7 @@ public class AzurePolicyTests
[TestMethod]
public void AliasRegistry_Denormalize_roundtrips_correctly()
{
using var registry = new AliasRegistry();
registry.LoadJson(StorageAliasesJson);
using var registry = AliasRegistry.FromJson(StorageAliasesJson);
// Normalize the ARM resource.
var envelope = registry.NormalizeAndWrap(StorageResourceJson);
@@ -177,8 +172,7 @@ public class AzurePolicyTests
}
var aliasesJson = File.ReadAllText(aliasesPath);
using var registry = new AliasRegistry();
registry.LoadJson(aliasesJson);
using var registry = AliasRegistry.FromJson(aliasesJson);
// The test_aliases.json file contains multiple providers.
Assert.IsTrue(registry.Length > 0,

View File

@@ -8,51 +8,43 @@ using Regorus.Internal;
namespace Regorus
{
/// <summary>
/// Manages Azure Policy alias definitions used for resource normalization
/// Immutable Azure Policy alias registry used for resource normalization
/// and policy compilation.
/// </summary>
public unsafe sealed class AliasRegistry : SafeHandleWrapper
{
/// <summary>
/// Create an empty alias registry.
/// </summary>
public AliasRegistry()
: base(RegorusAliasRegistryHandle.Create(), nameof(AliasRegistry))
internal AliasRegistry(RegorusAliasRegistryHandle handle)
: base(handle, nameof(AliasRegistry))
{
}
/// <summary>
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
/// Create an empty immutable alias registry.
/// </summary>
/// <param name="json">JSON array of ProviderAliases (e.g. from Get-AzPolicyAlias or ResourceTypesAndAliases.json)</param>
public void LoadJson(string json)
public static AliasRegistry Empty()
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(regPtr =>
{
CheckAndDropResult(API.regorus_alias_registry_load_json(
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
return 0;
});
});
using var builder = new AliasRegistryBuilder();
return builder.Build();
}
/// <summary>
/// Load a data-plane policy manifest from a JSON string.
/// Create an immutable alias registry from control-plane alias JSON.
/// </summary>
/// <param name="json">JSON object containing a DataPolicyManifest</param>
public void LoadManifest(string json)
public static AliasRegistry FromJson(string json)
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(regPtr =>
{
CheckAndDropResult(API.regorus_alias_registry_load_manifest(
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
return 0;
});
});
using var builder = new AliasRegistryBuilder();
builder.LoadJson(json);
return builder.Build();
}
/// <summary>
/// Create an immutable alias registry from a data-plane manifest JSON document.
/// </summary>
public static AliasRegistry FromManifest(string json)
{
using var builder = new AliasRegistryBuilder();
builder.LoadManifest(json);
return builder.Build();
}
/// <summary>
@@ -74,11 +66,6 @@ namespace Regorus
/// Normalize an ARM resource JSON and wrap it into the standard input envelope
/// expected by a compiled Azure Policy program.
/// </summary>
/// <param name="resourceJson">Raw ARM resource JSON</param>
/// <param name="apiVersion">API version string (e.g. "2023-01-01"), or null to use default alias paths</param>
/// <param name="contextJson">Additional context JSON object (pass "{}" if none)</param>
/// <param name="parametersJson">Policy parameter values JSON (pass "{}" if none)</param>
/// <returns>JSON string: { "resource": &lt;normalized&gt;, "context": &lt;context&gt;, "parameters": &lt;params&gt; }</returns>
public string? NormalizeAndWrap(string resourceJson, string? apiVersion = null, string contextJson = "{}", string parametersJson = "{}")
{
return Utf8Marshaller.WithUtf8(resourceJson, resPtr =>
@@ -96,27 +83,22 @@ namespace Regorus
(byte*)ctxPtr, (byte*)paramsPtr));
});
}
else
{
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_normalize_and_wrap(
(RegorusAliasRegistry*)regPtr,
(byte*)resPtr, (byte*)apiPtr,
(byte*)ctxPtr, (byte*)paramsPtr));
}));
}
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_normalize_and_wrap(
(RegorusAliasRegistry*)regPtr,
(byte*)resPtr, (byte*)apiPtr,
(byte*)ctxPtr, (byte*)paramsPtr));
}));
})));
}
/// <summary>
/// Denormalize a previously-normalized resource JSON back to ARM format.
/// </summary>
/// <param name="normalizedJson">The normalized resource JSON</param>
/// <param name="apiVersion">API version string, or null to use default alias paths</param>
/// <returns>Denormalized ARM JSON string</returns>
public string? Denormalize(string normalizedJson, string? apiVersion = null)
{
return Utf8Marshaller.WithUtf8(normalizedJson, normPtr =>
@@ -131,23 +113,16 @@ namespace Regorus
(byte*)normPtr, null));
});
}
else
{
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_denormalize(
(RegorusAliasRegistry*)regPtr,
(byte*)normPtr, (byte*)apiPtr));
}));
}
});
}
private static string? CheckAndDropResult(RegorusResult result)
{
return ResultHelpers.GetStringResult(result);
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_denormalize(
(RegorusAliasRegistry*)regPtr,
(byte*)normPtr, (byte*)apiPtr));
}));
});
}
}
}

View File

@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Mutable, single-threaded builder for <see cref="AliasRegistry"/>.
/// Load alias data, then call <see cref="Build"/> to freeze the registry.
/// </summary>
public unsafe sealed class AliasRegistryBuilder : SafeHandleWrapper
{
/// <summary>
/// Create an empty alias registry builder.
/// </summary>
public AliasRegistryBuilder()
: base(RegorusAliasRegistryBuilderHandle.Create(), nameof(AliasRegistryBuilder))
{
}
/// <summary>
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
/// </summary>
public void LoadJson(string json)
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(builderPtr =>
{
ResultHelpers.GetStringResult(API.regorus_alias_registry_builder_load_json(
(RegorusAliasRegistryBuilder*)builderPtr,
(byte*)jsonPtr));
});
});
}
/// <summary>
/// Load a data-plane policy manifest from a JSON string.
/// </summary>
public void LoadManifest(string json)
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(builderPtr =>
{
ResultHelpers.GetStringResult(API.regorus_alias_registry_builder_load_manifest(
(RegorusAliasRegistryBuilder*)builderPtr,
(byte*)jsonPtr));
});
});
}
/// <summary>
/// Freeze the builder into an immutable, thread-safe alias registry.
/// </summary>
public AliasRegistry Build()
{
return UseHandle(builderPtr =>
{
var registryPtr = ResultHelpers.GetPointerResult(
API.regorus_alias_registry_builder_build((RegorusAliasRegistryBuilder*)builderPtr));
return new AliasRegistry(RegorusAliasRegistryHandle.FromPointer(registryPtr));
});
}
}
}

View File

@@ -0,0 +1,183 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Provides static methods for compiling Azure Policy JSON definitions
/// into RVM programs that can be executed by <see cref="Rvm"/>.
/// </summary>
/// <remarks>
/// <para>
/// This class bridges the gap between Azure Policy JSON (the native
/// Azure policy language with <c>policyRule</c>, <c>field</c>,
/// <c>equals</c>, etc.) and Regorus's RVM execution engine.
/// </para>
///
/// <para>
/// <b>Typical workflow:</b>
/// </para>
/// <list type="number">
/// <item>Load alias definitions with <see cref="AliasRegistryBuilder"/> and freeze them into an <see cref="AliasRegistry"/>.</item>
/// <item>Normalize the ARM resource via <see cref="AliasRegistry.NormalizeAndWrap"/>.</item>
/// <item>Compile the JSON policyRule with <see cref="CompilePolicyRule"/> or the
/// full definition with <see cref="CompilePolicyDefinition"/>.</item>
/// <item>Execute the resulting <see cref="Program"/> in an <see cref="Rvm"/>
/// instance with the normalized input.</item>
/// </list>
///
/// <para>
/// <b>Context-dependent policies:</b> Policies that use context functions
/// such as <c>subscription()</c>, <c>resourceGroup()</c>, or
/// <c>requestContext()</c> require the VM context to be set separately via
/// <see cref="Rvm.SetContextJson"/> before execution. The context JSON
/// returned by <see cref="AliasRegistry.NormalizeAndWrap"/> is passed as
/// <c>input.context</c> but is <b>not</b> automatically wired into the VM's
/// ambient context — the caller must do both:
/// <c>vm.SetInputJson(envelope)</c> and <c>vm.SetContextJson(contextJson)</c>.
/// </para>
/// </remarks>
public static unsafe class AzurePolicyCompiler
{
/// <summary>
/// Compile an Azure Policy JSON policy rule into an RVM <see cref="Program"/>.
/// </summary>
/// <param name="aliasRegistry">
/// Alias registry for resolving fully-qualified alias names in field
/// references. Pass <c>null</c> if no alias resolution is needed.
/// <para>
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
/// property paths and will silently produce incorrect evaluation results for
/// policies that use aliases. Modify/Append effect policies will also skip
/// the compile-time modifiability validation. Only pass <c>null</c> when the
/// policy is known to contain no alias references (e.g. simple type/location
/// checks or unit-test scenarios).
/// </para>
/// </param>
/// <param name="policyRuleJson">
/// JSON string containing the policyRule object, e.g.
/// <c>{ "if": { "field": "type", "equals": "..." }, "then": { "effect": "deny" } }</c>
/// </param>
/// <returns>
/// A compiled <see cref="Program"/> ready to be loaded into an
/// <see cref="Rvm"/> instance.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="policyRuleJson"/> is <c>null</c>.
/// </exception>
/// <exception cref="Exception">
/// Thrown when parsing or compilation fails.
/// </exception>
public static Program CompilePolicyRule(AliasRegistry? aliasRegistry, string policyRuleJson)
{
if (policyRuleJson is null)
{
throw new ArgumentNullException(nameof(policyRuleJson));
}
return Utf8Marshaller.WithUtf8(policyRuleJson, rulePtr =>
{
if (aliasRegistry is null)
{
var result = API.regorus_compile_azure_policy_rule(
null, (byte*)rulePtr);
return GetProgramResult(result);
}
else
{
return aliasRegistry.UseHandleForInterop(regPtr =>
{
var result = API.regorus_compile_azure_policy_rule(
(RegorusAliasRegistry*)regPtr, (byte*)rulePtr);
return GetProgramResult(result);
});
}
});
}
/// <summary>
/// Compile a full Azure Policy definition JSON into an RVM <see cref="Program"/>.
/// </summary>
/// <param name="aliasRegistry">
/// Alias registry for resolving fully-qualified alias names in field
/// references. Pass <c>null</c> if no alias resolution is needed.
/// <para>
/// <b>Warning:</b> When <c>null</c>, alias field references compile as raw
/// property paths and will silently produce incorrect evaluation results for
/// policies that use aliases. Modify/Append effect policies will also skip
/// the compile-time modifiability validation. Only pass <c>null</c> when the
/// policy is known to contain no alias references (e.g. simple type/location
/// checks or unit-test scenarios).
/// </para>
/// </param>
/// <param name="policyDefinitionJson">
/// JSON string containing the full policy definition, which includes
/// <c>policyRule</c>, <c>parameters</c>, <c>displayName</c>, etc.
/// Accepted in both wrapped and unwrapped forms.
/// </param>
/// <returns>
/// A compiled <see cref="Program"/> ready to be loaded into an
/// <see cref="Rvm"/> instance.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="policyDefinitionJson"/> is <c>null</c>.
/// </exception>
/// <exception cref="Exception">
/// Thrown when parsing or compilation fails.
/// </exception>
public static Program CompilePolicyDefinition(AliasRegistry? aliasRegistry, string policyDefinitionJson)
{
if (policyDefinitionJson is null)
{
throw new ArgumentNullException(nameof(policyDefinitionJson));
}
return Utf8Marshaller.WithUtf8(policyDefinitionJson, defnPtr =>
{
if (aliasRegistry is null)
{
var result = API.regorus_compile_azure_policy_definition(
null, (byte*)defnPtr);
return GetProgramResult(result);
}
else
{
return aliasRegistry.UseHandleForInterop(regPtr =>
{
var result = API.regorus_compile_azure_policy_definition(
(RegorusAliasRegistry*)regPtr, (byte*)defnPtr);
return GetProgramResult(result);
});
}
});
}
private static Program GetProgramResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
{
throw new Exception("Expected program pointer but got different data type");
}
var handle = RegorusProgramHandle.FromPointer((IntPtr)result.pointer_value);
return new Program(handle);
}
finally
{
API.regorus_result_drop(result);
}
}
}
}

View File

@@ -178,6 +178,14 @@ namespace Regorus.Internal
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_rvm_set_input(RegorusRvm* vm, byte* input_json);
/// <summary>
/// Set the context document for the RVM.
/// The context provides host-supplied ambient data (e.g. resourceGroup(), subscription())
/// that Azure Policy functions can access.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_rvm_set_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_rvm_set_context(RegorusRvm* vm, byte* context_json);
/// <summary>
/// Execute the program.
/// </summary>
@@ -490,6 +498,20 @@ namespace Regorus.Internal
[DllImport(LibraryName, EntryPoint = "regorus_compile_policy_for_target", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_policy_for_target(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len);
/// <summary>
/// Compile an Azure Policy JSON policy rule into an RVM program.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compile_azure_policy_rule", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_azure_policy_rule(
RegorusAliasRegistry* registry, byte* policy_rule_json);
/// <summary>
/// Compile a full Azure Policy definition JSON into an RVM program.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compile_azure_policy_definition", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_azure_policy_definition(
RegorusAliasRegistry* registry, byte* policy_definition_json);
#endregion
#region Compiled Policy Methods
@@ -673,10 +695,34 @@ namespace Regorus.Internal
#region Alias Registry Methods
/// <summary>
/// Create a new, empty AliasRegistry.
/// Create a new alias registry builder.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusAliasRegistry* regorus_alias_registry_new();
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusAliasRegistryBuilder* regorus_alias_registry_builder_new();
/// <summary>
/// Drop an alias registry builder.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_alias_registry_builder_drop(RegorusAliasRegistryBuilder* builder);
/// <summary>
/// Load control-plane alias data into the builder.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_builder_load_json(RegorusAliasRegistryBuilder* builder, byte* json);
/// <summary>
/// Load a data-plane policy manifest into the builder.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_builder_load_manifest(RegorusAliasRegistryBuilder* builder, byte* json);
/// <summary>
/// Freeze a builder into an immutable alias registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_builder_build", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_builder_build(RegorusAliasRegistryBuilder* builder);
/// <summary>
/// Drop an AliasRegistry.
@@ -684,18 +730,6 @@ namespace Regorus.Internal
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_alias_registry_drop(RegorusAliasRegistry* registry);
/// <summary>
/// Load control-plane alias data (array of ProviderAliases) into the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_load_json(RegorusAliasRegistry* registry, byte* json);
/// <summary>
/// Load a data-plane policy manifest into the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_load_manifest(RegorusAliasRegistry* registry, byte* json);
/// <summary>
/// Return the number of resource types loaded in the alias registry.
/// </summary>
@@ -923,6 +957,14 @@ namespace Regorus.Internal
public byte* content;
}
/// <summary>
/// Wrapper for AliasRegistryBuilder.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusAliasRegistryBuilder
{
}
/// <summary>
/// Wrapper for AliasRegistry.
/// </summary>

View File

@@ -15,7 +15,7 @@ namespace Regorus
/// </summary>
public unsafe sealed class Program : SafeHandleWrapper
{
private Program(RegorusProgramHandle handle)
internal Program(RegorusProgramHandle handle)
: base(handle, nameof(Program))
{
}

View File

@@ -69,5 +69,29 @@ namespace Regorus.Internal
API.regorus_result_drop(result);
}
}
internal static IntPtr GetPointerResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
{
throw new InvalidOperationException("Expected pointer result.");
}
return (IntPtr)result.pointer_value;
}
finally
{
API.regorus_result_drop(result);
}
}
}
}

View File

@@ -106,6 +106,24 @@ namespace Regorus
});
}
/// <summary>
/// Set the context document for the VM.
/// The context provides host-supplied ambient data (e.g. resourceGroup(),
/// subscription()) that Azure Policy functions can access via LoadContext
/// instructions.
/// </summary>
public void SetContextJson(string contextJson)
{
Utf8Marshaller.WithUtf8(contextJson, contextPtr =>
{
UseHandle(vmPtr =>
{
CheckAndDropResult(API.regorus_rvm_set_context((RegorusRvm*)vmPtr, (byte*)contextPtr));
return 0;
});
});
}
/// <summary>
/// Set the execution mode (0 = run-to-completion, 1 = suspendable).
/// </summary>

View File

@@ -184,28 +184,48 @@ namespace Regorus
}
}
internal sealed class RegorusAliasRegistryBuilderHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private RegorusAliasRegistryBuilderHandle() : base(ownsHandle: true)
{
}
internal static RegorusAliasRegistryBuilderHandle Create()
{
unsafe
{
var raw = Internal.API.regorus_alias_registry_builder_new();
if (raw is null)
{
throw new InvalidOperationException("Failed to create Regorus alias registry builder.");
}
var handle = new RegorusAliasRegistryBuilderHandle();
handle.SetHandle((IntPtr)raw);
return handle;
}
}
protected override bool ReleaseHandle()
{
if (!IsInvalid)
{
unsafe
{
Internal.API.regorus_alias_registry_builder_drop((Internal.RegorusAliasRegistryBuilder*)handle);
}
SetHandle(IntPtr.Zero);
}
return true;
}
}
internal sealed class RegorusAliasRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private RegorusAliasRegistryHandle() : base(ownsHandle: true)
{
}
internal static RegorusAliasRegistryHandle Create()
{
unsafe
{
var raw = Internal.API.regorus_alias_registry_new();
if (raw is null)
{
throw new InvalidOperationException("Failed to create Regorus alias registry.");
}
var handle = new RegorusAliasRegistryHandle();
handle.SetHandle((IntPtr)raw);
return handle;
}
}
internal static RegorusAliasRegistryHandle FromPointer(IntPtr pointer)
{
if (pointer == IntPtr.Zero)

View File

@@ -232,6 +232,9 @@ allow if {
Console.WriteLine("\n8. RVM host await (suspend/resume):");
DemonstrateRvmHostAwait();
Console.WriteLine("\n9. Azure Policy JSON compilation:");
DemonstrateAzurePolicyJsonCompilation();
}
static void DemonstrateConcurrentEvaluation(Regorus.CompiledPolicy compiledPolicy)
@@ -492,4 +495,80 @@ allow if {
var resumed = vm.Resume("{\"tier\":\"gold\"}");
Console.WriteLine($"HostAwait resumed result: {resumed}");
}
// Azure Policy JSON constants
private const string STORAGE_ALIASES_JSON = @"[{
""namespace"": ""Microsoft.Storage"",
""resourceTypes"": [{
""resourceType"": ""storageAccounts"",
""capabilities"": ""SupportsTags, SupportsLocation"",
""aliases"": [
{
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
""paths"": []
}
]
}]
}]";
private const string HTTPS_DENY_RULE = @"{
""if"": {
""allOf"": [
{ ""field"": ""type"", ""equals"": ""Microsoft.Storage/storageAccounts"" },
{ ""field"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"", ""equals"": false }
]
},
""then"": { ""effect"": ""deny"" }
}";
static void DemonstrateAzurePolicyJsonCompilation()
{
// 1. Set up alias registry
using var registry = Regorus.AliasRegistry.FromJson(STORAGE_ALIASES_JSON);
Console.WriteLine("Loaded storage account aliases");
// 2. Compile the JSON policy rule directly (no Rego needed)
using var program = Regorus.AzurePolicyCompiler.CompilePolicyRule(registry, HTTPS_DENY_RULE);
Console.WriteLine("Compiled Azure Policy JSON rule to RVM program");
// 3. Normalize an ARM resource
var armResource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""insecurestorage"",
""location"": ""eastus"",
""properties"": { ""supportsHttpsTrafficOnly"": false }
}";
var envelope = registry.NormalizeAndWrap(armResource, apiVersion: null, contextJson: "{}", parametersJson: "{}");
Console.WriteLine($"Normalized ARM resource to evaluation envelope");
// 4. Execute in the RVM
// Note: For policies using context functions (subscription(), resourceGroup()),
// call vm.SetContextJson(contextJson) before execution. The context from
// NormalizeAndWrap is in the envelope but must also be set on the VM separately.
using var vm = new Regorus.Rvm();
vm.LoadProgram(program);
vm.SetInputJson(envelope!);
// vm.SetContextJson(contextJson); // ← required for context-dependent policies
var result = vm.ExecuteEntryPoint("main");
Console.WriteLine($"Evaluation result (non-compliant): {result}");
// 5. Test with a compliant resource
var compliantResource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""name"": ""securestorage"",
""location"": ""eastus"",
""properties"": { ""supportsHttpsTrafficOnly"": true }
}";
var compliantEnvelope = registry.NormalizeAndWrap(compliantResource, apiVersion: null, contextJson: "{}", parametersJson: "{}");
using var vm2 = new Regorus.Rvm();
vm2.LoadProgram(program);
vm2.SetInputJson(compliantEnvelope!);
var compliantResult = vm2.ExecuteEntryPoint("main");
Console.WriteLine($"Evaluation result (compliant): {compliantResult}");
// 6. Demonstrate program serialization
var binary = program.SerializeBinary();
Console.WriteLine($"Serialized program size: {binary.Length} bytes");
}
}

449
bindings/ffi/Cargo.lock generated
View File

@@ -92,15 +92,15 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.102"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "autocfg"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bit-set"
@@ -119,9 +119,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.11.1"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "borrow-or-share"
@@ -131,19 +131,19 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
version = "1.12.1"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
dependencies = [
"memchr",
"serde",
"serde_core",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytecount"
@@ -153,9 +153,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cbindgen"
version = "0.29.2"
version = "0.29.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799"
checksum = "2ecb53484c9c167ba674026b656d8a27d7657a58e6066aa902bfb1a4aa00ae20"
dependencies = [
"clap",
"heck",
@@ -172,9 +172,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.61"
version = "1.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -188,9 +188,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -199,9 +199,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.44"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -222,18 +222,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.6.1"
version = "4.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
version = "4.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
dependencies = [
"anstream",
"anstyle",
@@ -279,15 +279,15 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "dashmap"
version = "6.1.0"
version = "6.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -305,9 +305,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]]
name = "displaydoc"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
@@ -353,9 +353,9 @@ dependencies = [
[[package]]
name = "fancy-regex"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
dependencies = [
"bit-set",
"regex-automata",
@@ -385,12 +385,6 @@ dependencies = [
"serde",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
@@ -456,23 +450,21 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
"rand_core",
"wasip2",
"wasip3",
]
[[package]]
name = "globset"
version = "0.4.18"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
dependencies = [
"aho-corasick",
"bstr",
@@ -488,30 +480,15 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "heck"
version = "0.5.0"
@@ -650,12 +627,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
@@ -684,7 +655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
"hashbrown 0.17.1",
"serde",
"serde_core",
]
@@ -709,21 +680,20 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.97"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "jsonschema"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
checksum = "281c43ff06dcb331e9356d30e38853d559ce3d0a3f693e0b0e102667dec14fb1"
dependencies = [
"ahash",
"bytecount",
@@ -734,30 +704,33 @@ dependencies = [
"getrandom 0.3.4",
"idna",
"itoa",
"jsonschema-regex",
"num-cmp",
"num-traits",
"percent-encoding",
"referencing",
"regex",
"regex-syntax",
"serde",
"serde_json",
"unicode-general-category",
"uuid-simd",
]
[[package]]
name = "jsonschema-regex"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee0b351864e7ffbc5db9273daf7fa1b4d5177b0946713d667ca571b83c0b4045"
dependencies = [
"regex-syntax",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
@@ -787,21 +760,27 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.29"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "lru"
version = "0.16.4"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6"
[[package]]
name = "memchr"
version = "2.8.0"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "micromap"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
[[package]]
name = "msvc_spectre_libs"
@@ -818,7 +797,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-complex",
"num-integer",
"num-iter",
@@ -828,9 +807,19 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.6"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
dependencies = [
"num-integer",
"num-traits",
@@ -862,11 +851,10 @@ dependencies = [
[[package]]
name = "num-iter"
version = "0.1.45"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
@@ -877,7 +865,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-integer",
"num-traits",
]
@@ -985,16 +973,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -1006,9 +984,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
@@ -1027,12 +1005,12 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
"chacha20",
"getrandom 0.4.2",
"getrandom 0.4.3",
"rand_core",
]
@@ -1073,14 +1051,16 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
checksum = "348e860aeb0b7bd035778fd11dd9cd5290d32e4aed3b8f2274a00287a9fd362b"
dependencies = [
"ahash",
"fluent-uri",
"getrandom 0.3.4",
"hashbrown 0.16.1",
"hashbrown 0.17.1",
"itoa",
"micromap",
"parking_lot",
"percent-encoding",
"serde_json",
@@ -1088,9 +1068,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.3"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
@@ -1100,9 +1080,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.14"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [
"aho-corasick",
"memchr",
@@ -1111,13 +1091,13 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "regorus"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"anyhow",
"chrono",
@@ -1125,7 +1105,7 @@ dependencies = [
"dashmap",
"data-encoding",
"globset",
"hashbrown 0.16.1",
"hashbrown 0.17.1",
"icu_casemap",
"indexmap",
"ipnet",
@@ -1133,7 +1113,7 @@ dependencies = [
"lazy_static",
"lru",
"msvc_spectre_libs",
"num-bigint",
"num-bigint 0.5.1",
"num-traits",
"parking_lot",
"postcard",
@@ -1152,7 +1132,7 @@ dependencies = [
[[package]]
name = "regorus-ffi"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"anyhow",
"cbindgen",
@@ -1163,7 +1143,7 @@ dependencies = [
[[package]]
name = "regorus-mimalloc"
version = "2.2.6"
version = "2.2.7"
dependencies = [
"regorus-mimalloc-sys",
]
@@ -1190,9 +1170,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ryu"
@@ -1244,9 +1224,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -1279,15 +1259,15 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.3.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "siphasher"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "slab"
@@ -1297,15 +1277,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "spin"
version = "0.10.0"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
checksum = "8abadc99fd9c7bbb7d0ca2b31d72a067d0c0dcd7aad25ab8cac71ba91417694b"
[[package]]
name = "stable_deref_trait"
@@ -1321,9 +1301,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
@@ -1348,7 +1328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"getrandom 0.4.3",
"once_cell",
"rustix",
"windows-sys",
@@ -1415,14 +1395,14 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow 1.0.2",
"winnow 1.0.4",
]
[[package]]
name = "toml_writer"
version = "1.1.1+spec-1.1.0"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
[[package]]
name = "unicode-general-category"
@@ -1436,12 +1416,6 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
@@ -1474,11 +1448,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.23.1"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
dependencies = [
"getrandom 0.4.2",
"getrandom 0.4.3",
"rand",
]
@@ -1506,27 +1480,18 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [
"cfg-if",
"once_cell",
@@ -1537,9 +1502,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1547,9 +1512,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1560,47 +1525,13 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "windows-core"
version = "0.62.2"
@@ -1677,18 +1608,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
[[package]]
name = "winnow"
version = "1.0.2"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
[[package]]
name = "wit-bindgen"
@@ -1696,85 +1618,6 @@ version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.3"
@@ -1783,9 +1626,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yoke"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -1806,18 +1649,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
dependencies = [
"proc-macro2",
"quote",
@@ -1826,9 +1669,9 @@ dependencies = [
[[package]]
name = "zerofrom"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
@@ -1882,6 +1725,6 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

View File

@@ -2,7 +2,7 @@
[package]
name = "regorus-ffi"
version = "0.10.0"
version = "0.11.0"
edition = "2021"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
@@ -13,7 +13,7 @@ crate-type = ["cdylib", "staticlib"]
[dependencies]
anyhow = "1.0"
regorus = { path = "../..", default-features = false }
serde_json = "1.0.140"
serde_json = "1.0.150"
parking_lot = { version = "0.12", optional = true }
[profile.release]

View File

@@ -5,66 +5,108 @@
#![cfg(feature = "azure_policy")]
use crate::common::{from_c_str, to_ref, RegorusResult, RegorusStatus};
use crate::common::{from_c_str, to_ref, to_shared_ref, RegorusResult, RegorusStatus};
use crate::panic_guard::with_unwind_guard;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use anyhow::Result;
use core::ffi::c_char;
use core::ptr;
use alloc::sync::Arc;
use anyhow::{anyhow, Result};
use core::ffi::{c_char, c_void};
use core::{mem, ptr};
use regorus::languages::azure_policy::aliases::AliasRegistry;
/// Opaque wrapper for `AliasRegistry`.
pub struct RegorusAliasRegistry {
registry: AliasRegistry,
}
// ---------------------------------------------------------------------------
// Lifecycle
// ---------------------------------------------------------------------------
/// Create a new, empty `AliasRegistry`.
/// Mutable builder for `AliasRegistry`.
///
/// The caller must eventually call `regorus_alias_registry_drop` to free the handle.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_new() -> *mut RegorusAliasRegistry {
let wrapper = RegorusAliasRegistry {
registry: AliasRegistry::new(),
};
Box::into_raw(Box::new(wrapper))
/// This handle is intentionally single-threaded and must not be used
/// concurrently. Callers should finish loading alias data and then freeze it
/// into a `RegorusAliasRegistry` via `regorus_alias_registry_builder_build`.
pub struct RegorusAliasRegistryBuilder {
registry: AliasRegistry,
built: bool,
}
/// Drop a `RegorusAliasRegistry`.
impl RegorusAliasRegistryBuilder {
fn new() -> Self {
Self {
registry: AliasRegistry::new(),
built: false,
}
}
fn registry_mut(&mut self) -> Result<&mut AliasRegistry> {
if self.built {
return Err(anyhow!("alias registry builder has already been built"));
}
Ok(&mut self.registry)
}
fn build(&mut self) -> Result<RegorusAliasRegistry> {
if self.built {
return Err(anyhow!("alias registry builder has already been built"));
}
self.built = true;
Ok(RegorusAliasRegistry {
registry: Arc::new(mem::replace(&mut self.registry, AliasRegistry::new())),
})
}
}
/// Frozen, immutable alias registry.
pub struct RegorusAliasRegistry {
registry: Arc<AliasRegistry>,
}
impl RegorusAliasRegistry {
/// Return a shared reference to the inner registry for use by the compiler.
pub(crate) fn inner(&self) -> Arc<AliasRegistry> {
Arc::clone(&self.registry)
}
}
// ---------------------------------------------------------------------------
// Builder lifecycle
// ---------------------------------------------------------------------------
/// Create a new, empty `AliasRegistry` builder.
///
/// The caller must eventually call `regorus_alias_registry_builder_drop`.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_drop(registry: *mut RegorusAliasRegistry) {
if let Ok(r) = to_ref(registry) {
pub extern "C" fn regorus_alias_registry_builder_new() -> *mut RegorusAliasRegistryBuilder {
Box::into_raw(Box::new(RegorusAliasRegistryBuilder::new()))
}
/// Drop a `RegorusAliasRegistryBuilder`.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_builder_drop(builder: *mut RegorusAliasRegistryBuilder) {
if let Ok(builder) = to_ref(builder) {
unsafe {
let _ = Box::from_raw(ptr::from_mut(r));
let _ = Box::from_raw(ptr::from_mut(builder));
}
}
}
// ---------------------------------------------------------------------------
// Loading
// Builder loading
// ---------------------------------------------------------------------------
/// Load control-plane alias data (array of `ProviderAliases`) into the registry.
/// Load control-plane alias data (array of `ProviderAliases`) into the builder.
///
/// `json` must be a valid null-terminated UTF-8 string containing the JSON
/// array returned by `Get-AzPolicyAlias` or the static
/// `ResourceTypesAndAliases.json` file.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_load_json(
registry: *mut RegorusAliasRegistry,
pub extern "C" fn regorus_alias_registry_builder_load_json(
builder: *mut RegorusAliasRegistryBuilder,
json: *const c_char,
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<()> {
let json_str = from_c_str(json)?;
to_ref(registry)?.registry.load_from_json(&json_str)?;
to_ref(builder)?.registry_mut()?.load_from_json(&json_str)?;
Ok(())
}();
@@ -78,20 +120,20 @@ pub extern "C" fn regorus_alias_registry_load_json(
})
}
/// Load a data-plane policy manifest into the registry.
/// Load a data-plane policy manifest into the builder.
///
/// `json` must be a valid null-terminated UTF-8 string containing a single
/// `DataPolicyManifest` JSON object.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_load_manifest(
registry: *mut RegorusAliasRegistry,
pub extern "C" fn regorus_alias_registry_builder_load_manifest(
builder: *mut RegorusAliasRegistryBuilder,
json: *const c_char,
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<()> {
let json_str = from_c_str(json)?;
to_ref(registry)?
.registry
to_ref(builder)?
.registry_mut()?
.load_data_policy_manifest_json(&json_str)?;
Ok(())
}();
@@ -106,16 +148,52 @@ pub extern "C" fn regorus_alias_registry_load_manifest(
})
}
/// Freeze a builder into an immutable `RegorusAliasRegistry`.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_builder_build(
builder: *mut RegorusAliasRegistryBuilder,
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<*mut RegorusAliasRegistry> {
let registry = to_ref(builder)?.build()?;
Ok(Box::into_raw(Box::new(registry)))
}();
match output {
Ok(registry) => RegorusResult::ok_pointer(registry as *mut c_void),
Err(e) => {
RegorusResult::err_with_message(RegorusStatus::InvalidArgument, format!("{e}"))
}
}
})
}
// ---------------------------------------------------------------------------
// Queries
// Frozen registry lifecycle
// ---------------------------------------------------------------------------
/// Drop a `RegorusAliasRegistry`.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_drop(registry: *mut RegorusAliasRegistry) {
if let Ok(registry) = to_ref(registry) {
unsafe {
let _ = Box::from_raw(ptr::from_mut(registry));
}
}
}
// ---------------------------------------------------------------------------
// Frozen registry queries
// ---------------------------------------------------------------------------
/// Return the number of resource types loaded in the alias registry.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_len(registry: *mut RegorusAliasRegistry) -> RegorusResult {
pub extern "C" fn regorus_alias_registry_len(
registry: *const RegorusAliasRegistry,
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<i64> {
let len = to_ref(registry)?.registry.len();
let len = to_shared_ref(registry)?.registry.len();
Ok(len as i64)
}();
@@ -134,15 +212,9 @@ pub extern "C" fn regorus_alias_registry_len(registry: *mut RegorusAliasRegistry
///
/// Returns a JSON string:
/// `{ "resource": <normalized>, "context": <context>, "parameters": <params> }`.
///
/// * `resource_json` raw ARM resource JSON
/// * `api_version` API version string (e.g. `"2023-01-01"`), or null to use
/// the default alias paths
/// * `context_json` JSON object for additional context (pass `"{}"` if none)
/// * `parameters_json` JSON object of policy parameter values (pass `"{}"` if none)
#[no_mangle]
pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
registry: *mut RegorusAliasRegistry,
registry: *const RegorusAliasRegistry,
resource_json: *const c_char,
api_version: *const c_char,
context_json: *const c_char,
@@ -168,7 +240,7 @@ pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
let context = regorus::Value::from_json_str(&context_str)?;
let params = regorus::Value::from_json_str(&params_str)?;
let wrapped = to_ref(registry)?.registry.normalize_and_wrap(
let wrapped = to_shared_ref(registry)?.registry.normalize_and_wrap(
&resource,
api_ver.as_deref(),
Some(context),
@@ -185,14 +257,9 @@ pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
}
/// Denormalize a previously-normalized resource JSON back to ARM format.
///
/// * `normalized_json` the normalized resource JSON
/// * `api_version` API version string, or null to use the default alias paths
///
/// Returns the denormalized ARM JSON string.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_denormalize(
registry: *mut RegorusAliasRegistry,
registry: *const RegorusAliasRegistry,
normalized_json: *const c_char,
api_version: *const c_char,
) -> RegorusResult {
@@ -212,7 +279,7 @@ pub extern "C" fn regorus_alias_registry_denormalize(
let normalized = regorus::Value::from_json_str(&normalized_str)?;
let result = to_ref(registry)?
let result = to_shared_ref(registry)?
.registry
.denormalize(&normalized, api_ver.as_deref());
result.to_json_str()
@@ -232,12 +299,10 @@ mod tests {
use core::ffi::CStr;
use std::ffi::CString;
/// Helper: create a C string from a Rust &str.
fn c(s: &str) -> CString {
CString::new(s).expect("CString::new failed")
}
/// Helper: assert a RegorusResult has Ok status and extract string output.
fn assert_ok_string(r: &RegorusResult) -> String {
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
assert!(!r.output.is_null(), "expected non-null output");
@@ -248,12 +313,51 @@ mod tests {
s
}
/// Helper: assert a RegorusResult has Ok status with integer output.
fn assert_ok_int(r: &RegorusResult) -> i64 {
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
r.int_value
}
fn assert_ok_pointer(r: &RegorusResult) -> *mut c_void {
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
assert!(matches!(
r.data_type,
crate::common::RegorusDataType::Pointer
));
assert!(!r.pointer_value.is_null());
r.pointer_value
}
fn build_registry_with_json(json: &str) -> *mut RegorusAliasRegistry {
let builder = regorus_alias_registry_builder_new();
let json = c(json);
let r = regorus_alias_registry_builder_load_json(builder, json.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let r = regorus_alias_registry_builder_build(builder);
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
regorus_result_drop(r);
regorus_alias_registry_builder_drop(builder);
registry
}
fn build_registry_with_manifest(json: &str) -> *mut RegorusAliasRegistry {
let builder = regorus_alias_registry_builder_new();
let json = c(json);
let r = regorus_alias_registry_builder_load_manifest(builder, json.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let r = regorus_alias_registry_builder_build(builder);
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
regorus_result_drop(r);
regorus_alias_registry_builder_drop(builder);
registry
}
const ALIASES: &str = r#"[{
"namespace": "Microsoft.Storage",
"resourceTypes": [{
@@ -279,20 +383,21 @@ mod tests {
}"#;
#[test]
fn lifecycle_new_and_drop() {
let reg = regorus_alias_registry_new();
assert!(!reg.is_null());
regorus_alias_registry_drop(reg);
fn lifecycle_builder_build_and_drop() {
let builder = regorus_alias_registry_builder_new();
assert!(!builder.is_null());
let r = regorus_alias_registry_builder_build(builder);
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
regorus_result_drop(r);
regorus_alias_registry_builder_drop(builder);
regorus_alias_registry_drop(registry);
}
#[test]
fn load_json_and_check_len() {
let reg = regorus_alias_registry_new();
let json = c(ALIASES);
let r = regorus_alias_registry_load_json(reg, json.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let reg = build_registry_with_json(ALIASES);
let r = regorus_alias_registry_len(reg);
assert_eq!(assert_ok_int(&r), 1);
@@ -303,12 +408,7 @@ mod tests {
#[test]
fn load_manifest_and_check_len() {
let reg = regorus_alias_registry_new();
let json = c(MANIFEST);
let r = regorus_alias_registry_load_manifest(reg, json.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let reg = build_registry_with_manifest(MANIFEST);
let r = regorus_alias_registry_len(reg);
assert_eq!(assert_ok_int(&r), 1);
@@ -319,23 +419,39 @@ mod tests {
#[test]
fn load_invalid_json_returns_error() {
let reg = regorus_alias_registry_new();
let builder = regorus_alias_registry_builder_new();
let bad = c("not valid json");
let r = regorus_alias_registry_load_json(reg, bad.as_ptr());
let r = regorus_alias_registry_builder_load_json(builder, bad.as_ptr());
assert_ne!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
regorus_alias_registry_drop(reg);
regorus_alias_registry_builder_drop(builder);
}
#[test]
fn builder_cannot_be_reused_after_build() {
let builder = regorus_alias_registry_builder_new();
let r = regorus_alias_registry_builder_build(builder);
let registry = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
regorus_result_drop(r);
let aliases = c(ALIASES);
let r = regorus_alias_registry_builder_load_json(builder, aliases.as_ptr());
assert_ne!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let r = regorus_alias_registry_builder_build(builder);
assert_ne!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
regorus_alias_registry_builder_drop(builder);
regorus_alias_registry_drop(registry);
}
#[test]
fn normalize_and_wrap_round_trip() {
let reg = regorus_alias_registry_new();
let aliases = c(ALIASES);
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let reg = build_registry_with_json(ALIASES);
let resource = c(r#"{
"name": "acct1",
@@ -346,7 +462,6 @@ mod tests {
let ctx = c(r#"{"resourceGroup": {"name": "rg1"}}"#);
let params = c(r#"{"env": "prod"}"#);
// Normalize
let r = regorus_alias_registry_normalize_and_wrap(
reg,
resource.as_ptr(),
@@ -357,7 +472,6 @@ mod tests {
let envelope_json = assert_ok_string(&r);
regorus_result_drop(r);
// Parse and verify structure
let envelope: serde_json::Value =
serde_json::from_str(&envelope_json).expect("invalid JSON output");
assert!(
@@ -373,16 +487,13 @@ mod tests {
"envelope missing 'context'"
);
// The normalized resource should have lowercased alias fields
let res = &envelope["resource"];
assert_eq!(res["supportshttpstrafficonly"], true);
assert_eq!(res["name"], "acct1");
// Context and parameters should be passed through
assert_eq!(envelope["context"]["resourceGroup"]["name"], "rg1");
assert_eq!(envelope["parameters"]["env"], "prod");
// Denormalize the resource portion
let resource_json = serde_json::to_string(&res).expect("serialize resource");
let norm_cstr = c(&resource_json);
@@ -392,7 +503,6 @@ mod tests {
let denorm: serde_json::Value =
serde_json::from_str(&denorm_json).expect("invalid denorm JSON");
// Should be back under properties with restored casing
assert_eq!(
denorm["properties"]["supportsHttpsTrafficOnly"], true,
"expected restored casing under properties"
@@ -403,11 +513,7 @@ mod tests {
#[test]
fn denormalize_invalid_json_returns_error() {
let reg = regorus_alias_registry_new();
let aliases = c(ALIASES);
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let reg = build_registry_with_json(ALIASES);
let bad = c("not json");
let api = c("2023-01-01");
@@ -420,11 +526,7 @@ mod tests {
#[test]
fn normalize_data_plane_manifest() {
let reg = regorus_alias_registry_new();
let manifest = c(MANIFEST);
let r = regorus_alias_registry_load_manifest(reg, manifest.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let reg = build_registry_with_manifest(MANIFEST);
let resource = c(r#"{
"type": "Microsoft.KeyVault.Data/vaults/certificates",
@@ -453,7 +555,12 @@ mod tests {
#[test]
fn empty_registry_normalize() {
let reg = regorus_alias_registry_new();
let builder = regorus_alias_registry_builder_new();
let r = regorus_alias_registry_builder_build(builder);
let reg = assert_ok_pointer(&r) as *mut RegorusAliasRegistry;
regorus_result_drop(r);
regorus_alias_registry_builder_drop(builder);
let resource = c(r#"{"name": "test", "type": "Unknown/type", "properties": {"foo": 1}}"#);
let api = c("");
let ctx = c("{}");
@@ -470,7 +577,6 @@ mod tests {
regorus_result_drop(r);
let envelope: serde_json::Value = serde_json::from_str(&json).expect("invalid JSON");
// Without aliases, properties should still be flattened
assert_eq!(envelope["resource"]["foo"], 1);
assert_eq!(envelope["resource"]["name"], "test");

View File

@@ -236,6 +236,10 @@ pub(crate) fn to_ref<'a, T>(t: *mut T) -> Result<&'a mut T> {
unsafe { t.as_mut().ok_or_else(|| anyhow!("null pointer")) }
}
pub(crate) fn to_shared_ref<'a, T>(t: *const T) -> Result<&'a T> {
unsafe { t.as_ref().ok_or_else(|| anyhow!("null pointer")) }
}
pub(crate) fn to_regorus_result(r: Result<()>) -> RegorusResult {
match r {
Ok(()) => RegorusResult::ok_void(),

View File

@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
use crate::common::{from_c_str, to_shared_ref, RegorusResult, RegorusStatus};
use crate::compiled_policy::RegorusCompiledPolicy;
use crate::panic_guard::with_unwind_guard;
use alloc::boxed::Box;
@@ -208,6 +208,220 @@ fn convert_c_modules_to_rust(
Ok(policy_modules)
}
// ---------------------------------------------------------------------------
// Azure Policy JSON compilation
// ---------------------------------------------------------------------------
/// Compile an Azure Policy JSON policy rule into an RVM program.
///
/// Parses the JSON `policyRule` (the `{ "if": ..., "then": ... }` object),
/// resolves aliases using the provided registry, and compiles the result
/// into an RVM [`Program`] that can be loaded into a [`RegorusRvm`].
///
/// # Parameters
/// * `registry` - Alias registry handle, or null.
/// * `policy_rule_json` - JSON string containing the policyRule object
///
/// # Null registry behavior
///
/// When `registry` is null, compilation proceeds **without alias resolution**.
/// Field references that correspond to Azure resource provider aliases
/// (e.g. `Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly`) will
/// be compiled as raw property paths rather than being resolved to their
/// short forms. This means:
///
/// - Policies that rely on aliases will **silently produce incorrect
/// evaluation results** because the field paths won't match the
/// normalized resource structure.
/// - **Modify / Append** effect policies will **skip the modifiability
/// validation** that normally rejects writes to non-modifiable aliases
/// at compile time.
///
/// Pass null only when the policy is known to contain no alias references
/// (e.g. simple `type` / `location` checks, or in unit-test scenarios).
///
/// # Returns
/// Returns a `RegorusResult` containing a `RegorusProgram` pointer on success.
///
/// # Safety
/// `policy_rule_json` must be a valid null-terminated UTF-8 string.
/// If `registry` is non-null it must be a valid `RegorusAliasRegistry` pointer.
/// The caller must eventually call `regorus_program_drop` on the returned handle.
#[cfg(all(feature = "azure_policy", feature = "rvm"))]
#[no_mangle]
pub extern "C" fn regorus_compile_azure_policy_rule(
registry: *const crate::alias_registry::RegorusAliasRegistry,
policy_rule_json: *const c_char,
) -> RegorusResult {
use crate::alias_registry::RegorusAliasRegistry;
use crate::rvm::RegorusProgram;
use alloc::sync::Arc;
use regorus::languages::azure_policy::{compiler, parser};
use regorus::Rc;
use regorus::Source;
with_unwind_guard(|| {
let result = || -> Result<RegorusProgram, (RegorusStatus, alloc::string::String)> {
let json_str = from_c_str(policy_rule_json).map_err(|e| {
(
RegorusStatus::InvalidDataFormat,
format!("Invalid policy rule JSON string: {e}"),
)
})?;
let source = Source::from_contents("policy_rule".into(), json_str).map_err(|e| {
(
RegorusStatus::InvalidDataFormat,
format!("Failed to create source: {e}"),
)
})?;
let ast = parser::parse_policy_rule(&source).map_err(|e| {
(
RegorusStatus::InvalidPolicy,
format!("Failed to parse policy rule: {e}"),
)
})?;
let program = if registry.is_null() {
compiler::compile_policy_rule(&ast)
} else {
let reg: &RegorusAliasRegistry = to_shared_ref(registry).map_err(|e| {
(
RegorusStatus::InvalidArgument,
format!("Invalid alias registry: {e}"),
)
})?;
compiler::compile_policy_rule_with_aliases(&ast, reg.inner())
};
program
.map(|p| RegorusProgram {
program: Arc::new(Rc::try_unwrap(p).unwrap_or_else(|rc| (*rc).clone())),
})
.map_err(|e| {
(
RegorusStatus::CompilationFailed,
format!("Failed to compile policy rule: {e}"),
)
})
}();
match result {
Ok(program) => {
RegorusResult::ok_pointer(Box::into_raw(Box::new(program)) as *mut c_void)
}
Err((status, msg)) => RegorusResult::err_with_message(status, msg),
}
})
}
/// Compile a full Azure Policy definition JSON into an RVM program.
///
/// Parses the JSON policy definition (which includes `policyRule`, `parameters`,
/// `displayName`, etc.), resolves aliases using the provided registry, and
/// compiles the result into an RVM [`Program`].
///
/// The definition JSON may be in either wrapped or unwrapped form:
/// - **Wrapped**: `{ "properties": { "policyRule": ..., "parameters": ... }, "id": ... }`
/// - **Unwrapped**: `{ "policyRule": ..., "parameters": ..., "displayName": ... }`
///
/// # Parameters
/// * `registry` - Alias registry handle, or null.
/// * `policy_definition_json` - JSON string containing the full policy definition
///
/// # Null registry behavior
///
/// When `registry` is null, compilation proceeds **without alias resolution**.
/// Field references that correspond to Azure resource provider aliases will
/// be compiled as raw property paths rather than being resolved. This means:
///
/// - Policies that rely on aliases will **silently produce incorrect
/// evaluation results**.
/// - **Modify / Append** effect policies will **skip the modifiability
/// validation** that normally rejects writes to non-modifiable aliases
/// at compile time.
///
/// Pass null only when the policy is known to contain no alias references
/// (e.g. simple `type` / `location` checks, or in unit-test scenarios).
///
/// # Returns
/// Returns a `RegorusResult` containing a `RegorusProgram` pointer on success.
///
/// # Safety
/// `policy_definition_json` must be a valid null-terminated UTF-8 string.
/// If `registry` is non-null it must be a valid `RegorusAliasRegistry` pointer.
/// The caller must eventually call `regorus_program_drop` on the returned handle.
#[cfg(all(feature = "azure_policy", feature = "rvm"))]
#[no_mangle]
pub extern "C" fn regorus_compile_azure_policy_definition(
registry: *const crate::alias_registry::RegorusAliasRegistry,
policy_definition_json: *const c_char,
) -> RegorusResult {
use crate::alias_registry::RegorusAliasRegistry;
use crate::rvm::RegorusProgram;
use alloc::sync::Arc;
use regorus::languages::azure_policy::{compiler, parser};
use regorus::Rc;
use regorus::Source;
with_unwind_guard(|| {
let result = || -> Result<RegorusProgram, (RegorusStatus, alloc::string::String)> {
let json_str = from_c_str(policy_definition_json).map_err(|e| {
(
RegorusStatus::InvalidDataFormat,
format!("Invalid policy definition JSON string: {e}"),
)
})?;
let source =
Source::from_contents("policy_definition".into(), json_str).map_err(|e| {
(
RegorusStatus::InvalidDataFormat,
format!("Failed to create source: {e}"),
)
})?;
let defn = parser::parse_policy_definition(&source).map_err(|e| {
(
RegorusStatus::InvalidPolicy,
format!("Failed to parse policy definition: {e}"),
)
})?;
let program = if registry.is_null() {
compiler::compile_policy_definition(&defn)
} else {
let reg: &RegorusAliasRegistry = to_shared_ref(registry).map_err(|e| {
(
RegorusStatus::InvalidArgument,
format!("Invalid alias registry: {e}"),
)
})?;
compiler::compile_policy_definition_with_aliases(&defn, reg.inner())
};
program
.map(|p| RegorusProgram {
program: Arc::new(Rc::try_unwrap(p).unwrap_or_else(|rc| (*rc).clone())),
})
.map_err(|e| {
(
RegorusStatus::CompilationFailed,
format!("Failed to compile policy definition: {e}"),
)
})
}();
match result {
Ok(program) => {
RegorusResult::ok_pointer(Box::into_raw(Box::new(program)) as *mut c_void)
}
Err((status, msg)) => RegorusResult::err_with_message(status, msg),
}
})
}
#[cfg(feature = "std")]
fn report_module_error(index: usize, kind: &str, err: &anyhow::Error) {
eprintln!("Invalid {} at index {}: {}", kind, index, err);
@@ -215,3 +429,402 @@ fn report_module_error(index: usize, kind: &str, err: &anyhow::Error) {
#[cfg(not(feature = "std"))]
fn report_module_error(_index: usize, _kind: &str, _err: &anyhow::Error) {}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use crate::common::regorus_result_drop;
use core::ffi::CStr;
use std::ffi::CString;
fn c(s: &str) -> CString {
CString::new(s).expect("CString::new failed")
}
fn assert_ok_pointer(r: &RegorusResult) -> *mut c_void {
assert_eq!(
r.status,
RegorusStatus::Ok,
"expected Ok, got {:?}",
r.status
);
assert!(!r.pointer_value.is_null(), "expected non-null pointer");
r.pointer_value
}
#[cfg(all(feature = "azure_policy", feature = "rvm"))]
mod azure_policy_json {
use super::*;
use crate::alias_registry::regorus_alias_registry_drop;
use crate::rvm::{
regorus_program_drop, regorus_rvm_drop, regorus_rvm_execute_entry_point_by_name,
regorus_rvm_load_program, regorus_rvm_new, regorus_rvm_set_context,
regorus_rvm_set_input, RegorusProgram,
};
const ALIASES: &str = r#"[{
"namespace": "Microsoft.Storage",
"resourceTypes": [{
"resourceType": "storageAccounts",
"aliases": [{
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"defaultPath": "properties.supportsHttpsTrafficOnly",
"paths": []
}, {
"name": "Microsoft.Storage/storageAccounts/minimumTlsVersion",
"defaultPath": "properties.minimumTlsVersion",
"paths": []
}]
}]
}]"#;
const SIMPLE_POLICY_RULE: &str = r#"{
"if": {
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
"then": { "effect": "audit" }
}"#;
const ALIAS_POLICY_RULE: &str = r#"{
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
]
},
"then": { "effect": "deny" }
}"#;
const POLICY_DEFINITION: &str = r#"{
"displayName": "Require HTTPS for storage accounts",
"policyType": "Custom",
"mode": "Indexed",
"parameters": {
"effect": {
"type": "String",
"defaultValue": "deny"
}
},
"policyRule": {
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
{ "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly", "equals": false }
]
},
"then": { "effect": "[parameters('effect')]" }
}
}"#;
/// Wrap a normalized resource JSON into the input envelope expected by
/// the compiled Azure Policy RVM program.
fn wrap_input(resource_json: &str, parameters_json: &str) -> String {
format!(r#"{{"resource": {resource_json}, "parameters": {parameters_json}}}"#)
}
fn build_registry_with_json(
json: &str,
) -> *mut crate::alias_registry::RegorusAliasRegistry {
let builder = crate::alias_registry::regorus_alias_registry_builder_new();
let json_c = c(json);
let r = crate::alias_registry::regorus_alias_registry_builder_load_json(
builder,
json_c.as_ptr(),
);
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let r = crate::alias_registry::regorus_alias_registry_builder_build(builder);
let registry =
assert_ok_pointer(&r) as *mut crate::alias_registry::RegorusAliasRegistry;
regorus_result_drop(r);
crate::alias_registry::regorus_alias_registry_builder_drop(builder);
registry
}
/// Helper: compile a policy rule, execute it with input, and return the
/// result string.
unsafe fn compile_and_eval_rule(
registry: *const crate::alias_registry::RegorusAliasRegistry,
policy_rule: &str,
input_json: &str,
) -> String {
let rule_c = c(policy_rule);
let r = regorus_compile_azure_policy_rule(registry, rule_c.as_ptr());
let program_ptr = assert_ok_pointer(&r) as *mut RegorusProgram;
regorus_result_drop(r);
let vm = regorus_rvm_new();
assert!(!vm.is_null());
let r = regorus_rvm_load_program(vm, program_ptr);
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let input_c = c(input_json);
let r = regorus_rvm_set_input(vm, input_c.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let entry = c("main");
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok, "execute failed");
let output = CStr::from_ptr(r.output)
.to_str()
.expect("invalid UTF-8")
.to_string();
regorus_result_drop(r);
regorus_rvm_drop(vm);
regorus_program_drop(program_ptr);
output
}
#[test]
fn compile_simple_rule_no_aliases() {
let rule_c = c(SIMPLE_POLICY_RULE);
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), rule_c.as_ptr());
let ptr = assert_ok_pointer(&r);
regorus_result_drop(r);
regorus_program_drop(ptr as *mut RegorusProgram);
}
#[test]
fn compile_rule_with_aliases() {
let reg = build_registry_with_json(ALIASES);
let rule_c = c(ALIAS_POLICY_RULE);
let r = regorus_compile_azure_policy_rule(reg, rule_c.as_ptr());
let ptr = assert_ok_pointer(&r);
regorus_result_drop(r);
regorus_program_drop(ptr as *mut RegorusProgram);
regorus_alias_registry_drop(reg);
}
#[test]
fn compile_and_eval_simple_rule_matching() {
let input = wrap_input(r#"{"type":"microsoft.storage/storageaccounts"}"#, "{}");
let result =
unsafe { compile_and_eval_rule(core::ptr::null_mut(), SIMPLE_POLICY_RULE, &input) };
let parsed: serde_json::Value =
serde_json::from_str(&result).expect("result should be valid JSON");
assert_eq!(
parsed["effect"], "audit",
"expected audit effect, got: {result}"
);
}
#[test]
fn compile_and_eval_simple_rule_not_matching() {
let input = wrap_input(r#"{"type":"microsoft.compute/virtualmachines"}"#, "{}");
let result =
unsafe { compile_and_eval_rule(core::ptr::null_mut(), SIMPLE_POLICY_RULE, &input) };
// When the "if" condition doesn't match, the result should be undefined
assert!(
result.contains("undefined"),
"expected undefined for non-matching input, got: {result}"
);
}
#[test]
fn compile_and_eval_alias_rule_deny() {
let reg = build_registry_with_json(ALIASES);
// Non-compliant resource: HTTPS not enabled (normalized form)
let input = wrap_input(
r#"{"type": "microsoft.storage/storageaccounts", "supportshttpstrafficonly": false}"#,
"{}",
);
let result = unsafe { compile_and_eval_rule(reg, ALIAS_POLICY_RULE, &input) };
let parsed: serde_json::Value = serde_json::from_str(&result).expect("valid JSON");
assert_eq!(parsed["effect"], "deny", "expected deny, got: {result}");
regorus_alias_registry_drop(reg);
}
#[test]
fn compile_and_eval_alias_rule_compliant() {
let reg = build_registry_with_json(ALIASES);
// Compliant resource: HTTPS enabled (normalized form)
let input = wrap_input(
r#"{"type": "microsoft.storage/storageaccounts", "supportshttpstrafficonly": true}"#,
"{}",
);
let result = unsafe { compile_and_eval_rule(reg, ALIAS_POLICY_RULE, &input) };
assert!(
result.contains("undefined"),
"expected undefined for compliant resource, got: {result}"
);
regorus_alias_registry_drop(reg);
}
#[test]
fn compile_definition_no_aliases() {
let defn_c = c(POLICY_DEFINITION);
let r = regorus_compile_azure_policy_definition(core::ptr::null_mut(), defn_c.as_ptr());
let ptr = assert_ok_pointer(&r);
regorus_result_drop(r);
regorus_program_drop(ptr as *mut RegorusProgram);
}
#[test]
fn compile_definition_with_aliases_and_eval() {
let reg = build_registry_with_json(ALIASES);
let defn_c = c(POLICY_DEFINITION);
let r = regorus_compile_azure_policy_definition(reg, defn_c.as_ptr());
let program_ptr = assert_ok_pointer(&r) as *mut RegorusProgram;
regorus_result_drop(r);
// Evaluate with a non-compliant resource (normalized form, wrapped in envelope)
unsafe {
let vm = regorus_rvm_new();
let r = regorus_rvm_load_program(vm, program_ptr);
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let input_json = wrap_input(
r#"{"type": "microsoft.storage/storageaccounts", "supportshttpstrafficonly": false}"#,
"{}",
);
let input = c(&input_json);
let r = regorus_rvm_set_input(vm, input.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let entry = c("main");
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
let result = CStr::from_ptr(r.output)
.to_str()
.expect("UTF-8")
.to_string();
regorus_result_drop(r);
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
// The default parameter value is "deny"
assert_eq!(parsed["effect"], "deny", "got: {result}");
regorus_rvm_drop(vm);
regorus_program_drop(program_ptr);
}
regorus_alias_registry_drop(reg);
}
#[test]
fn invalid_json_returns_error() {
let bad = c("not valid json");
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), bad.as_ptr());
assert_ne!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
}
#[test]
fn invalid_definition_returns_error() {
let bad = c(r#"{"not": "a policy definition"}"#);
let r = regorus_compile_azure_policy_definition(core::ptr::null_mut(), bad.as_ptr());
assert_ne!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
}
/// Policy rule that uses a context function (subscription()).
const CONTEXT_POLICY_RULE: &str = r#"{
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
{ "value": "[subscription().subscriptionId]", "equals": "sub-123" }
]
},
"then": { "effect": "deny" }
}"#;
#[test]
fn context_policy_evaluates_with_set_context() {
let rule_c = c(CONTEXT_POLICY_RULE);
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), rule_c.as_ptr());
let program = assert_ok_pointer(&r) as *mut RegorusProgram;
regorus_result_drop(r);
let vm = regorus_rvm_new();
assert!(!vm.is_null());
let r = regorus_rvm_load_program(vm, program);
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
// Set the context with subscription info
let context = c(r#"{"subscription": {"subscriptionId": "sub-123"}}"#);
let r = regorus_rvm_set_context(vm, context.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
// Set matching input
let input = c(&wrap_input(
r#"{"type": "microsoft.storage/storageaccounts"}"#,
"{}",
));
let r = regorus_rvm_set_input(vm, input.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let entry = c("main");
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
let output = unsafe { CStr::from_ptr(r.output) }.to_str().unwrap();
assert!(
output.contains("deny"),
"expected deny effect with matching context, got: {output}"
);
regorus_result_drop(r);
regorus_rvm_drop(vm);
regorus_program_drop(program);
}
#[test]
fn context_policy_undefined_without_context() {
let rule_c = c(CONTEXT_POLICY_RULE);
let r = regorus_compile_azure_policy_rule(core::ptr::null_mut(), rule_c.as_ptr());
let program = assert_ok_pointer(&r) as *mut RegorusProgram;
regorus_result_drop(r);
let vm = regorus_rvm_new();
assert!(!vm.is_null());
let r = regorus_rvm_load_program(vm, program);
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
// No context set — subscription() will be undefined
let input = c(&wrap_input(
r#"{"type": "microsoft.storage/storageaccounts"}"#,
"{}",
));
let r = regorus_rvm_set_input(vm, input.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let entry = c("main");
let r = regorus_rvm_execute_entry_point_by_name(vm, entry.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
let output = unsafe { CStr::from_ptr(r.output) }.to_str().unwrap();
assert!(
output.contains("undefined"),
"expected undefined without context, got: {output}"
);
regorus_result_drop(r);
regorus_rvm_drop(vm);
regorus_program_drop(program);
}
}
}

View File

@@ -39,7 +39,7 @@ pub extern "C" fn regorus_compiled_policy_eval_with_input(
with_unwind_guard(|| {
let output = || -> Result<String> {
let input_value = regorus::Value::from_json_str(&from_c_str(input)?)?;
let result = to_ref(compiled_policy)?
let result = to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?
.compiled_policy
.eval_with_input(input_value)?;
result.to_json_str()
@@ -65,7 +65,9 @@ pub extern "C" fn regorus_compiled_policy_get_policy_info(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let info = to_ref(compiled_policy)?.compiled_policy.get_policy_info()?;
let info = to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?
.compiled_policy
.get_policy_info()?;
serde_json::to_string(&info)
.map_err(|e| anyhow::anyhow!("Failed to serialize policy info: {}", e))
}();

View File

@@ -2,7 +2,8 @@
// Licensed under the MIT License.
use crate::common::{
from_c_str, to_ref, to_regorus_result, to_regorus_string_result, RegorusResult, RegorusStatus,
from_c_str, to_ref, to_regorus_result, to_regorus_string_result, to_shared_ref, RegorusResult,
RegorusStatus,
};
use crate::compiled_policy::RegorusCompiledPolicy;
use crate::limits::RegorusExecutionTimerConfig;
@@ -193,7 +194,7 @@ pub extern "C" fn regorus_engine_new() -> *mut RegorusEngine {
///
#[no_mangle]
pub extern "C" fn regorus_engine_clone(engine: *mut RegorusEngine) -> *mut RegorusEngine {
match to_ref(engine) {
match to_shared_ref(engine as *const RegorusEngine) {
Ok(e) => Box::into_raw(Box::new(e.clone())),
_ => ptr::null_mut(),
}
@@ -223,7 +224,7 @@ pub extern "C" fn regorus_engine_add_policy(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_string_result(|| -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.add_policy(from_c_str(path)?, from_c_str(rego)?)
}())
@@ -238,7 +239,7 @@ pub extern "C" fn regorus_engine_add_policy_from_file(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_string_result(|| -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.add_policy_from_file(from_c_str(path)?)
}())
@@ -256,7 +257,7 @@ pub extern "C" fn regorus_engine_add_data_json(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
}())
@@ -270,7 +271,7 @@ pub extern "C" fn regorus_engine_add_data_json(
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_string_result(|| -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let guard = engine.try_read()?;
serde_json::to_string_pretty(&guard.get_packages()?).map_err(anyhow::Error::msg)
}())
@@ -284,7 +285,7 @@ pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> Reg
pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_string_result(|| -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let guard = engine.try_read()?;
guard.get_policies_as_json()
}())
@@ -299,7 +300,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.add_data(regorus::Value::from_json_file(from_c_str(path)?)?)
}())
@@ -313,7 +314,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.clear_data();
Ok(())
@@ -332,7 +333,7 @@ pub extern "C" fn regorus_engine_set_input_json(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
Ok(())
@@ -348,7 +349,7 @@ pub extern "C" fn regorus_engine_set_input_from_json_file(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.set_input(regorus::Value::from_json_file(from_c_str(path)?)?);
Ok(())
@@ -367,7 +368,7 @@ pub extern "C" fn regorus_engine_eval_query(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
let results = guard.eval_query(from_c_str(query)?, false)?;
Ok(serde_json::to_string_pretty(&results)?)
@@ -390,7 +391,7 @@ pub extern "C" fn regorus_engine_eval_rule(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.eval_rule(from_c_str(rule)?)?.to_json_str()
}();
@@ -413,7 +414,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.set_enable_coverage(enable);
Ok(())
@@ -429,7 +430,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let guard = engine.try_read()?;
Ok(serde_json::to_string_pretty(&guard.get_coverage_report()?)?)
}();
@@ -451,7 +452,7 @@ pub extern "C" fn regorus_engine_set_strict_builtin_errors(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.set_strict_builtin_errors(strict);
Ok(())
@@ -465,18 +466,20 @@ pub extern "C" fn regorus_engine_set_execution_timer_config(
engine: *mut RegorusEngine,
config: *const RegorusExecutionTimerConfig,
) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let config = unsafe {
config
.as_ref()
.copied()
.ok_or_else(|| anyhow!("execution timer config pointer is null"))?
};
let mut guard = engine.try_write()?;
guard.set_execution_timer_config(config.to_execution_timer_config()?);
Ok(())
}())
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let config = unsafe {
config
.as_ref()
.copied()
.ok_or_else(|| anyhow!("execution timer config pointer is null"))?
};
let mut guard = engine.try_write()?;
guard.set_execution_timer_config(config.to_execution_timer_config()?);
Ok(())
}())
})
}
#[no_mangle]
@@ -484,12 +487,14 @@ pub extern "C" fn regorus_engine_set_execution_timer_config(
pub extern "C" fn regorus_engine_clear_execution_timer_config(
engine: *mut RegorusEngine,
) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let mut guard = engine.try_write()?;
guard.clear_execution_timer_config();
Ok(())
}())
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.clear_execution_timer_config();
Ok(())
}())
})
}
/// Set the policy length limits used when loading policies.
@@ -500,7 +505,7 @@ pub extern "C" fn regorus_engine_set_policy_length_config(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.set_policy_length_config(config.to_policy_length_config()?);
Ok(())
@@ -515,7 +520,7 @@ pub extern "C" fn regorus_engine_clear_policy_length_config(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.clear_policy_length_config();
Ok(())
@@ -533,7 +538,7 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let guard = engine.try_read()?;
guard.get_coverage_report()?.to_string_pretty()
}();
@@ -552,7 +557,7 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.clear_coverage_data();
Ok(())
@@ -571,7 +576,7 @@ pub extern "C" fn regorus_engine_set_gather_prints(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.set_gather_prints(enable);
Ok(())
@@ -586,7 +591,7 @@ pub extern "C" fn regorus_engine_set_gather_prints(
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
Ok(serde_json::to_string_pretty(&guard.take_prints()?)?)
}();
@@ -605,7 +610,7 @@ pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> Rego
pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let guard = engine.try_read()?;
guard.get_ast_as_json()
}();
@@ -626,7 +631,7 @@ pub extern "C" fn regorus_engine_get_policy_package_names(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let guard = engine.try_read()?;
serde_json::to_string_pretty(&guard.get_policy_package_names()?)
.map_err(anyhow::Error::msg)
@@ -648,7 +653,7 @@ pub extern "C" fn regorus_engine_get_policy_parameters(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let guard = engine.try_read()?;
serde_json::to_string_pretty(&guard.get_policy_parameters()?)
.map_err(anyhow::Error::msg)
@@ -670,7 +675,7 @@ pub extern "C" fn regorus_engine_set_rego_v0(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<()> {
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
guard.set_rego_v0(enable);
Ok(())
@@ -692,7 +697,7 @@ pub extern "C" fn regorus_engine_set_rego_v0(
#[cfg(feature = "azure_policy")]
pub extern "C" fn regorus_engine_compile_for_target(engine: *mut RegorusEngine) -> RegorusResult {
with_unwind_guard(|| {
let engine = match to_ref(engine) {
let engine = match to_shared_ref(engine as *const RegorusEngine) {
Ok(engine) => engine,
Err(e) => {
return RegorusResult::err_with_message(
@@ -741,7 +746,7 @@ pub extern "C" fn regorus_engine_compile_with_entrypoint(
let result = || -> Result<RegorusCompiledPolicy> {
let rule_str = from_c_str(rule)?;
let rule_rc: regorus::Rc<str> = rule_str.into();
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;
Ok(RegorusCompiledPolicy { compiled_policy })
@@ -800,7 +805,7 @@ pub extern "C" fn regorus_engine_compile_program_with_entrypoints(
.ok_or_else(|| anyhow!("entry_points must contain at least one entry"))?;
let rule_rc: regorus::Rc<str> = (*rule).into();
let engine = to_ref(engine)?;
let engine = to_shared_ref(engine as *const RegorusEngine)?;
let mut guard = engine.try_write()?;
let compiled_policy = guard.compile_with_entrypoint(&rule_rc)?;

View File

@@ -2,7 +2,8 @@
// Licensed under the MIT License.
use crate::common::{
from_c_str, to_ref, to_regorus_result, RegorusBuffer, RegorusResult, RegorusStatus,
from_c_str, to_ref, to_regorus_result, to_shared_ref, RegorusBuffer, RegorusResult,
RegorusStatus,
};
use crate::compile::RegorusPolicyModule;
use crate::compiled_policy::RegorusCompiledPolicy;
@@ -106,7 +107,8 @@ pub extern "C" fn regorus_program_compile_from_policy(
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
let compiled_policy = &to_ref(compiled_policy)?.compiled_policy;
let compiled_policy =
&to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?.compiled_policy;
let program = Compiler::compile_from_policy(compiled_policy, &entry_points_ref)?;
Ok(Box::into_raw(Box::new(RegorusProgram { program })))
}();
@@ -187,7 +189,7 @@ pub extern "C" fn regorus_program_new() -> *mut RegorusProgram {
pub extern "C" fn regorus_program_serialize_binary(program: *mut RegorusProgram) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<*mut RegorusBuffer> {
let program = &to_ref(program)?.program;
let program = &to_shared_ref(program as *const RegorusProgram)?.program;
let bytes = program.serialize_binary().map_err(|e| anyhow!(e))?;
Ok(RegorusBuffer::from_vec(bytes))
}();
@@ -211,7 +213,10 @@ pub extern "C" fn regorus_program_deserialize_binary(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<(*mut RegorusProgram, bool)> {
if data.is_null() && len > 0 {
if data.is_null() {
if len > 0 {
return Err(anyhow!("null data pointer with non-zero length"));
}
return Err(anyhow!("null data pointer"));
}
let data = unsafe { core::slice::from_raw_parts(data, len) };
@@ -249,7 +254,7 @@ pub extern "C" fn regorus_program_deserialize_binary(
pub extern "C" fn regorus_program_generate_listing(program: *mut RegorusProgram) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let program = &to_ref(program)?.program;
let program = &to_shared_ref(program as *const RegorusProgram)?.program;
Ok(generate_assembly_listing(
program,
&AssemblyListingConfig::default(),
@@ -270,7 +275,7 @@ pub extern "C" fn regorus_program_generate_tabular_listing(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let program = &to_ref(program)?.program;
let program = &to_shared_ref(program as *const RegorusProgram)?.program;
Ok(generate_tabular_assembly_listing(
program,
&AssemblyListingConfig::default(),
@@ -297,7 +302,9 @@ pub extern "C" fn regorus_rvm_new_with_policy(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<*mut RegorusRvm> {
let policy = to_ref(compiled_policy)?.compiled_policy.clone();
let policy = to_shared_ref(compiled_policy as *const RegorusCompiledPolicy)?
.compiled_policy
.clone();
Ok(Box::into_raw(Box::new(RegorusRvm::new(
RegoVM::new_with_policy(policy),
))))
@@ -318,9 +325,11 @@ pub extern "C" fn regorus_rvm_load_program(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
let program = to_ref(program)?.program.clone();
let program = to_shared_ref(program as *const RegorusProgram)?
.program
.clone();
guard.load_program(program);
Ok(())
}())
@@ -332,7 +341,7 @@ pub extern "C" fn regorus_rvm_load_program(
pub extern "C" fn regorus_rvm_set_data(vm: *mut RegorusRvm, data: *const c_char) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
let data_value = Value::from_json_str(&from_c_str(data)?)?;
guard.set_data(data_value)?;
@@ -349,7 +358,7 @@ pub extern "C" fn regorus_rvm_set_input(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
let input_value = Value::from_json_str(&from_c_str(input)?)?;
guard.set_input(input_value);
@@ -358,6 +367,33 @@ pub extern "C" fn regorus_rvm_set_input(
})
}
/// Set the VM context document from JSON.
///
/// The context provides host-supplied ambient data (e.g. `resourceGroup()`,
/// `subscription()`) that Azure Policy functions can access via `LoadContext`
/// instructions. This must be called before `regorus_rvm_execute` when
/// evaluating policies that reference context functions.
///
/// # Safety
/// - `vm` must be a valid pointer to a `RegorusRvm` created by `regorus_rvm_new`.
/// - `context_json` must be a valid null-terminated UTF-8 string.
#[cfg(feature = "azure_policy")]
#[no_mangle]
pub extern "C" fn regorus_rvm_set_context(
vm: *mut RegorusRvm,
context_json: *const c_char,
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
let context_value = Value::from_json_str(&from_c_str(context_json)?)?;
guard.set_context(context_value);
Ok(())
}())
})
}
/// Set the maximum number of instructions that can execute.
#[no_mangle]
pub extern "C" fn regorus_rvm_set_max_instructions(
@@ -366,7 +402,7 @@ pub extern "C" fn regorus_rvm_set_max_instructions(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
guard.set_max_instructions(max_instructions);
Ok(())
@@ -382,7 +418,7 @@ pub extern "C" fn regorus_rvm_set_strict_builtin_errors(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
guard.set_strict_builtin_errors(strict);
Ok(())
@@ -395,7 +431,7 @@ pub extern "C" fn regorus_rvm_set_strict_builtin_errors(
pub extern "C" fn regorus_rvm_set_execution_mode(vm: *mut RegorusRvm, mode: u8) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
let mode = match mode {
0 => ExecutionMode::RunToCompletion,
@@ -413,7 +449,7 @@ pub extern "C" fn regorus_rvm_set_execution_mode(vm: *mut RegorusRvm, mode: u8)
pub extern "C" fn regorus_rvm_set_step_mode(vm: *mut RegorusRvm, enabled: bool) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
guard.set_step_mode(enabled);
Ok(())
@@ -430,7 +466,7 @@ pub extern "C" fn regorus_rvm_set_execution_timer_config(
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
if has_config {
guard.set_execution_timer_config(Some(config.to_execution_timer_config()?));
@@ -447,7 +483,7 @@ pub extern "C" fn regorus_rvm_set_execution_timer_config(
pub extern "C" fn regorus_rvm_execute(vm: *mut RegorusRvm) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
let result = guard.execute()?;
result.to_json_str()
@@ -468,7 +504,7 @@ pub extern "C" fn regorus_rvm_execute_entry_point_by_name(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
let name = from_c_str(entry_point)?;
let result = guard.execute_entry_point_by_name(&name)?;
@@ -490,7 +526,7 @@ pub extern "C" fn regorus_rvm_execute_entry_point_by_index(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
let result = guard.execute_entry_point_by_index(index)?;
result.to_json_str()
@@ -512,7 +548,7 @@ pub extern "C" fn regorus_rvm_resume(
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let mut guard = vm.try_write()?;
let value = if has_value {
Some(Value::from_json_str(&from_c_str(resume_value_json)?)?)
@@ -535,7 +571,7 @@ pub extern "C" fn regorus_rvm_resume(
pub extern "C" fn regorus_rvm_get_execution_state(vm: *mut RegorusRvm) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let vm = to_ref(vm)?;
let vm = to_shared_ref(vm as *const RegorusRvm)?;
let guard = vm.try_read()?;
let state: ExecutionState = guard.execution_state().clone();
Ok(format!("{:?}", state))

429
bindings/java/Cargo.lock generated
View File

@@ -42,15 +42,15 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.102"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "autocfg"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bit-set"
@@ -69,9 +69,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.11.1"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "borrow-or-share"
@@ -81,19 +81,19 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
version = "1.12.1"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
dependencies = [
"memchr",
"serde",
"serde_core",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytecount"
@@ -103,15 +103,15 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "bytes"
version = "1.11.1"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cc"
version = "1.2.61"
version = "1.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -125,9 +125,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -136,9 +136,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.44"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -199,9 +199,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]]
name = "displaydoc"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
@@ -237,9 +237,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fancy-regex"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
dependencies = [
"bit-set",
"regex-automata",
@@ -263,12 +263,6 @@ dependencies = [
"serde",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
@@ -334,23 +328,21 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
"rand_core",
"wasip2",
"wasip3",
]
[[package]]
name = "globset"
version = "0.4.18"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
dependencies = [
"aho-corasick",
"bstr",
@@ -360,36 +352,15 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "iana-time-zone"
version = "0.1.65"
@@ -496,12 +467,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
@@ -530,7 +495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
"hashbrown",
"serde",
"serde_core",
]
@@ -598,21 +563,20 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.97"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "jsonschema"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
checksum = "281c43ff06dcb331e9356d30e38853d559ce3d0a3f693e0b0e102667dec14fb1"
dependencies = [
"ahash",
"bytecount",
@@ -623,30 +587,33 @@ dependencies = [
"getrandom 0.3.4",
"idna",
"itoa",
"jsonschema-regex",
"num-cmp",
"num-traits",
"percent-encoding",
"referencing",
"regex",
"regex-syntax",
"serde",
"serde_json",
"unicode-general-category",
"uuid-simd",
]
[[package]]
name = "jsonschema-regex"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee0b351864e7ffbc5db9273daf7fa1b4d5177b0946713d667ca571b83c0b4045"
dependencies = [
"regex-syntax",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
@@ -670,21 +637,27 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.29"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "lru"
version = "0.16.4"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6"
[[package]]
name = "memchr"
version = "2.8.0"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "micromap"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
[[package]]
name = "msvc_spectre_libs"
@@ -701,7 +674,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-complex",
"num-integer",
"num-iter",
@@ -711,9 +684,19 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.6"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
dependencies = [
"num-integer",
"num-traits",
@@ -745,11 +728,10 @@ dependencies = [
[[package]]
name = "num-iter"
version = "0.1.45"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
@@ -760,7 +742,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-integer",
"num-traits",
]
@@ -860,16 +842,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -881,9 +853,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
@@ -902,12 +874,12 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
"chacha20",
"getrandom 0.4.2",
"getrandom 0.4.3",
"rand_core",
]
@@ -948,14 +920,16 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
checksum = "348e860aeb0b7bd035778fd11dd9cd5290d32e4aed3b8f2274a00287a9fd362b"
dependencies = [
"ahash",
"fluent-uri",
"getrandom 0.3.4",
"hashbrown 0.16.1",
"hashbrown",
"itoa",
"micromap",
"parking_lot",
"percent-encoding",
"serde_json",
@@ -963,9 +937,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.3"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
@@ -975,9 +949,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.14"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [
"aho-corasick",
"memchr",
@@ -986,13 +960,13 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "regorus"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"anyhow",
"chrono",
@@ -1005,7 +979,7 @@ dependencies = [
"lazy_static",
"lru",
"msvc_spectre_libs",
"num-bigint",
"num-bigint 0.5.1",
"num-traits",
"parking_lot",
"postcard",
@@ -1024,7 +998,7 @@ dependencies = [
[[package]]
name = "regorus-java"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"anyhow",
"jni",
@@ -1034,7 +1008,7 @@ dependencies = [
[[package]]
name = "regorus-mimalloc"
version = "2.2.6"
version = "2.2.7"
dependencies = [
"regorus-mimalloc-sys",
]
@@ -1057,9 +1031,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ryu"
@@ -1120,9 +1094,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -1146,15 +1120,15 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.3.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "simd_cesu8"
version = "1.1.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
dependencies = [
"rustc_version",
"simdutf8",
@@ -1168,9 +1142,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "siphasher"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "slab"
@@ -1180,15 +1154,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "spin"
version = "0.10.0"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
checksum = "8abadc99fd9c7bbb7d0ca2b31d72a067d0c0dcd7aad25ab8cac71ba91417694b"
[[package]]
name = "stable_deref_trait"
@@ -1198,9 +1172,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
@@ -1260,12 +1234,6 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
@@ -1292,11 +1260,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.23.1"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
dependencies = [
"getrandom 0.4.2",
"getrandom 0.4.3",
"rand",
]
@@ -1334,27 +1302,18 @@ dependencies = [
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [
"cfg-if",
"once_cell",
@@ -1365,9 +1324,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1375,9 +1334,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1388,47 +1347,13 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
@@ -1506,100 +1431,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.3"
@@ -1608,9 +1445,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yoke"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -1631,18 +1468,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
dependencies = [
"proc-macro2",
"quote",
@@ -1651,9 +1488,9 @@ dependencies = [
[[package]]
name = "zerofrom"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
@@ -1705,6 +1542,6 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

View File

@@ -2,7 +2,7 @@
[package]
name = "regorus-java"
version = "0.10.0"
version = "0.11.0"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/java"
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -21,6 +21,6 @@ cache = ["regorus/cache"]
[dependencies]
anyhow = "1.0"
serde_json = "1.0.112"
serde_json = "1.0.150"
jni = "0.22.4"
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }

View File

@@ -9,7 +9,7 @@
<groupId>com.microsoft.regorus</groupId>
<artifactId>regorus-java</artifactId>
<version>0.10.0</version>
<version>0.11.0</version>
<name>Regorus Java</name>
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>
@@ -97,7 +97,7 @@
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.5</version>
<version>3.5.6</version>
<configuration>
<!-- 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>

View File

@@ -462,7 +462,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromModul
}
let mut modules = Vec::with_capacity(ids.len());
for (id, content) in ids.into_iter().zip(contents.into_iter()) {
for (id, content) in ids.into_iter().zip(contents) {
modules.push(PolicyModule {
id: Rc::from(id.as_str()),
content: Rc::from(content.as_str()),

View File

@@ -42,15 +42,15 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.102"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "autocfg"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bit-set"
@@ -69,9 +69,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.11.1"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "borrow-or-share"
@@ -81,19 +81,19 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
version = "1.12.1"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
dependencies = [
"memchr",
"serde",
"serde_core",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytecount"
@@ -103,9 +103,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.61"
version = "1.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -119,9 +119,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -130,9 +130,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.44"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -183,9 +183,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]]
name = "displaydoc"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
@@ -221,9 +221,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fancy-regex"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
dependencies = [
"bit-set",
"regex-automata",
@@ -247,12 +247,6 @@ dependencies = [
"serde",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
@@ -318,23 +312,21 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
"rand_core",
"wasip2",
"wasip3",
]
[[package]]
name = "globset"
version = "0.4.18"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
dependencies = [
"aho-corasick",
"bstr",
@@ -344,30 +336,15 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "heck"
version = "0.5.0"
@@ -480,12 +457,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
@@ -514,7 +485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
"hashbrown",
"serde",
"serde_core",
]
@@ -533,21 +504,20 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.97"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "jsonschema"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
checksum = "281c43ff06dcb331e9356d30e38853d559ce3d0a3f693e0b0e102667dec14fb1"
dependencies = [
"ahash",
"bytecount",
@@ -558,30 +528,33 @@ dependencies = [
"getrandom 0.3.4",
"idna",
"itoa",
"jsonschema-regex",
"num-cmp",
"num-traits",
"percent-encoding",
"referencing",
"regex",
"regex-syntax",
"serde",
"serde_json",
"unicode-general-category",
"uuid-simd",
]
[[package]]
name = "jsonschema-regex"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee0b351864e7ffbc5db9273daf7fa1b4d5177b0946713d667ca571b83c0b4045"
dependencies = [
"regex-syntax",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
@@ -605,21 +578,27 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.29"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "lru"
version = "0.16.4"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6"
[[package]]
name = "memchr"
version = "2.8.0"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "micromap"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
[[package]]
name = "msvc_spectre_libs"
@@ -636,7 +615,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-complex",
"num-integer",
"num-iter",
@@ -646,9 +625,19 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.6"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
dependencies = [
"num-integer",
"num-traits",
@@ -680,11 +669,10 @@ dependencies = [
[[package]]
name = "num-iter"
version = "0.1.45"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
@@ -695,7 +683,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-integer",
"num-traits",
]
@@ -810,16 +798,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -831,9 +809,9 @@ dependencies = [
[[package]]
name = "pyo3"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c"
dependencies = [
"anyhow",
"libc",
@@ -846,18 +824,18 @@ dependencies = [
[[package]]
name = "pyo3-build-config"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
dependencies = [
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
dependencies = [
"libc",
"pyo3-build-config",
@@ -865,9 +843,9 @@ dependencies = [
[[package]]
name = "pyo3-macros"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
@@ -877,22 +855,21 @@ dependencies = [
[[package]]
name = "pyo3-macros-backend"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362"
dependencies = [
"heck",
"proc-macro2",
"pyo3-build-config",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
@@ -911,12 +888,12 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
"chacha20",
"getrandom 0.4.2",
"getrandom 0.4.3",
"rand_core",
]
@@ -957,14 +934,16 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
checksum = "348e860aeb0b7bd035778fd11dd9cd5290d32e4aed3b8f2274a00287a9fd362b"
dependencies = [
"ahash",
"fluent-uri",
"getrandom 0.3.4",
"hashbrown 0.16.1",
"hashbrown",
"itoa",
"micromap",
"parking_lot",
"percent-encoding",
"serde_json",
@@ -972,9 +951,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.3"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
@@ -984,9 +963,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.14"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [
"aho-corasick",
"memchr",
@@ -995,13 +974,13 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "regorus"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"anyhow",
"chrono",
@@ -1014,7 +993,7 @@ dependencies = [
"lazy_static",
"lru",
"msvc_spectre_libs",
"num-bigint",
"num-bigint 0.5.1",
"num-traits",
"parking_lot",
"postcard",
@@ -1033,7 +1012,7 @@ dependencies = [
[[package]]
name = "regorus-mimalloc"
version = "2.2.6"
version = "2.2.7"
dependencies = [
"regorus-mimalloc-sys",
]
@@ -1047,7 +1026,7 @@ dependencies = [
[[package]]
name = "regoruspy"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"anyhow",
"ordered-float",
@@ -1058,9 +1037,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ryu"
@@ -1112,9 +1091,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -1138,15 +1117,15 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.3.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "siphasher"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "slab"
@@ -1156,15 +1135,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "spin"
version = "0.10.0"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
checksum = "8abadc99fd9c7bbb7d0ca2b31d72a067d0c0dcd7aad25ab8cac71ba91417694b"
[[package]]
name = "stable_deref_trait"
@@ -1174,9 +1153,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
@@ -1242,12 +1221,6 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
@@ -1274,11 +1247,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.23.1"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
dependencies = [
"getrandom 0.4.2",
"getrandom 0.4.3",
"rand",
]
@@ -1306,27 +1279,18 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [
"cfg-if",
"once_cell",
@@ -1337,9 +1301,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1347,9 +1311,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1360,47 +1324,13 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "windows-core"
version = "0.62.2"
@@ -1460,100 +1390,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.3"
@@ -1562,9 +1404,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yoke"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -1585,18 +1427,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
dependencies = [
"proc-macro2",
"quote",
@@ -1605,9 +1447,9 @@ dependencies = [
[[package]]
name = "zerofrom"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
@@ -1659,6 +1501,6 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

View File

@@ -2,7 +2,7 @@
[package]
name = "regoruspy"
version = "0.10.0"
version = "0.11.0"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/python"
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -23,7 +23,7 @@ coverage = ["regorus/coverage"]
[dependencies]
anyhow = "1.0"
ordered-float = "5.3.0"
pyo3 = { version = "0.28.3", features = ["abi3-py310", "anyhow", "extension-module"] }
pyo3 = { version = "0.29.0", features = ["abi3-py310", "anyhow", "extension-module"] }
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
serde_json = "1.0.140"
serde_json = "1.0.150"

View File

@@ -1,5 +1,5 @@
[build-system]
requires = ["maturin>=1.4,<2.0"]
requires = ["maturin>=1.14.1,<2.0"]
build-backend = "maturin"
[project]

445
bindings/ruby/Cargo.lock generated
View File

@@ -42,15 +42,15 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.102"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "autocfg"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bindgen"
@@ -66,7 +66,7 @@ dependencies = [
"quote",
"regex",
"rustc-hash",
"shlex",
"shlex 1.3.0",
"syn",
]
@@ -87,9 +87,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.11.1"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "borrow-or-share"
@@ -99,19 +99,19 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
version = "1.12.1"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
dependencies = [
"memchr",
"serde",
"serde_core",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytecount"
@@ -121,12 +121,12 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.61"
version = "1.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
dependencies = [
"find-msvc-tools",
"shlex",
"shlex 2.0.1",
]
[[package]]
@@ -146,9 +146,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -157,9 +157,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.44"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -212,9 +212,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]]
name = "displaydoc"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
@@ -223,9 +223,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.15.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
[[package]]
name = "email_address"
@@ -244,9 +244,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fancy-regex"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
dependencies = [
"bit-set",
"regex-automata",
@@ -270,12 +270,6 @@ dependencies = [
"serde",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
@@ -341,16 +335,14 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
"rand_core",
"wasip2",
"wasip3",
]
[[package]]
@@ -361,9 +353,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "globset"
version = "0.4.18"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
dependencies = [
"aho-corasick",
"bstr",
@@ -373,36 +365,15 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "iana-time-zone"
version = "0.1.65"
@@ -509,12 +480,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
@@ -543,7 +508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
"hashbrown",
"serde",
"serde_core",
]
@@ -571,21 +536,20 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.97"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "jsonschema"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
checksum = "281c43ff06dcb331e9356d30e38853d559ce3d0a3f693e0b0e102667dec14fb1"
dependencies = [
"ahash",
"bytecount",
@@ -596,30 +560,33 @@ dependencies = [
"getrandom 0.3.4",
"idna",
"itoa",
"jsonschema-regex",
"num-cmp",
"num-traits",
"percent-encoding",
"referencing",
"regex",
"regex-syntax",
"serde",
"serde_json",
"unicode-general-category",
"uuid-simd",
]
[[package]]
name = "jsonschema-regex"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee0b351864e7ffbc5db9273daf7fa1b4d5177b0946713d667ca571b83c0b4045"
dependencies = [
"regex-syntax",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
@@ -653,15 +620,15 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.29"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "lru"
version = "0.16.4"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6"
[[package]]
name = "magnus"
@@ -688,9 +655,15 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.8.0"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "micromap"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
[[package]]
name = "minimal-lexical"
@@ -723,7 +696,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-complex",
"num-integer",
"num-iter",
@@ -733,9 +706,19 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.6"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
dependencies = [
"num-integer",
"num-traits",
@@ -767,11 +750,10 @@ dependencies = [
[[package]]
name = "num-iter"
version = "0.1.45"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
@@ -782,7 +764,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-integer",
"num-traits",
]
@@ -870,16 +852,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -891,9 +863,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
@@ -912,12 +884,12 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
"chacha20",
"getrandom 0.4.2",
"getrandom 0.4.3",
"rand_core",
]
@@ -929,18 +901,18 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "rb-sys"
version = "0.9.127"
version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d7c9560fe42dcffa576941394075f18a17dce89fcf718a2fa90b7dc2134d12"
checksum = "45ca28513560e56cfb79a62b1fce363c73af170a182024ce880c77ee9429920a"
dependencies = [
"rb-sys-build",
]
[[package]]
name = "rb-sys-build"
version = "0.9.127"
version = "0.9.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1688e8f32967ba48c89e4dfa283b57f901075f542fc7ee9c3d7c5f9091ca1d9"
checksum = "ce04b2c55eff3a21aaa623fcc655d94373238e72cac6b3e1a3641ff31649f99a"
dependencies = [
"bindgen",
"lazy_static",
@@ -988,14 +960,16 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
checksum = "348e860aeb0b7bd035778fd11dd9cd5290d32e4aed3b8f2274a00287a9fd362b"
dependencies = [
"ahash",
"fluent-uri",
"getrandom 0.3.4",
"hashbrown 0.16.1",
"hashbrown",
"itoa",
"micromap",
"parking_lot",
"percent-encoding",
"serde_json",
@@ -1003,9 +977,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.3"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
@@ -1015,9 +989,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.14"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [
"aho-corasick",
"memchr",
@@ -1026,13 +1000,13 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "regorus"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"anyhow",
"chrono",
@@ -1045,7 +1019,7 @@ dependencies = [
"lazy_static",
"lru",
"msvc_spectre_libs",
"num-bigint",
"num-bigint 0.5.1",
"num-traits",
"parking_lot",
"rand",
@@ -1063,7 +1037,7 @@ dependencies = [
[[package]]
name = "regorus-mimalloc"
version = "2.2.6"
version = "2.2.7"
dependencies = [
"regorus-mimalloc-sys",
]
@@ -1077,7 +1051,7 @@ dependencies = [
[[package]]
name = "regorusrb"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"magnus",
"regorus",
@@ -1088,15 +1062,15 @@ dependencies = [
[[package]]
name = "rustc-hash"
version = "2.1.2"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rustversion"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ryu"
@@ -1154,9 +1128,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -1202,10 +1176,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "siphasher"
version = "1.0.2"
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "siphasher"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "slab"
@@ -1215,15 +1195,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "spin"
version = "0.10.0"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
checksum = "8abadc99fd9c7bbb7d0ca2b31d72a067d0c0dcd7aad25ab8cac71ba91417694b"
[[package]]
name = "stable_deref_trait"
@@ -1233,9 +1213,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
@@ -1301,12 +1281,6 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
@@ -1333,11 +1307,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.23.1"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
dependencies = [
"getrandom 0.4.2",
"getrandom 0.4.3",
"rand",
]
@@ -1365,27 +1339,18 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [
"cfg-if",
"once_cell",
@@ -1396,9 +1361,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1406,9 +1371,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1419,47 +1384,13 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "windows-core"
version = "0.62.2"
@@ -1519,100 +1450,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.3"
@@ -1621,9 +1464,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yoke"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -1644,18 +1487,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
dependencies = [
"proc-macro2",
"quote",
@@ -1664,9 +1507,9 @@ dependencies = [
[[package]]
name = "zerofrom"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
@@ -1718,6 +1561,6 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

View File

@@ -11,6 +11,6 @@ gem "minitest", "~> 6.0"
gem "rake", "~> 13.4"
gem "rake-compiler", "~> 1.3"
gem "rake-compiler-dock", "~> 1.12"
gem "rubocop", "~> 1.86", require: false
gem "rubocop", "~> 1.88", require: false
gem "rubocop-minitest", "~> 0.39.1", require: false
gem "rubocop-rake", "~> 0.7.1", require: false

View File

@@ -9,10 +9,10 @@ GEM
specs:
ast (2.4.3)
drb (2.2.3)
json (2.19.4)
json (2.20.0)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
minitest (6.0.5)
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
parallel (2.1.0)
@@ -26,10 +26,10 @@ GEM
rake-compiler (1.3.1)
rake
rake-compiler-dock (1.12.0)
rb_sys (0.9.127)
rb_sys (0.9.128)
rake-compiler-dock (= 1.12.0)
regexp_parser (2.12.0)
rubocop (1.86.1)
rubocop (1.88.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -65,7 +65,7 @@ DEPENDENCIES
rake-compiler (~> 1.3)
rake-compiler-dock (~> 1.12)
regorusrb!
rubocop (~> 1.86)
rubocop (~> 1.88)
rubocop-minitest (~> 0.39.1)
rubocop-rake (~> 0.7.1)

View File

@@ -1,6 +1,6 @@
[package]
name = "regorusrb"
version = "0.10.0"
version = "0.11.0"
edition = "2024"
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Regorus
VERSION = "0.10.0"
VERSION = "0.11.0"
end

437
bindings/wasm/Cargo.lock generated
View File

@@ -42,9 +42,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.102"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "async-trait"
@@ -59,9 +59,9 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bit-set"
@@ -80,9 +80,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.11.1"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "borrow-or-share"
@@ -92,19 +92,19 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c"
[[package]]
name = "bstr"
version = "1.12.1"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
dependencies = [
"memchr",
"serde",
"serde_core",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytecount"
@@ -120,9 +120,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.61"
version = "1.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -136,9 +136,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -147,9 +147,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.44"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -200,9 +200,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]]
name = "displaydoc"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
@@ -238,9 +238,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fancy-regex"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
checksum = "e1e1dacd0d2082dfcf1351c4bdd566bbe89a2b263235a2b50058f1e130a47277"
dependencies = [
"bit-set",
"regex-automata",
@@ -264,12 +264,6 @@ dependencies = [
"serde",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
@@ -348,25 +342,23 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi 6.0.0",
"rand_core",
"wasip2",
"wasip3",
"wasm-bindgen",
]
[[package]]
name = "globset"
version = "0.4.18"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
dependencies = [
"aho-corasick",
"bstr",
@@ -376,36 +368,15 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "iana-time-zone"
version = "0.1.65"
@@ -512,12 +483,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
@@ -546,7 +511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
"hashbrown",
"serde",
"serde_core",
]
@@ -565,21 +530,20 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.97"
version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "jsonschema"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "257eb0e588b76827bbddc9e73945a9743693dd2adeaee9da26420f93cfedb798"
checksum = "281c43ff06dcb331e9356d30e38853d559ce3d0a3f693e0b0e102667dec14fb1"
dependencies = [
"ahash",
"bytecount",
@@ -590,30 +554,33 @@ dependencies = [
"getrandom 0.3.4",
"idna",
"itoa",
"jsonschema-regex",
"num-cmp",
"num-traits",
"percent-encoding",
"referencing",
"regex",
"regex-syntax",
"serde",
"serde_json",
"unicode-general-category",
"uuid-simd",
]
[[package]]
name = "jsonschema-regex"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee0b351864e7ffbc5db9273daf7fa1b4d5177b0946713d667ca571b83c0b4045"
dependencies = [
"regex-syntax",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
@@ -643,21 +610,27 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.29"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "lru"
version = "0.16.4"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6"
[[package]]
name = "memchr"
version = "2.8.0"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "micromap"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
[[package]]
name = "minicov"
@@ -693,7 +666,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-complex",
"num-integer",
"num-iter",
@@ -703,9 +676,19 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.6"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
dependencies = [
"num-integer",
"num-traits",
@@ -737,11 +720,10 @@ dependencies = [
[[package]]
name = "num-iter"
version = "0.1.45"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
@@ -752,7 +734,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-bigint 0.4.8",
"num-integer",
"num-traits",
]
@@ -859,16 +841,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -880,9 +852,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
@@ -901,12 +873,12 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
"chacha20",
"getrandom 0.4.2",
"getrandom 0.4.3",
"rand_core",
]
@@ -947,14 +919,16 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.45.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2f38748ceca8d0b0013e60f534d94a6e23dfd89fd2a88318fc5a2d04fda1010"
checksum = "348e860aeb0b7bd035778fd11dd9cd5290d32e4aed3b8f2274a00287a9fd362b"
dependencies = [
"ahash",
"fluent-uri",
"getrandom 0.3.4",
"hashbrown 0.16.1",
"hashbrown",
"itoa",
"micromap",
"parking_lot",
"percent-encoding",
"serde_json",
@@ -962,9 +936,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.3"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
@@ -974,9 +948,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.14"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
dependencies = [
"aho-corasick",
"memchr",
@@ -985,13 +959,13 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "regorus"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"anyhow",
"chrono",
@@ -1004,7 +978,7 @@ dependencies = [
"lazy_static",
"lru",
"msvc_spectre_libs",
"num-bigint",
"num-bigint 0.5.1",
"num-traits",
"parking_lot",
"postcard",
@@ -1022,11 +996,11 @@ dependencies = [
[[package]]
name = "regorusjs"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"getrandom 0.2.17",
"getrandom 0.3.4",
"getrandom 0.4.2",
"getrandom 0.4.3",
"regorus",
"serde",
"serde-wasm-bindgen",
@@ -1038,9 +1012,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ryu"
@@ -1112,9 +1086,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -1138,15 +1112,15 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.3.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "siphasher"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "slab"
@@ -1156,15 +1130,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "spin"
version = "0.10.0"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
checksum = "8abadc99fd9c7bbb7d0ca2b31d72a067d0c0dcd7aad25ab8cac71ba91417694b"
[[package]]
name = "stable_deref_trait"
@@ -1174,9 +1148,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
@@ -1236,12 +1210,6 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
@@ -1268,11 +1236,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.23.1"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
dependencies = [
"getrandom 0.4.2",
"getrandom 0.4.3",
"js-sys",
"rand",
"wasm-bindgen",
@@ -1318,27 +1286,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [
"cfg-if",
"once_cell",
@@ -1349,9 +1308,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.70"
version = "0.4.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084"
checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -1359,9 +1318,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1369,9 +1328,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1382,18 +1341,18 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-bindgen-test"
version = "0.3.70"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29826f9d9ecaa314c480d376b276d1c790e6cb6a4681fab8532da69cbabf977d"
checksum = "2a0d555ca874445df8d314f94f5c948a4e74e5418f332c89f660a3d8310a96f4"
dependencies = [
"async-trait",
"cast",
@@ -1413,9 +1372,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.70"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c610311887f9e6599a546d278d12d69dfd3a3e92639b2129e4b11ad6cf1961d6"
checksum = "94eb68555b95bcea5e8cf4abe280b529049479fa995bfc23734af96a6aedc120"
dependencies = [
"proc-macro2",
"quote",
@@ -1424,43 +1383,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-shared"
version = "0.2.120"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60238e5b4b1b295701d6f9a66d2a126fe19990348f5fb9dae3b623a370119d94"
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
checksum = "c31d56021e873866c968588ed85ccdf56db5c426e44afdb4618c39895104b920"
[[package]]
name = "winapi-util"
@@ -1539,100 +1464,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.3"
@@ -1641,9 +1478,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yoke"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -1664,18 +1501,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
dependencies = [
"proc-macro2",
"quote",
@@ -1684,9 +1521,9 @@ dependencies = [
[[package]]
name = "zerofrom"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
@@ -1738,6 +1575,6 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

View File

@@ -2,7 +2,7 @@
[package]
name = "regorusjs"
version = "0.10.0"
version = "0.11.0"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/wasm"
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -42,7 +42,7 @@ coverage = ["regorus/coverage"]
[dependencies]
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
serde_json = "1.0.150"
wasm-bindgen = "0.2.100"
serde-wasm-bindgen = "0.6"
# Specify uuid as a mandatory dependency so as to enable `js` feature which is now required
@@ -55,7 +55,7 @@ getrandom03 = { package = "getrandom", version = "0.3.1", features = ["std", "wa
getrandom = { version = "0.4.2", features = ["wasm_js"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.67"
wasm-bindgen-test = "0.3.72"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }

View File

@@ -254,7 +254,13 @@ include formatted state snapshots where possible.
7. **Host await**: In run-to-completion mode, `HostAwait` consumes a response
from `host_await_responses`. Suspendable mode yields control with a
`SuspendReason::HostAwait { dest, argument, identifier }` that the host must
service.
service. The compiler supports two ways to emit `HostAwait`:
- **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
`InstructionOutcome::Return`, unwinding frames until the entry frame is
cleared. `RuleReturn` is a specialised variant used by rule execution

View File

@@ -177,6 +177,75 @@ Parameter tables:
- Suspendable: emits `InstructionOutcome::Suspend` with `SuspendReason::HostAwait`.
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

84
docs/value/object.md Normal file
View File

@@ -0,0 +1,84 @@
# Object
Opaque container for `Value::Object`'s key→value storage, enabling
alternative backends without call-site changes.
## Design
`Object` wraps the storage for a key→value collection of `Value`s and
provides a curated set of methods (`get`, `insert`, `remove`, `iter`,
`iter_sorted`, `cursor`, serde). The backing store is private; callers
never see or pattern-match on it, so the representation can change
without rippling through call sites.
Multiple backends can coexist at runtime. Because the backing store is
private, different `Object` instances in the same process can use
different implementations — e.g., a lazy DB-backed object for `input`,
inline small-map objects for SARIF location records, and a regular
sorted map elsewhere — all interoperating through the same opaque
type. This is stronger than the typical Cargo-feature-selected backend
seen in precedent crates.
Iteration is split intentionally. `iter()` makes no ordering promise,
which lets backends that don't keep entries sorted skip any sort work.
`iter_sorted()` returns entries in `Value` order and is what
serialization and `Ord` rely on for deterministic output. Cursor types
add resumable, incremental traversal for the RVM iteration state
without leaking iterator internals.
`Ord` and `PartialOrd` are defined against `iter_sorted()` rather than
derived from the storage. Two `Object`s built on different backends —
or with different insertion histories — compare equal whenever their
sorted entries match, so changing the backend never changes observable
comparison results.
## Precedents
Other crates that hide storage behind a stable API so the implementation
can change without breaking callers:
- **`serde_json::Map`** — opaque newtype allowing cargo-feature based
swap between `BTreeMap` (canonical order) and `IndexMap` (insertion
order).
- **`toml::Table`** — opaque newtype allowing cargo-feature based swap
between `BTreeMap` and `IndexMap`.
- **`simdjson` DOM** — opaque tree that lazily materializes nodes on
access instead of parsing the whole document up front.
## Use cases
- **SARIF small-object pressure** — SARIF reports contain millions of
small objects (location records, rule references, message arguments),
most with 2-5 keys. A small-map-optimized backend (inline storage
for ≤N entries, heap above) eliminates per-object BTreeMap allocation
for the common case.
- **Kubernetes admission policies** — large, deeply-nested resource
objects (Pod specs, CRDs) where policies typically touch a handful
of paths. A lazy-materializing backend (`LazyObjectProvider` over
the incoming JSON) parses only the accessed subtrees.
- **Azure Policy aliases** — ARM exposes the same logical property
under multiple aliases (e.g. paths like
`Microsoft.Compute/virtualMachines/storageProfile.osDisk.managedDisk.id`).
An alias-aware backend resolves lookups across canonical and alias
forms without rewriting every policy.
- **Azure Policy case-insensitive compare** — ARM property names are
case-preserving but case-insensitive on lookup (`tags.Environment`
and `tags.environment` resolve identically). A case-insensitive
backend centralizes this once at the storage layer instead of at
every comparison site.
- **External data sources** — `input` or `data` backed by a database
query, CBOR slice, REST endpoint, or other streaming source via a
`LazyObjectProvider`. Entries materialize on demand; the policy
only pays for what it touches.
- **Eval-time temporaries** — objects constructed during evaluation
(comprehensions, intermediate rule results) on a bumpalo arena.
The whole arena drops at query end with zero per-entry free cost.
- **Host-language interop** — Python dicts or JS objects accessed via
FFI callbacks from the embedding application, without copying into
Rust on every binding boundary.

79
docs/value/set.md Normal file
View File

@@ -0,0 +1,79 @@
# 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.

View File

@@ -23,8 +23,7 @@ use regorus::languages::azure_policy::aliases::AliasRegistry;
use regorus::languages::azure_policy::compiler;
use regorus::languages::azure_policy::parser;
use regorus::rvm::RegoVM;
use regorus::Source;
use regorus::Value;
use regorus::{Rc, Source, Value};
/// Evaluate an Azure Policy definition against a resource.
///
@@ -60,11 +59,8 @@ pub fn azure_policy_eval(
println!("Parsed policy definition from {policy_definition}");
// 3. Compile to RVM bytecode.
let program = compiler::compile_policy_definition_with_aliases(
&defn,
registry.alias_map(),
registry.alias_modifiable_map(),
)?;
let registry = Rc::new(registry);
let program = compiler::compile_policy_definition_with_aliases(&defn, Rc::clone(&registry))?;
println!("Compiled policy to RVM bytecode");
// 4. Build normalized input.
@@ -138,7 +134,7 @@ pub fn azure_policy_aliases(aliases: String, resource_type: Option<String>) -> R
if let Some(ref rt) = resource_type {
let rt_lower = rt.to_lowercase();
let mut found = false;
for (alias_name, _) in registry.alias_map() {
for alias_name in registry.alias_map().keys() {
if alias_name.to_lowercase().starts_with(&rt_lower) {
println!(" {alias_name}");
found = true;
@@ -148,7 +144,7 @@ pub fn azure_policy_aliases(aliases: String, resource_type: Option<String>) -> R
bail!("no aliases found for resource type '{rt}'");
}
} else {
for (alias_name, _) in registry.alias_map() {
for alias_name in registry.alias_map().keys() {
println!(" {alias_name}");
}
}

View File

@@ -2,7 +2,7 @@
name = "regorus-mimalloc"
description = "Vendored mimalloc allocator for regorus"
edition = "2021"
version = "2.2.6"
version = "2.2.7"
license = "MIT"
repository = "https://github.com/microsoft/regorus"

View File

@@ -319,7 +319,7 @@ pub fn resolve_path(root: &Value, path: &str) -> Value {
match &current {
Value::Object(map) => {
let mut next = None;
for (key, value) in map.iter() {
for (key, value) in map.iter_sorted() {
if let Value::String(ref key_str) = *key {
if strings::keys::eq(key_str, &segment) {
next = Some(value.clone());

View File

@@ -8,10 +8,10 @@
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::lexer::Span;
use crate::value::Object;
use crate::value::Value;
use crate::Rc;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use anyhow::Result;
@@ -72,7 +72,7 @@ fn fn_intersection(
// Intersection of objects: keep key-value pairs from the first
// object only when the key exists in every other object AND
// the value is equal across all of them.
let mut result: BTreeMap<Value, Value> = first.as_ref().clone();
let mut result: Object = first.as_ref().clone();
for arg in rest {
let Value::Object(ref other) = *arg else {
return Ok(Value::Undefined);
@@ -114,7 +114,7 @@ fn fn_union(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool)
Value::Object(_) => {
// Union of objects: recursive merge. Nested objects are merged
// recursively; all other types (including arrays) use last-writer-wins.
let mut result = BTreeMap::<Value, Value>::new();
let mut result = Object::new();
for arg in args {
let Value::Object(ref obj) = *arg else {
return Ok(Value::Undefined);
@@ -264,7 +264,7 @@ fn fn_create_object(
);
}
let mut map = BTreeMap::<Value, Value>::new();
let mut map = Object::new();
for pair in args.chunks(2) {
#[allow(clippy::pattern_type_mismatch)]
@@ -280,9 +280,9 @@ fn fn_create_object(
/// Recursively merge two objects. Nested objects are merged; everything
/// else (including arrays) uses the value from `incoming`.
fn merge_objects(base: &BTreeMap<Value, Value>, overlay: &BTreeMap<Value, Value>) -> Value {
fn merge_objects(base: &Object, overlay: &Object) -> Value {
let mut result = base.clone();
for (k, v) in overlay {
for (k, v) in overlay.iter() {
#[allow(clippy::needless_borrowed_reference)]
let merged = match (result.get(k), v) {
(Some(&Value::Object(ref prev)), &Value::Object(ref next)) => merge_objects(prev, next),

View File

@@ -8,10 +8,10 @@
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::lexer::Span;
use crate::value::Object;
use crate::value::Value;
use crate::Rc;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
use anyhow::Result;
@@ -84,8 +84,8 @@ fn fn_items(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool)
return Ok(Value::Undefined);
};
let mut result = Vec::with_capacity(obj.len());
for (k, v) in obj.as_ref() {
let mut entry = BTreeMap::<Value, Value>::new();
for (k, v) in obj.iter_sorted() {
let mut entry = Object::new();
entry.insert(Value::from("key"), k.clone());
entry.insert(Value::from("value"), v.clone());
result.push(Value::Object(Rc::new(entry)));

View File

@@ -308,7 +308,7 @@ fn urlquery_encode_object(
{
let mut pairs = url.query_pairs_mut();
for (key, value) in obj.iter() {
for (key, value) in obj.iter_sorted() {
let key = ensure_string(name, &params[0], key)?;
match value {
Value::String(v) => {

View File

@@ -7,10 +7,11 @@ use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{enforce_limit, ensure_args_count, ensure_object};
use crate::lexer::Span;
use crate::value::Object;
use crate::value::Value;
use crate::*;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::collections::BTreeSet;
use anyhow::{bail, Result};
@@ -80,7 +81,7 @@ fn reachable(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) ->
}
fn visit(
graph: &BTreeMap<Value, Value>,
graph: &Object,
visited: &mut BTreeSet<Value>,
node: &Value,
path: &mut Vec<Value>,
@@ -211,7 +212,7 @@ fn walk_visit(path: &mut Vec<Value>, value: &Value, paths: &mut Vec<Value>) -> R
}
}
Value::Object(obj) => {
for (key, value) in obj.iter() {
for (key, value) in obj.iter_sorted() {
path.push(key.clone());
// Guard path stack growth while traversing object entries.
enforce_limit()?;

View File

@@ -205,7 +205,7 @@ fn merge_filters(
let vref = match f {
Value::Object(obj) => {
let obj = Rc::make_mut(obj);
let entry = obj.entry(p.clone()).or_insert_with(Value::new_object);
let entry = obj.get_or_insert_with(p.clone(), Value::new_object);
// Guard filter map growth when creating nested objects.
enforce_limit()?;
entry

View File

@@ -207,7 +207,7 @@ fn to_string(v: &Value, unescape: bool) -> String {
}
Value::Object(o) => {
"{".to_owned()
+ &o.iter()
+ &o.iter_sorted()
.map(|(k, v)| to_string(k, true) + ": " + &to_string(v, true))
.collect::<Vec<String>>()
.join(", ")
@@ -568,7 +568,7 @@ fn replace_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -
let mut s = ensure_string(name, &params[1], &args[1])?;
let span = params[0].span();
for item in obj.as_ref().iter() {
for item in obj.as_ref().iter_sorted() {
match item {
(Value::String(k), Value::String(v)) => {
s = s.replace(k.as_ref(), v.as_ref()).into();

View File

@@ -5,11 +5,12 @@
use crate::ast::{Expr, Ref};
use crate::lexer::Span;
use crate::number::Number;
use crate::value::Object;
use crate::Rc;
use crate::Value;
use crate::*;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::collections::BTreeSet;
use anyhow::{bail, Result};
@@ -168,7 +169,7 @@ pub fn ensure_set(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<BTreeSet<Value>>
})
}
pub fn ensure_object(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<BTreeMap<Value, Value>>> {
pub fn ensure_object(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<Object>> {
Ok(match v {
Value::Object(o) => o,
_ => {

View File

@@ -217,7 +217,7 @@ pub(crate) struct CompiledPolicyData {
pub(crate) default_rules: Map<String, Vec<DefaultRuleInfo>>,
pub(crate) imports: BTreeMap<String, Ref<Expr>>,
pub(crate) functions: FunctionTable,
pub(crate) rule_paths: Set<String>,
pub(crate) rule_paths: MapSet<String>,
#[cfg(feature = "azure_policy")]
pub(crate) target_info: Option<TargetInfo>,
#[cfg(feature = "azure_policy")]

View File

@@ -314,7 +314,7 @@ fn order_element_pairs<T: VariableBindingContext>(
if ready {
let (value_expr, plan, _deps, binds) = remaining.remove(idx);
scheduled.extend(binds.into_iter());
scheduled.extend(binds);
ordered.push((value_expr, plan));
progress = true;
break;

View File

@@ -434,7 +434,13 @@ impl Engine {
/// Add data document.
///
/// The specified data document is merged into existing data document.
/// The specified data document is deep-merged into the existing data document. Nested
/// 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::*;
@@ -453,9 +459,13 @@ impl Engine {
/// // Merge { "z" : 3 }. Conflict error.
/// 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!(
/// engine.eval_query("data".to_string(), false)?.result[0].expressions[0].value,
/// Value::from_json_str(r#"{ "x": 1, "y": {}, "z": 2}"#)?
/// Value::from_json_str(r#"{ "x": 1, "y": { "a": 10, "b": 20 }, "z": 2}"#)?
/// );
/// # Ok(())
/// # }
@@ -464,8 +474,29 @@ impl Engine {
if data.as_object().is_err() {
bail!("data must be object");
}
self.prepared = false;
self.interpreter.get_init_data_mut().merge(data)
// add_data is all-or-nothing; the atomic strategy differs by build because the failure
// 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.

View File

@@ -28,7 +28,6 @@ use crate::{Expression, Extension, Location, QueryResult, QueryResults};
use crate::query::traversal::traverse;
use crate::Rc;
use alloc::collections::btree_map::Entry as BTreeMapEntry;
use alloc::collections::{BTreeMap, BTreeSet};
use anyhow::{anyhow, bail, Result};
use core::ops::Bound::*;
@@ -61,6 +60,17 @@ enum FunctionModifier {
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>)>;
#[derive(Debug)]
@@ -1312,10 +1322,10 @@ impl Interpreter {
*obj = Value::new_object();
}
obj = obj
.as_object_mut()?
.entry(Value::String(p.to_string().into()))
.or_insert(Value::new_object());
obj = obj.as_object_mut()?.get_or_insert_with(
Value::String(p.to_string().into()),
Value::new_object,
);
}
*obj = value;
// Mark modified rules as processed.
@@ -1682,8 +1692,7 @@ impl Interpreter {
let set = obj
.as_object_mut()
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
.entry(p)
.or_insert(Value::new_set())
.get_or_insert_with(p, Value::new_set)
.as_set_mut()
.map_err(|_| anyhow!(span.error("previous value is not a set")))?;
set.append(value.as_set_mut()?);
@@ -1691,20 +1700,13 @@ impl Interpreter {
let obj = obj
.as_object_mut()
.map_err(|_| anyhow!(span.error("previous value is not an object")))?;
match obj.entry(p) {
BTreeMapEntry::Vacant(v) => {
if value != Value::Undefined {
v.insert(value);
} else {
// TODO: clean this assumption between Undefined vs Object.
v.insert(Value::new_object());
}
}
BTreeMapEntry::Occupied(o) => {
if o.get() != &value && value != Value::Undefined {
bail!(span
.error("complete rules should not produce multiple outputs"))
}
if value == Value::Undefined {
// TODO: clean this assumption between Undefined vs Object.
obj.get_or_insert_with(p, Value::new_object);
} else {
let existing = obj.get_or_insert_with(p, || value.clone());
if *existing != value {
bail!(span.error("complete rules should not produce multiple outputs"))
}
}
}
@@ -1713,8 +1715,7 @@ impl Interpreter {
obj = obj
.as_object_mut()
.map_err(|_| anyhow!(span.error("previous value is not an object")))?
.entry(p)
.or_insert(Value::new_object());
.get_or_insert_with(p, Value::new_object);
}
}
Ok(())
@@ -1782,6 +1783,7 @@ impl Interpreter {
let mut comps = self.eval_rule_ref(&rule_ref)?;
if let Some(ke) = &key_expr {
is_const_rule = is_const_rule && Self::is_simple_literal(ke)?;
comps.push(self.eval_expr(ke)?);
}
let output = if let Some(oe) = &output_expr {
@@ -1821,8 +1823,7 @@ impl Interpreter {
let set = ctx_mut
.rule_value
.as_object_mut()?
.entry(Value::from_array(comps))
.or_insert(Value::new_set());
.get_or_insert_with(Value::from_array(comps), Value::new_set);
if output != Value::Undefined {
set.as_set_mut()?.insert(output);
return Ok(true);
@@ -1831,20 +1832,13 @@ impl Interpreter {
}
// Non-set rule.
match ctx_mut
.rule_value
.as_object_mut()?
.entry(Value::from_array(comps))
{
BTreeMapEntry::Vacant(v) => {
v.insert(output);
}
BTreeMapEntry::Occupied(o) if o.get() != &output => bail!(rule_ref
let key = Value::from_array(comps);
let obj_mut = ctx_mut.rule_value.as_object_mut()?;
let existing = obj_mut.get_or_insert_with(key, || output.clone());
if *existing != output {
bail!(rule_ref
.span()
.error("rules must not produce multiple outputs")),
_ => {
// Rule produced same value.
}
.error("rules must not produce multiple outputs"));
}
return Ok(true);
@@ -2470,7 +2464,7 @@ impl Interpreter {
}
Value::Object(map) => {
s.push('{');
for (idx, (k, entry_value)) in map.iter().enumerate() {
for (idx, (k, entry_value)) in map.iter_sorted().enumerate() {
if idx > 0 {
s.push_str(", ");
}
@@ -3425,6 +3419,23 @@ 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> {
let mut comps = vec![];
let mut expr_opt = Some(refr);
@@ -3680,6 +3691,7 @@ impl Interpreter {
_refr: &Expr,
path: &[&str],
value: Value,
merge: RuleValueMerge,
) -> Result<()> {
if value == Value::Undefined {
return Ok(());
@@ -3687,7 +3699,10 @@ impl Interpreter {
// Ensure that path is created.
let vref = Self::make_or_get_value_mut(&mut self.data, path)?;
if Self::get_value_chained(self.init_data.clone(), path) == Value::Undefined {
Self::merge_rule_value(span, vref, value)
match merge {
RuleValueMerge::Strict => Self::merge_rule_value_strict(span, vref, value),
RuleValueMerge::Combine => Self::merge_rule_value(span, vref, value),
}
} else {
// Retain specified value.
Ok(())
@@ -3795,7 +3810,13 @@ impl Interpreter {
// `a` is created as an empty object.
if let Some((_, prefix)) = path.split_last() {
if !prefix.is_empty() {
self.update_data(span, refr, prefix, Value::new_object())?;
self.update_data(
span,
refr,
prefix,
Value::new_object(),
RuleValueMerge::Combine,
)?;
}
}
@@ -3807,7 +3828,13 @@ impl Interpreter {
};
let value = self.eval_rule_bodies(ctx, span, rule_body)?;
self.update_data(refr.span(), refr, &path[..], value)?;
self.update_data(
refr.span(),
refr,
&path[..],
value,
RuleValueMerge::Strict,
)?;
}
}
}
@@ -4054,6 +4081,7 @@ impl Interpreter {
rule_refr,
&prefix_path,
Value::new_object(),
RuleValueMerge::Combine,
)?;
}
}

View File

@@ -213,10 +213,10 @@ pub fn denormalize_with_aliases(
// Phase 4: Attach properties to result.
if !properties.is_empty() {
if let Some(Value::Object(existing_rc)) = result.get_mut("properties") {
// Merge directly into the BTreeMap, avoiding full ObjMap round-trip.
// Merge directly into the Object, avoiding full ObjMap round-trip.
let existing = Rc::make_mut(existing_rc);
for (k, v) in properties {
existing.entry(Value::String(k)).or_insert(v);
existing.get_or_insert_with(Value::String(k), || v);
}
} else {
obj_insert(&mut result, "properties", make_value(properties));

View File

@@ -7,6 +7,7 @@ use alloc::collections::{BTreeMap, BTreeSet};
use alloc::string::String;
use alloc::vec::Vec;
use crate::value::Object;
use crate::Value;
use super::super::obj_map::{make_value, new_map, obj_insert, val_str, ObjMap};
@@ -141,7 +142,7 @@ fn rewrap_nested_array(
/// BTreeMap-native recursion for nested sub-resource array re-wrapping,
/// avoiding ObjMap round-trips on each array element.
fn rewrap_nested_array_in_btree(
btree: &mut alloc::collections::BTreeMap<Value, Value>,
btree: &mut Object,
parent_parts: &[&str],
array_name: &str,
envelope_fields: &BTreeSet<String>,
@@ -187,10 +188,7 @@ fn rewrap_nested_array_in_btree(
}
/// Find a key in a BTreeMap using case-insensitive comparison.
fn find_key_ci_btree(
btree: &alloc::collections::BTreeMap<Value, Value>,
key: &str,
) -> Option<Value> {
fn find_key_ci_btree(btree: &Object, key: &str) -> Option<Value> {
btree
.keys()
.find(|k| val_str(k).is_some_and(|s| s.eq_ignore_ascii_case(key)))

View File

@@ -172,11 +172,31 @@ impl AliasRegistry {
let prefix = alloc::format!("{}/", fq_type);
for alias in aliases {
// Skip aliases without a default_path — the normalizer's
// resolve_resource_type also skips these, so inserting them into
// compiler maps would cause a divergence where the compiler
// resolves the alias but normalized input never contains the field.
if alias.default_path.is_none() {
continue;
}
// Derive the short name by stripping the resource type prefix.
let raw_short = if alias.name.len() > prefix.len()
&& alias.name[..prefix.len()].eq_ignore_ascii_case(&prefix)
&& alias
.name
.get(..prefix.len())
.is_some_and(|s| s.eq_ignore_ascii_case(&prefix))
{
alias.name[prefix.len()..].to_string()
// Both slice boundaries are valid: prefix is ASCII
// (resource type + '/'), so if `..prefix.len()` succeeded
// above, `prefix.len()..` is guaranteed to be on a char
// boundary too. The `unwrap_or` is a defensive fallback
// that can never trigger for well-formed Azure alias names.
alias
.name
.get(prefix.len()..)
.unwrap_or(&alias.name)
.to_string()
} else if let Some(rest) = alias
.name
.rfind('/')
@@ -260,20 +280,19 @@ impl AliasRegistry {
.map(String::as_str)
}
/// Return a clone of the alias-to-short-name map for use by the compiler.
/// Return a reference to the alias-to-short-name map.
///
/// The compiler stores this map internally so it can resolve fully-qualified
/// alias names without holding a reference to the registry.
pub fn alias_map(&self) -> BTreeMap<String, String> {
self.alias_to_short.clone()
/// Keys are lowercase fully-qualified alias names; values are short names.
pub const fn alias_map(&self) -> &BTreeMap<String, String> {
&self.alias_to_short
}
/// Return a clone of the alias-to-modifiable map for use by the compiler.
/// Return a reference to the alias-to-modifiable map.
///
/// Maps lowercase fully-qualified alias names to `true` when the alias
/// has `defaultMetadata.attributes = "Modifiable"`.
pub fn alias_modifiable_map(&self) -> BTreeMap<String, bool> {
self.alias_modifiable.clone()
/// Keys are lowercase fully-qualified alias names; values are `true` when
/// the alias has `defaultMetadata.attributes = "Modifiable"`.
pub const fn alias_modifiable_map(&self) -> &BTreeMap<String, bool> {
&self.alias_modifiable
}
/// Normalize a raw ARM resource and wrap it in the input envelope.

View File

@@ -4,14 +4,13 @@
//! Per-alias path resolution: reads values from versioned ARM paths and places
//! them at alias short name paths in the normalized output.
use alloc::string::String;
use crate::Rc;
use crate::Value;
use super::super::obj_map::remove_element_field;
use super::super::obj_map::{
collision_safe_key, is_root_field_collision, obj_contains, obj_insert, obj_remove,
set_nested_lowercased, ObjMap,
collision_safe_key, is_root_field_collision, obj_contains, obj_insert, obj_insert_rc,
obj_remove, set_nested_lowercased, ObjMap,
};
use super::super::types::ResolvedAliases;
use super::element_remap::apply_element_remap_precomputed;
@@ -48,12 +47,14 @@ pub fn apply_alias_entries(
if let Some(value) = value {
let value = normalize_value(&value, &entry.short_name, None);
let target = if is_root_field_collision(&entry.short_name, &entry.default_path) {
collision_safe_key(&entry.short_name)
if is_root_field_collision(&entry.short_name, &entry.default_path) {
let target = collision_safe_key(&entry.short_name);
set_nested_lowercased(result, &target, value);
} else if entry.short_name.contains('.') {
set_nested_lowercased(result, &entry.short_name, value);
} else {
entry.short_name.clone()
};
set_nested_lowercased(result, &target, value);
obj_insert_rc(result, Rc::clone(&entry.short_name_lc), value);
}
}
}
@@ -84,13 +85,13 @@ pub fn apply_alias_entries(
}
/// Navigate an ARM path using precomputed segments (avoids per-call split).
fn navigate_arm_path_segments(value: &Value, segments: &[String]) -> Option<Value> {
fn navigate_arm_path_segments(value: &Value, segments: &[Rc<str>]) -> Option<Value> {
let mut current = value;
for segment in segments {
current = current
.as_object()
.ok()?
.get(&Value::from(segment.as_str()))?;
.get(&Value::String(Rc::clone(segment)))?;
}
Some(current.clone())
}

View File

@@ -6,11 +6,12 @@
use alloc::string::String;
use alloc::vec::Vec;
use crate::value::Object;
use crate::Value;
use super::super::obj_map::{
obj_get, obj_get_mut, obj_insert, set_nested_in_btree, set_nested_lowercased,
set_nested_verbatim, ObjMap,
obj_get, obj_get_mut, obj_insert, set_nested, set_nested_lowercased, set_nested_verbatim,
ObjMap,
};
use super::super::types::PrecomputedRemap;
@@ -118,7 +119,7 @@ fn apply_remap_at_depth(
/// BTreeMap-native recursion for element-level remap, avoiding ObjMap
/// round-trips on each array element.
fn remap_at_depth_in_btree(
btree: &mut alloc::collections::BTreeMap<Value, Value>,
btree: &mut Object,
array_chain: &[Vec<String>],
depth: usize,
source_field: &str,
@@ -177,12 +178,7 @@ fn remap_at_depth_in_btree(
}
/// Remap a value between dotted paths directly in a BTreeMap.
fn remap_deep_field_in_btree(
btree: &mut alloc::collections::BTreeMap<Value, Value>,
source: &str,
target: &str,
lowercase: bool,
) {
fn remap_deep_field_in_btree(btree: &mut Object, source: &str, target: &str, lowercase: bool) {
let val = match read_dotted_path_btree(btree, source) {
Some(v) => v,
None => return,
@@ -198,14 +194,11 @@ fn remap_deep_field_in_btree(
}
return;
}
set_nested_in_btree(btree, &segments, val, lowercase);
set_nested(btree, &segments, val, lowercase);
}
/// Read a value at a dotted path from a BTreeMap.
fn read_dotted_path_btree(
btree: &alloc::collections::BTreeMap<Value, Value>,
path: &str,
) -> Option<Value> {
fn read_dotted_path_btree(btree: &Object, path: &str) -> Option<Value> {
let segments: Vec<&str> = path.split('.').collect();
let first = segments.first()?;
let mut cur: &Value = btree.get(&Value::from(*first))?;

View File

@@ -13,6 +13,7 @@ mod flatten;
// Re-export items used by the denormalizer.
pub(crate) use element_remap::{apply_element_remap, ElementRemap};
use crate::value::Object;
use crate::Value;
use super::obj_map::{
@@ -109,7 +110,7 @@ pub fn normalize_with_aliases(
/// Merge `properties` fields into the result map, skipping keys that already
/// exist.
fn merge_properties(
obj: &alloc::collections::BTreeMap<Value, Value>,
obj: &Object,
result: &mut ObjMap,
sub_arrays: Option<&alloc::collections::BTreeSet<alloc::string::String>>,
) {

View File

@@ -4,14 +4,15 @@
//! Lightweight string-keyed map used during normalization/denormalization.
//!
//! Internally uses `hashbrown::HashMap<Rc<str>, Value>` for O(1) lookups,
//! then converts to `Value::Object` (a `BTreeMap<Value, Value>`) only at
//! then converts to `Value::Object` (an `Object`) only at
//! the output boundary via [`make_value`].
use alloc::string::{String, ToString as _};
use alloc::string::String;
use alloc::vec::Vec;
use hashbrown::HashMap;
use crate::value::Object;
use crate::Rc;
use crate::Value;
@@ -41,6 +42,33 @@ pub fn obj_insert(map: &mut ObjMap, key: &str, val: Value) {
map.insert(Rc::from(key), val);
}
/// Insert a key-value pair using a pre-allocated `Rc<str>` key.
///
/// Avoids the `Rc::from(key)` heap allocation that [`obj_insert`] performs.
pub fn obj_insert_rc(map: &mut ObjMap, key: Rc<str>, val: Value) {
map.insert(key, val);
}
/// Lowercase a string, returning an `Rc<str>`.
///
/// Both paths allocate an `Rc<str>` (header + string bytes). The fast-path
/// avoids creating an intermediate lowercased `String` when the input is
/// already all-lowercase ASCII.
pub fn rc_lowercase(s: &str) -> Rc<str> {
if s.bytes().all(|b| !b.is_ascii_uppercase()) {
Rc::from(s)
} else {
Rc::from(s.to_ascii_lowercase())
}
}
/// Insert a key-value pair with the key lowercased, using [`rc_lowercase`]
/// for the allocation fast-path.
pub fn obj_insert_lc(map: &mut ObjMap, key: &str, val: Value) {
let lc = rc_lowercase(key);
map.insert(lc, val);
}
/// Check whether a key exists.
pub fn obj_contains(map: &ObjMap, key: &str) -> bool {
map.contains_key(key)
@@ -54,14 +82,13 @@ pub fn obj_remove(map: &mut ObjMap, key: &str) -> Option<Value> {
/// Convert an [`ObjMap`] into a [`Value::Object`].
///
/// Keys are converted from `Rc<str>` to `Value::String` and inserted into
/// a `BTreeMap` to match the `Value::Object` representation.
/// an `Object` to match the `Value::Object` representation.
pub fn make_value(map: ObjMap) -> Value {
use alloc::collections::BTreeMap;
let mut btree = BTreeMap::new();
for (k, v) in map {
btree.insert(Value::String(k), v);
}
Value::Object(Rc::new(btree))
let obj: Object = map
.into_iter()
.map(|(k, v)| (Value::String(k), v))
.collect();
Value::Object(Rc::new(obj))
}
/// Convert a `Vec<Value>` into a `Value::Array`.
@@ -88,14 +115,14 @@ pub fn extract_type_field(resource: &Value) -> Option<&str> {
})
}
/// Convert a `Value::Object` (BTreeMap<Value, Value>) into an [`ObjMap`].
/// Convert a `Value::Object` (Object) into an [`ObjMap`].
///
/// Non-string keys are silently skipped.
#[allow(dead_code)]
pub fn value_to_obj_map(value: &Value) -> Option<ObjMap> {
let btree = value.as_object().ok()?;
let mut map = ObjMap::with_capacity(btree.len());
for (k, v) in btree.iter() {
let obj = value.as_object().ok()?;
let mut map = ObjMap::with_capacity(obj.len());
for (k, v) in obj.iter() {
if let Value::String(s) = k {
map.insert(Rc::clone(s), v.clone());
}
@@ -112,7 +139,7 @@ pub fn set_nested_lowercased(result: &mut ObjMap, path: &str, value: Value) {
}
if segments.len() == 1 {
if let Some(&seg) = segments.first() {
obj_insert(result, &seg.to_ascii_lowercase(), value);
obj_insert_lc(result, seg, value);
}
return;
}
@@ -144,30 +171,30 @@ fn set_nested_inner(obj: &mut ObjMap, segments: &[&str], value: Value, lowercase
};
if segments.len() == 1 {
let key = if lowercase {
first.to_ascii_lowercase()
let key: Rc<str> = if lowercase {
rc_lowercase(first)
} else {
first.to_string()
Rc::from(first)
};
obj_insert(obj, &key, value);
obj_insert_rc(obj, key, value);
return;
}
let seg = if lowercase {
first.to_ascii_lowercase()
let seg: Rc<str> = if lowercase {
rc_lowercase(first)
} else {
first.to_string()
Rc::from(first)
};
// Ensure an intermediate object exists at `seg`.
if !obj_contains(obj, &seg) {
obj_insert(obj, &seg, make_value(new_map()));
if !obj.contains_key(&*seg) {
obj_insert_rc(obj, Rc::clone(&seg), make_value(new_map()));
}
// Descend directly into the BTreeMap, avoiding ObjMap round-trip.
if let Some(Value::Object(inner_rc)) = obj_get_mut(obj, &seg) {
if let Some(Value::Object(inner_rc)) = obj.get_mut(&*seg) {
let inner_btree = Rc::make_mut(inner_rc);
set_nested_in_btree(
set_nested(
inner_btree,
segments.get(1..).unwrap_or_default(),
value,
@@ -176,41 +203,36 @@ fn set_nested_inner(obj: &mut ObjMap, segments: &[&str], value: Value, lowercase
}
}
/// Set a value at a path directly in a `BTreeMap<Value, Value>`, creating
/// Set a value at a path directly in an `Object`, creating
/// intermediate `Value::Object` nodes as needed.
///
/// This avoids the `btree_to_obj_map` / `obj_map_to_btree` round-trip that
/// would clone every sibling entry at each nesting level.
pub fn set_nested_in_btree(
btree: &mut alloc::collections::BTreeMap<Value, Value>,
segments: &[&str],
value: Value,
lowercase: bool,
) {
pub fn set_nested(obj: &mut Object, segments: &[&str], value: Value, lowercase: bool) {
let Some(&first) = segments.first() else {
return;
};
let key_str: String = if lowercase {
first.to_ascii_lowercase()
let key_rc: Rc<str> = if lowercase {
rc_lowercase(first)
} else {
first.to_string()
Rc::from(first)
};
let key_val = Value::String(Rc::from(key_str.as_str()));
let key_val = Value::String(Rc::clone(&key_rc));
if segments.len() == 1 {
btree.insert(key_val, value);
obj.insert(key_val, value);
return;
}
// Ensure an intermediate object exists.
if !btree.contains_key(&key_val) {
btree.insert(key_val.clone(), make_value(new_map()));
if !obj.contains_key(&key_val) {
obj.insert(key_val.clone(), make_value(new_map()));
}
if let Some(Value::Object(inner_rc)) = btree.get_mut(&key_val) {
if let Some(Value::Object(inner_rc)) = obj.get_mut(&key_val) {
let inner = Rc::make_mut(inner_rc);
set_nested_in_btree(
set_nested(
inner,
segments.get(1..).unwrap_or_default(),
value,
@@ -243,13 +265,24 @@ pub const ROOT_FIELDS: &[&str] = &[
"extendedLocation",
];
const PROPERTIES_DOT: &[u8] = b"properties.";
/// Check whether an alias short name collides with a reserved ARM root field
/// and needs a collision-safe key.
pub fn is_root_field_collision(short_name: &str, default_path: &str) -> bool {
ROOT_FIELDS
.iter()
.any(|f| f.eq_ignore_ascii_case(short_name))
&& default_path.to_ascii_lowercase().starts_with("properties.")
&& default_path.len() > PROPERTIES_DOT.len()
&& default_path
.as_bytes()
.get(..PROPERTIES_DOT.len())
.is_some_and(|prefix| {
prefix
.iter()
.zip(PROPERTIES_DOT)
.all(|(a, b)| a.to_ascii_lowercase() == *b)
})
}
/// Return a collision-safe key for an alias whose short name collides with a
@@ -314,20 +347,15 @@ fn remove_field_at_depth(obj: &mut ObjMap, array_chain: &[Vec<String>], depth: u
for elem in inner.iter_mut() {
if let Value::Object(obj_rc) = elem {
let inner_btree = Rc::make_mut(obj_rc);
remove_field_at_depth_in_btree(
inner_btree,
array_chain,
depth.saturating_add(1),
field,
);
remove_field_at_depth_obj(inner_btree, array_chain, depth.saturating_add(1), field);
}
}
}
}
/// BTreeMap-native recursion for element-level field removal.
fn remove_field_at_depth_in_btree(
btree: &mut alloc::collections::BTreeMap<Value, Value>,
/// Object-native recursion for element-level field removal.
fn remove_field_at_depth_obj(
obj: &mut Object,
array_chain: &[Vec<String>],
depth: usize,
field: &str,
@@ -336,10 +364,10 @@ fn remove_field_at_depth_in_btree(
let segments: Vec<&str> = field.split('.').collect();
if segments.len() == 1 {
if let Some(&seg) = segments.first() {
btree.remove(&Value::from(seg));
obj.remove(&Value::from(seg));
}
} else if segments.len() > 1 {
remove_at_dotted_path_in_btree(btree, &segments);
remove_at_dotted_path_obj(obj, &segments);
}
return;
};
@@ -351,12 +379,12 @@ fn remove_field_at_depth_in_btree(
let key_val = Value::from(first);
let arr_val = if nav.len() == 1 {
match btree.get_mut(&key_val) {
match obj.get_mut(&key_val) {
Some(v) => v,
None => return,
}
} else {
let mut cur: &mut Value = match btree.get_mut(&key_val) {
let mut cur: &mut Value = match obj.get_mut(&key_val) {
Some(v) => v,
None => return,
};
@@ -377,27 +405,19 @@ fn remove_field_at_depth_in_btree(
for elem in inner.iter_mut() {
if let Value::Object(obj_rc) = elem {
let inner_btree = Rc::make_mut(obj_rc);
remove_field_at_depth_in_btree(
inner_btree,
array_chain,
depth.saturating_add(1),
field,
);
remove_field_at_depth_obj(inner_btree, array_chain, depth.saturating_add(1), field);
}
}
}
}
/// Remove the leaf segment at a dotted path directly in a BTreeMap.
fn remove_at_dotted_path_in_btree(
btree: &mut alloc::collections::BTreeMap<Value, Value>,
segments: &[&str],
) {
/// Remove the leaf segment at a dotted path directly in an Object.
fn remove_at_dotted_path_obj(obj: &mut Object, segments: &[&str]) {
let Some((&leaf, parent_segs)) = segments.split_last() else {
return;
};
if parent_segs.is_empty() {
btree.remove(&Value::from(leaf));
obj.remove(&Value::from(leaf));
return;
}
@@ -405,7 +425,7 @@ fn remove_at_dotted_path_in_btree(
return;
};
let first_key = Value::from(first);
let parent_val = match btree.get_mut(&first_key) {
let parent_val = match obj.get_mut(&first_key) {
Some(v) => v,
None => return,
};

View File

@@ -18,6 +18,22 @@ use alloc::vec::Vec;
use serde::{Deserialize, Deserializer};
use crate::Rc;
// ---------------------------------------------------------------------------
// Deserialization helpers
// ---------------------------------------------------------------------------
/// Deserialize a `Vec<T>` that tolerates JSON `null` by mapping it to an
/// empty vector.
fn deserialize_null_as_empty_vec<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
Ok(Option::<Vec<T>>::deserialize(deserializer)?.unwrap_or_default())
}
// ─── Top-level response wrappers ────────────────────────────────────────────
/// ARM API response envelope: `{ "value": [...] }`
@@ -98,7 +114,10 @@ pub struct AliasEntry {
/// Versioned path entries. Empty for the vast majority of aliases that
/// have only a `defaultPath`.
#[serde(default)]
///
/// In real Azure catalog data (~97% of aliases), `az provider list` emits
/// `"paths": null` rather than an empty array.
#[serde(default, deserialize_with = "deserialize_null_as_empty_vec")]
pub paths: Vec<AliasPath>,
}
@@ -404,11 +423,13 @@ pub struct ResolvedEntry {
// ── Precomputed fields (derived at registry-load time) ──────────────
/// Whether `short_name` contains `[*]` (i.e., this is a wildcard/array alias).
pub is_wildcard: bool,
/// Pre-lowercased short name as `Rc<str>` for allocation-free common-case inserts.
pub(crate) short_name_lc: Rc<str>,
/// Precomputed `default_path.split('.').collect()` for fast ARM path navigation.
pub default_path_segments: Vec<String>,
pub(crate) default_path_segments: Vec<Rc<str>>,
/// Precomputed path segments for each versioned path, in the same order
/// as `versioned_paths`.
pub versioned_path_segments: Vec<Vec<String>>,
pub(crate) versioned_path_segments: Vec<Vec<Rc<str>>>,
}
impl ResolvedEntry {
@@ -420,10 +441,15 @@ impl ResolvedEntry {
metadata: Option<AliasPathMetadata>,
) -> Self {
let is_wildcard = short_name.contains("[*]");
let default_path_segments = default_path.split('.').map(String::from).collect();
let short_name_lc = if short_name.bytes().all(|b| !b.is_ascii_uppercase()) {
Rc::from(short_name.as_str())
} else {
Rc::from(short_name.to_ascii_lowercase())
};
let default_path_segments = default_path.split('.').map(Rc::from).collect();
let versioned_path_segments = versioned_paths
.iter()
.map(|(_, p)| p.split('.').map(String::from).collect())
.map(|(_, p)| p.split('.').map(Rc::from).collect())
.collect();
Self {
short_name,
@@ -431,6 +457,7 @@ impl ResolvedEntry {
versioned_paths,
metadata,
is_wildcard,
short_name_lc,
default_path_segments,
versioned_path_segments,
}
@@ -456,7 +483,7 @@ impl ResolvedEntry {
/// Returns the versioned segments if `api_version` matches, otherwise
/// the default segments. This avoids per-call `split('.')` for both
/// default and versioned scalar alias navigation.
pub fn select_path_segments(&self, api_version: Option<&str>) -> &[String] {
pub(crate) fn select_path_segments(&self, api_version: Option<&str>) -> &[Rc<str>] {
if let Some(ver) = api_version {
for (i, (v, _)) in self.versioned_paths.iter().enumerate() {
if v.eq_ignore_ascii_case(ver) {

View File

@@ -18,6 +18,7 @@ use crate::rvm::program::{Program, SpanInfo};
use crate::rvm::Instruction;
use crate::{Rc, Value};
use crate::languages::azure_policy::aliases::AliasRegistry;
use crate::languages::azure_policy::ast::PolicyRule;
// ---------------------------------------------------------------------------
@@ -44,10 +45,9 @@ pub(super) struct Compiler {
pub(super) cached_input_reg: Option<u8>,
/// Cached register for `LoadContext` — allocated once on first use.
pub(super) cached_context_reg: Option<u8>,
/// Map from lowercase fully-qualified alias name → short name.
pub(super) alias_map: BTreeMap<String, String>,
/// Map from lowercase fully-qualified alias name → modifiable flag.
pub(super) alias_modifiable: BTreeMap<String, bool>,
/// Alias registry for resolving fully-qualified alias names.
/// Shared via `Rc` to avoid cloning the 73K-entry alias maps.
pub(super) alias_registry: Option<Rc<AliasRegistry>>,
/// Default values for policy parameters.
pub(super) parameter_defaults: Option<Value>,
/// Cached literal-table index for `parameter_defaults` (or an empty object
@@ -338,8 +338,13 @@ impl Compiler {
path: &str,
span: &crate::lexer::Span,
) -> Result<String> {
let alias_map = match &self.alias_registry {
Some(reg) => reg.alias_map(),
None => return Ok(path.to_string()),
};
let lc = path.to_ascii_lowercase();
if let Some(short) = self.alias_map.get(&lc) {
if let Some(short) = alias_map.get(&lc) {
let resolved = short.clone();
let result = Self::strip_fq_prefix(&resolved).to_ascii_lowercase();
return Ok(result);
@@ -348,7 +353,7 @@ impl Compiler {
// Fallback: derive array path from a corresponding `[*]` alias.
if !lc.contains("[*]") {
let wildcard_key = alloc::format!("{}[*]", lc);
if let Some(short) = self.alias_map.get(&wildcard_key) {
if let Some(short) = alias_map.get(&wildcard_key) {
let resolved = Self::strip_fq_prefix(short).to_ascii_lowercase();
if let Some(base) = resolved.strip_suffix("[*]") {
return Ok(base.to_string());
@@ -356,14 +361,14 @@ impl Compiler {
}
}
if !self.alias_map.is_empty() && !self.alias_fallback_to_raw {
if !alias_map.is_empty() && !self.alias_fallback_to_raw {
bail!(span.error(&alloc::format!(
"unknown alias '{}': field references must use fully-qualified alias names when an alias catalog is loaded",
path
)));
}
if self.alias_map.is_empty() {
if alias_map.is_empty() {
Ok(path.to_string())
} else {
let result = Self::strip_fq_prefix(path).to_ascii_lowercase();

View File

@@ -998,7 +998,13 @@ impl Compiler {
return Ok(result);
}
}
Err(e) if !self.alias_map.is_empty() && !self.alias_fallback_to_raw => {
Err(e)
if self
.alias_registry
.as_ref()
.is_some_and(|r| !r.alias_map().is_empty())
&& !self.alias_fallback_to_raw =>
{
return Err(e);
}
_ => {}

View File

@@ -11,7 +11,7 @@
//! to fetch a related resource and an optional `existenceCondition` evaluated
//! inline.
use alloc::collections::BTreeMap;
use crate::value::Object;
use alloc::format;
use alloc::string::ToString as _;
use alloc::vec::Vec;
@@ -692,13 +692,18 @@ impl Compiler {
field_path: &str,
span: &crate::lexer::Span,
) -> Result<()> {
if self.alias_modifiable.is_empty() {
let modifiable_map = match &self.alias_registry {
Some(reg) => reg.alias_modifiable_map(),
None => return Ok(()),
};
if modifiable_map.is_empty() {
return Ok(());
}
let lc = field_path.to_lowercase();
if let Some(&modifiable) = self.alias_modifiable.get(&lc) {
if let Some(&modifiable) = modifiable_map.get(&lc) {
if !modifiable {
bail!(span.error(&format!(
"alias '{}' is not modifiable (defaultMetadata.attributes != 'Modifiable')",
@@ -803,26 +808,41 @@ fn unescape_arm_literal(s: &str) -> alloc::string::String {
/// 1. Build a template `BTreeMap` with `Value::Undefined` placeholders.
/// 2. Sort keys by their literal value (BTreeMap order).
/// 3. Emit `ObjectCreate`.
#[allow(clippy::indexing_slicing)]
pub(super) fn build_object_from_keys(
compiler: &mut Compiler,
mut keys: Vec<(u16, u8)>,
span: &crate::lexer::Span,
) -> Result<u8> {
// Build template: object with all keys set to Undefined.
let mut template = BTreeMap::new();
let mut template = Object::new();
for &(key_idx, _) in &keys {
// SAFETY: key_idx was just returned by `add_literal_u16`, so the
// index is guaranteed to be in bounds.
let key_val = compiler.program.literals[usize::from(key_idx)].clone();
// key_idx was returned by `add_literal_u16` in the calling code,
// so it is always in bounds. We use `.get()` + `?` instead of
// direct indexing to satisfy the crate-wide `deny(indexing_slicing)`.
let key_val = compiler
.program
.literals
.get(usize::from(key_idx))
.ok_or_else(|| {
anyhow!(
"internal error in build_object_from_keys: \
literal index {} out of bounds (literals len = {})",
key_idx,
compiler.program.literals.len()
)
})?
.clone();
template.insert(key_val, Value::Undefined);
}
let template_idx = compiler.add_literal_u16(Value::Object(crate::Rc::new(template)))?;
// Sort keys by literal value (BTreeMap order).
// Sort keys by literal value (BTreeMap order). All indices were
// validated in the loop above (which returns Err for out-of-bounds),
// so `.get()` always returns `Some` here — `None` is unreachable.
keys.sort_by(|a, b| {
compiler.program.literals[usize::from(a.0)]
.cmp(&compiler.program.literals[usize::from(b.0)])
let a_val = compiler.program.literals.get(usize::from(a.0));
let b_val = compiler.program.literals.get(usize::from(b.0));
a_val.cmp(&b_val)
});
let dest = compiler.alloc_register()?;

View File

@@ -272,7 +272,7 @@ impl Compiler {
fn insert_string_set_annotation(
annot: &mut alloc::collections::BTreeMap<String, Value>,
key: &str,
observed: &BTreeSet<String>,
observed: &alloc::collections::BTreeSet<String>,
) {
if !observed.is_empty() {
let set: BTreeSet<Value> = observed

View File

@@ -30,11 +30,11 @@ mod metadata;
mod template_dispatch;
mod utils;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString as _};
use alloc::string::ToString as _;
use anyhow::Result;
use crate::languages::azure_policy::aliases::AliasRegistry;
use crate::languages::azure_policy::ast::{PolicyDefinition, PolicyRule};
use crate::rvm::program::Program;
use crate::{Rc, Value};
@@ -69,17 +69,14 @@ pub fn compile_policy_rule(rule: &PolicyRule) -> Result<Rc<Program>> {
/// Compile a parsed Azure Policy rule with alias resolution.
///
/// The `alias_map` maps lowercase fully-qualified alias names to their short
/// names. Obtain it from
/// [`AliasRegistry::alias_map()`](crate::languages::azure_policy::aliases::AliasRegistry::alias_map).
/// The registry provides alias-to-short-name resolution and modifiability
/// data. Pass it as an `Rc` to avoid cloning the internal alias maps.
pub fn compile_policy_rule_with_aliases(
rule: &PolicyRule,
alias_map: BTreeMap<String, String>,
alias_modifiable: BTreeMap<String, bool>,
registry: Rc<AliasRegistry>,
) -> Result<Rc<Program>> {
let mut compiler = Compiler::new();
compiler.alias_map = alias_map;
compiler.alias_modifiable = alias_modifiable;
compiler.alias_registry = Some(registry);
init_effect_annotation(&mut compiler, rule);
compiler.compile(rule)
}
@@ -100,12 +97,10 @@ pub fn compile_policy_definition(defn: &PolicyDefinition) -> Result<Rc<Program>>
/// Compile a parsed Azure Policy definition with alias resolution.
pub fn compile_policy_definition_with_aliases(
defn: &PolicyDefinition,
alias_map: BTreeMap<String, String>,
alias_modifiable: BTreeMap<String, bool>,
registry: Rc<AliasRegistry>,
) -> Result<Rc<Program>> {
let mut compiler = Compiler::new();
compiler.alias_map = alias_map;
compiler.alias_modifiable = alias_modifiable;
compiler.alias_registry = Some(registry);
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
compiler.populate_definition_metadata(defn);
init_effect_annotation(&mut compiler, &defn.policy_rule);
@@ -119,13 +114,11 @@ pub fn compile_policy_definition_with_aliases(
/// a known alias are silently treated as raw property paths.
pub fn compile_policy_definition_with_aliases_opts(
defn: &PolicyDefinition,
alias_map: BTreeMap<String, String>,
alias_modifiable: BTreeMap<String, bool>,
registry: Rc<AliasRegistry>,
alias_fallback_to_raw: bool,
) -> Result<Rc<Program>> {
let mut compiler = Compiler::new();
compiler.alias_map = alias_map;
compiler.alias_modifiable = alias_modifiable;
compiler.alias_registry = Some(registry);
compiler.alias_fallback_to_raw = alias_fallback_to_raw;
compiler.parameter_defaults = Some(build_parameter_defaults(&defn.parameters)?);
compiler.populate_definition_metadata(defn);

View File

@@ -264,18 +264,17 @@ impl<'source> Parser<'source> {
"metadata" => {
*metadata = Some(self.parse_json_value()?);
}
"parameters" => {
"parameters" if self.token_text() == "{" => {
// Parameters must be a JSON object; if not, push to extra.
if self.token_text() == "{" {
*parameters = self.parse_parameter_definitions()?;
} else {
let value = self.parse_json_value()?;
extra.push(ObjectEntry {
key_span,
key: key.into(),
value,
});
}
*parameters = self.parse_parameter_definitions()?;
}
"parameters" => {
let value = self.parse_json_value()?;
extra.push(ObjectEntry {
key_span,
key: key.into(),
value,
});
}
"policyrule" => {
// Parse the policyRule directly from the token stream!

View File

@@ -63,6 +63,14 @@ pub enum CompilerError {
#[error("Invalid function expression with package")]
InvalidFunctionExpressionWithPackage,
#[error("partial object rules with constant keys are not yet supported by the RVM compiler")]
PartialObjectConstantKeyUnsupported,
#[error(
"partial object rules with nested bracket keys are not yet supported by the RVM compiler"
)]
PartialObjectNestedKeyUnsupported,
#[error("Compilation error: {message}")]
General { message: String },
}

View File

@@ -11,8 +11,9 @@ use crate::ast::{Expr, ExprRef};
use crate::lexer::Span;
use crate::rvm::instructions::{ArrayCreateParams, ObjectCreateParams, SetCreateParams};
use crate::rvm::Instruction;
use crate::value::Object;
use crate::{Rc, Value};
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
/// Try to evaluate an expression as a compile-time constant.
@@ -43,7 +44,7 @@ pub(in crate::languages::rego::compiler) fn try_eval_const(expr: &Expr) -> Optio
Expr::Object { fields, .. } => fields
.iter()
.map(|(_, k, v)| Some((try_eval_const(k.as_ref())?, try_eval_const(v.as_ref())?)))
.collect::<Option<BTreeMap<_, _>>>()
.collect::<Option<Object>>()
.map(|m| Value::Object(Rc::new(m))),
_ => None,
}
@@ -117,7 +118,7 @@ impl<'a> Compiler<'a> {
fields: &[(crate::lexer::Span, ExprRef, ExprRef)],
span: &Span,
) -> Result<Register> {
let all_const: Option<BTreeMap<_, _>> = fields
let all_const: Option<Object> = fields
.iter()
.map(|(_, k, v)| Some((try_eval_const(k.as_ref())?, try_eval_const(v.as_ref())?)))
.collect();
@@ -166,7 +167,7 @@ impl<'a> Compiler<'a> {
let mut template_keys = literal_keys.clone();
template_keys.sort();
let mut template_obj = BTreeMap::new();
let mut template_obj = Object::new();
for key in &template_keys {
template_obj.insert(key.clone(), Value::Undefined);
}

View File

@@ -17,8 +17,20 @@ use crate::lexer::Span;
use crate::rvm::instructions::{BuiltinCallParams, FunctionCallParams};
use crate::rvm::Instruction;
use crate::utils::get_path_string;
use alloc::{format, string::ToString, vec::Vec};
use crate::value::Value;
use alloc::{
format,
string::{String, ToString},
vec::Vec,
};
/// Resolved destination of a Rego function-call expression. Produced by
/// [`Compiler::determine_call_target`] and consumed by
/// [`Compiler::compile_function_call`] to choose which instruction to emit.
/// Carrying the discrimination in the type (rather than re-matching on a
/// magic name at the emit site) keeps the host-await handling honest under
/// future refactors — the compiler will refuse to build if a new variant is
/// added without updating every match site.
enum CallTarget {
User {
rule_index: u16,
@@ -28,9 +40,14 @@ enum CallTarget {
builtin_index: u16,
expected_args: Option<usize>,
},
HostAwait {
expected_args: Option<usize>,
},
/// Explicit `__builtin_host_await(arg, id)` call form (2 user args).
/// The identifier is supplied by the policy author at runtime via the
/// second argument register.
ExplicitHostAwait,
/// A registered host-awaitable builtin invoked by its registered name
/// (1 user arg). The identifier is the registered name itself and is
/// baked into the bytecode as a string literal at compile time.
RegisteredHostAwait { identifier: String },
}
impl<'a> Compiler<'a> {
@@ -59,7 +76,11 @@ impl<'a> Compiler<'a> {
let expected_args = match &call_target {
CallTarget::User { expected_args, .. } => *expected_args,
CallTarget::Builtin { expected_args, .. } => *expected_args,
CallTarget::HostAwait { expected_args } => *expected_args,
// Both host-await variants have a known fixed arity; carrying it
// in the variant lets the rest of the compiler depend on the type
// rather than re-matching on the magic name `__builtin_host_await`.
CallTarget::ExplicitHostAwait => Some(2),
CallTarget::RegisteredHostAwait { .. } => Some(1),
};
if let Some(expected) = expected_args {
@@ -126,7 +147,8 @@ impl<'a> Compiler<'a> {
});
self.emit_instruction(Instruction::BuiltinCall { params_index }, &span);
}
CallTarget::HostAwait { .. } => {
CallTarget::ExplicitHostAwait => {
// Explicit __builtin_host_await(arg, id) — 2 arguments
if arg_regs.len() != 2 {
return Err(CompilerError::General {
message: format!(
@@ -136,7 +158,6 @@ impl<'a> Compiler<'a> {
}
.at(&span));
}
self.emit_instruction(
Instruction::HostAwait {
dest,
@@ -146,6 +167,37 @@ impl<'a> Compiler<'a> {
&span,
);
}
CallTarget::RegisteredHostAwait { identifier } => {
// Registered host-awaitable builtin — the identifier is the
// registered name and is baked into the bytecode as a literal.
if arg_regs.len() != 1 {
return Err(CompilerError::General {
message: format!(
"host-awaitable builtin '{}' expects exactly 1 argument, got {}",
identifier,
arg_regs.len()
),
}
.at(&span));
}
let id_reg = self.alloc_register();
let literal_idx = self.add_literal(Value::String(identifier.into()));
self.emit_instruction(
Instruction::Load {
dest: id_reg,
literal_idx,
},
&span,
);
self.emit_instruction(
Instruction::HostAwait {
dest,
arg: arg_regs[0],
id: id_reg,
},
&span,
);
}
}
if let Some((plan, plan_span)) = &out_param_plan {
@@ -187,8 +239,26 @@ impl<'a> Compiler<'a> {
span: &Span,
) -> Result<CallTarget> {
if original_fcn_path == "__builtin_host_await" {
return Ok(CallTarget::HostAwait {
expected_args: Some(2),
return Ok(CallTarget::ExplicitHostAwait);
}
// Check registered host-awaitable builtins. Registered builtins are
// restricted to arg_count == 1 at registration time (see
// `Compiler::register_host_await_builtin`), so the variant doesn't
// need to carry an arity — it's fixed at 1.
//
// We deliberately match against `original_fcn_path` only, not
// `full_fcn_path`. Registration intercepts the *unqualified* call
// form (e.g. `lookup(x)` inside the policy's own package). A
// package-qualified call like `data.other.lookup(x)` is left to
// resolve through the normal user-defined / builtin path, so a
// registered name does not leak into unrelated packages that
// happen to expose a rule with the same identifier. This is
// documented on `register_host_await_builtin`; the
// `registered_host_await.yaml` suite pins the behavior.
if self.host_await_builtins.contains_key(original_fcn_path) {
return Ok(CallTarget::RegisteredHostAwait {
identifier: original_fcn_path.to_string(),
});
}

View File

@@ -13,7 +13,7 @@ use crate::ast::{self, ExprRef, LiteralStmt, Query};
use crate::compiler::destructuring_planner::plans::BindingPlan;
use crate::compiler::hoist::{HoistedLoop, LoopType};
use crate::lexer::Span;
use crate::rvm::instructions::{LoopMode, LoopStartParams};
use crate::rvm::instructions::{GuardMode, LoopMode, LoopStartParams};
use crate::rvm::Instruction;
use crate::Value;
use alloc::format;
@@ -197,6 +197,19 @@ impl<'a> Compiler<'a> {
*end = loop_end;
}
// The loop writes its overall pass/fail into `result_reg`
// (`success_count == total_iterations` for `Every`). The enclosing query
// must fail (evaluate to undefined) when the quantifier does not hold, so
// guard on `result_reg` here. Without this the `every` result is computed
// but discarded, leaving the surrounding rule to always succeed.
self.emit_instruction(
Instruction::Guard {
register: result_reg,
mode: GuardMode::Condition,
},
span,
);
Ok(())
}
@@ -312,6 +325,25 @@ impl<'a> Compiler<'a> {
*end = loop_end;
}
// A hoisted index-iteration loop inside an `every` body acts as a
// condition on the current iteration: if the indexed reference matches
// nothing the iteration must fail. The `every` body emits no context
// yield, so the loop result register is otherwise discarded (same
// situation as `some ... in`). Guard on it so a non-matching indexed
// reference fails the enclosing `every` iteration.
if matches!(
self.context_stack.last().map(|c| &c.context_type),
Some(ContextType::Every)
) {
self.emit_instruction(
Instruction::Guard {
register: result_reg,
mode: GuardMode::Condition,
},
collection.span(),
);
}
Ok(())
}

View File

@@ -26,7 +26,9 @@ use crate::rvm::program::{Program, RuleType, SpanInfo};
use crate::CompiledPolicy;
use crate::Value;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::format;
use alloc::string::String;
use alloc::string::ToString as _;
use alloc::vec;
use alloc::vec::Vec;
use indexmap::IndexMap;
@@ -139,6 +141,10 @@ pub struct Compiler<'a> {
current_call_stack: Vec<u16>,
entry_points: IndexMap<String, usize>,
soft_assert_mode: bool,
/// Registered host-awaitable builtins: name → expected arg count.
/// When the compiler encounters a call to one of these names, it emits a
/// `HostAwait` instruction instead of a regular function or builtin call.
host_await_builtins: BTreeMap<String, usize>,
}
impl<'a> Compiler<'a> {
@@ -173,9 +179,75 @@ impl<'a> Compiler<'a> {
current_call_stack: Vec::new(),
entry_points: IndexMap::new(),
soft_assert_mode: false,
host_await_builtins: BTreeMap::new(),
}
}
/// Register a function name as a host-awaitable builtin.
///
/// When the compiler encounters an **unqualified** call to `name(arg)`
/// (i.e. `name(arg)` from inside the policy's own package, not
/// `data.pkg.name(arg)` or any other package-qualified form), it will
/// emit a `HostAwait` instruction with the argument and `name` as the
/// identifier, instead of treating it as a user-defined or standard
/// builtin function.
///
/// Package-qualified calls (e.g. `data.other.name(arg)`) are **not**
/// intercepted by registration. Those resolve through the normal
/// user-defined / builtin lookup against their fully-qualified path
/// (`data.other.name`).
///
/// `arg_count` must be exactly 1. The `HostAwait` instruction carries a
/// single argument register; use object packing to pass multiple values
/// (e.g. `name({"key1": v1, "key2": v2})`).
///
/// Returns `Err` when:
/// - `name` is the reserved identifier `__builtin_host_await`,
/// - `name` is empty, only whitespace, or has leading/trailing
/// whitespace (whitespace-padded names would never match the
/// trimmed identifier produced by the Rego parser, creating dead
/// registrations),
/// - `name` is already registered (duplicate registration is rejected
/// rather than silently overwritten),
/// - `arg_count` is not exactly 1.
pub fn register_host_await_builtin(&mut self, name: &str, arg_count: usize) -> Result<()> {
if name == "__builtin_host_await" {
return Err(CompilerError::General {
message: "__builtin_host_await is a reserved name and cannot be registered as a host-await builtin"
.to_string(),
}
.into());
}
if name.is_empty() || name != name.trim() {
return Err(CompilerError::General {
message: format!(
"host-await builtin name {name:?} must not be empty or contain leading/trailing whitespace"
),
}
.into());
}
if self.host_await_builtins.contains_key(name) {
return Err(CompilerError::General {
message: format!(
"host-await builtin '{name}' is already registered; \
duplicate registration is not allowed"
),
}
.into());
}
if arg_count != 1 {
return Err(CompilerError::General {
message: format!(
"registered host-await builtin '{name}' must have arg_count == 1, got {arg_count}. \
Use object packing to pass multiple values."
),
}
.into());
}
self.host_await_builtins.insert(name.to_string(), arg_count);
Ok(())
}
pub(super) fn with_soft_assert_mode<F, R>(&mut self, enabled: bool, f: F) -> R
where
F: FnOnce(&mut Self) -> R,

View File

@@ -70,12 +70,31 @@ impl<'a> Compiler<'a> {
..
} = &stmt.literal
{
self.compile_some_in_loop_with_remaining_statements(
let some_result_reg = self.compile_some_in_loop_with_remaining_statements(
key,
value,
collection,
&stmts[idx..],
)?;
// Inside an `every` body a `some ... in` acts as a condition
// on the current iteration: if it matches nothing the
// iteration must fail. Unlike a top-level rule body (where
// per-iteration context yields produce the results), the
// `every` body has no yield, so the loop result register is
// otherwise discarded. Guard on it so a `some` that matches
// nothing fails the enclosing `every` iteration.
if matches!(
self.context_stack.last().map(|c| &c.context_type),
Some(ContextType::Every)
) {
self.emit_instruction(
Instruction::Guard {
register: some_result_reg,
mode: GuardMode::Condition,
},
&stmt.span,
);
}
return Ok(());
}
}

View File

@@ -59,7 +59,7 @@ impl<'a> Compiler<'a> {
crate::ast::Expr::RefBrack { .. } if assign.is_some() => {
RuleType::PartialObject
}
crate::ast::Expr::RefBrack { .. } => RuleType::PartialSet,
crate::ast::Expr::RefBrack { .. } => RuleType::PartialObject,
_ => RuleType::Complete,
},
_ => RuleType::Complete,
@@ -88,6 +88,54 @@ impl<'a> Compiler<'a> {
})
}
fn validate_partial_object_shape(&self, refr: &ExprRef) -> Result<()> {
let Expr::RefBrack {
refr: prefix,
index,
..
} = refr.as_ref()
else {
return Ok(());
};
if Self::has_unsupported_bracket_prefix(prefix) {
return Err(CompilerError::PartialObjectNestedKeyUnsupported.at(refr.span()));
}
if Self::is_simple_literal(index) {
return Err(CompilerError::PartialObjectConstantKeyUnsupported.at(index.span()));
}
Ok(())
}
fn has_unsupported_bracket_prefix(expr: &ExprRef) -> bool {
match expr.as_ref() {
Expr::RefBrack { refr, index, .. } => {
!Self::is_string_literal(index) || Self::has_unsupported_bracket_prefix(refr)
}
Expr::RefDot { refr, .. } => Self::has_unsupported_bracket_prefix(refr),
_ => false,
}
}
fn is_string_literal(expr: &ExprRef) -> bool {
matches!(expr.as_ref(), Expr::String { .. } | Expr::RawString { .. })
}
fn is_simple_literal(expr: &ExprRef) -> bool {
match expr.as_ref() {
Expr::String { .. }
| Expr::RawString { .. }
| Expr::Number { .. }
| Expr::Bool { .. }
| Expr::Null { .. } => true,
// Unary expressions like `-1` are constant literals too.
Expr::UnaryExpr { expr, .. } => Self::is_simple_literal(expr),
_ => false,
}
}
pub(super) fn get_or_assign_rule_index(&mut self, rule_path: &str) -> Result<u16> {
if let Some(&index) = self.rule_index_map.get(rule_path) {
return Ok(index);
@@ -186,8 +234,20 @@ impl<'a> Compiler<'a> {
pub fn compile_from_policy(
policy: &CompiledPolicy,
entry_points: &[&str],
) -> Result<Arc<Program>> {
Self::compile_from_policy_with_host_await(policy, entry_points, &[])
}
/// Compile from a CompiledPolicy to RVM Program with registered host-awaitable builtins.
pub fn compile_from_policy_with_host_await(
policy: &CompiledPolicy,
entry_points: &[&str],
host_await_builtins: &[(&str, usize)],
) -> Result<Arc<Program>> {
let mut compiler = Compiler::with_policy(policy);
for &(name, arg_count) in host_await_builtins {
compiler.register_host_await_builtin(name, arg_count)?;
}
compiler.current_rule_path = "".to_string();
let rules = policy.get_rules();
@@ -345,6 +405,10 @@ impl<'a> Compiler<'a> {
let (key_expr, value_expr) = match head {
RuleHead::Compr { refr, assign, .. } => {
if rule_type == RuleType::PartialObject {
self.validate_partial_object_shape(refr)?;
}
self.rule_definition_function_params[rule_index as usize].push(None);
self.rule_definition_destructuring_patterns[rule_index as usize]
.push(None);

View File

@@ -155,7 +155,7 @@ pub mod target;
#[cfg(any(test, all(feature = "yaml", feature = "std")))]
pub mod test_utils;
pub mod utils;
mod value;
pub mod value;
#[cfg(feature = "azure_policy")]
pub use {
@@ -205,10 +205,10 @@ pub use alloc::sync::Arc as Rc;
pub use alloc::rc::Rc;
#[cfg(feature = "std")]
use std::collections::{hash_map::Entry as MapEntry, HashMap as Map, HashSet as Set};
use std::collections::{hash_map::Entry as MapEntry, HashMap as Map, HashSet as MapSet};
#[cfg(not(feature = "std"))]
use alloc::collections::{btree_map::Entry as MapEntry, BTreeMap as Map, BTreeSet as Set};
use alloc::collections::{btree_map::Entry as MapEntry, BTreeMap as Map, BTreeSet as MapSet};
use alloc::{
borrow::ToOwned as _,

View File

@@ -354,6 +354,33 @@ impl<'source> Parser<'source> {
}
}
/// Parse a field name after `.` in a ref expression.
///
/// Unlike [`Self::parse_var`] and [`Self::parse_ident`], this method accepts **any**
/// `TokenKind::Ident` token, including reserved keywords (e.g. `as`, `default`, `else`,
/// `false`, `if`, `import`, `in`, `not`, `null`, `package`, `some`, `true`, `with`).
///
/// The position immediately after `.` is unambiguously a field name, so there is no
/// syntactic ambiguity with statement-level keywords. This matches OPA's
/// `keywords_in_refs` capability, which is enabled by default in standard OPA builds.
///
/// # Example
/// ```rego
/// allow if { input.v0.package.format == "npm" } # `package` is a keyword but valid here
/// ```
fn parse_ref_field(&mut self) -> Result<Span> {
let span = self.tok.1.clone();
match self.tok.0 {
TokenKind::Ident => {
self.next_token()?;
Ok(span)
}
_ => Err(self
.source
.error(self.tok.1.line, self.tok.1.col, "expecting identifier")),
}
}
fn read_number(&mut self, span: Span) -> Result<Expr> {
match Number::from_str(span.text()) {
Ok(v) => Ok(Expr::Number {
@@ -743,9 +770,10 @@ impl<'source> Parser<'source> {
);
}
"." => {
// Read identifier.
// Read identifier. Keywords are allowed as field names in
// dot-notation refs (e.g. `input.package.name`).
self.next_token()?;
let field = self.parse_var()?;
let field = self.parse_ref_field()?;
span.end = self.end;
// Disallow any whitespace between . and identifier.
@@ -1418,9 +1446,10 @@ impl<'source> Parser<'source> {
);
}
"." => {
// Read identifier.
// Read identifier. Keywords are allowed as field names in
// dot-notation refs (e.g. `import data.my.package`).
self.next_token()?;
let field = self.parse_ident()?;
let field = self.parse_ref_field()?;
span.end = self.end;
// Disallow any whitespace between . and identifier.
@@ -1523,7 +1552,8 @@ impl<'source> Parser<'source> {
"." => {
let sep_pos = self.tok.1.start;
self.next_token()?;
let field = self.parse_var()?;
// Keywords are allowed as field names in dot-notation refs.
let field = self.parse_ref_field()?;
span.end = self.end;
// Disallow any whitespace between . and identifier.

View File

@@ -11,9 +11,9 @@
//! values are converted through [`MetadataValue`] — a postcard/bincode-safe
//! enum that avoids `deserialize_any`.
use crate::value::Object;
use crate::Rc;
use alloc::collections::BTreeMap;
use alloc::collections::BTreeSet;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
@@ -52,7 +52,7 @@ impl ProgramMetadata {
pub fn to_value(&self) -> crate::value::Value {
use crate::value::Value;
let mut obj = BTreeMap::new();
let mut obj = Object::new();
obj.insert(
Value::String("compiler_version".into()),
Value::String(self.compiler_version.as_str().into()),
@@ -75,7 +75,7 @@ impl ProgramMetadata {
);
if !self.annotations.is_empty() {
let mut annotations_obj = BTreeMap::new();
let mut annotations_obj = Object::new();
for (k, v) in &self.annotations {
annotations_obj.insert(Value::String(k.as_str().into()), v.clone());
}
@@ -198,7 +198,7 @@ impl MetadataValue {
match *self {
MetadataValue::String(ref s) => Value::String(s.as_str().into()),
MetadataValue::StringSet(ref set) => {
let mut bset = alloc::collections::BTreeSet::new();
let mut bset = BTreeSet::new();
for s in set {
bset.insert(Value::String(s.as_str().into()));
}
@@ -211,7 +211,7 @@ impl MetadataValue {
Value::Array(Rc::new(values))
}
MetadataValue::Map(ref map) => {
let mut obj = BTreeMap::new();
let mut obj = Object::new();
for (k, v) in map {
obj.insert(Value::String(k.as_str().into()), v.to_value());
}
@@ -257,7 +257,6 @@ mod metadata_serde {
mod tests {
use super::*;
use crate::value::Value;
use alloc::collections::BTreeSet;
/// Round-trip: Value → MetadataValue → Value must be equivalent for
/// all lossless variants (strings, bools, integers, arrays, objects).

View File

@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::collections::BTreeSet;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
@@ -11,6 +11,7 @@ use serde::ser::{SerializeSeq as _, SerializeTuple as _};
use serde::{Deserialize, Serialize};
use crate::number::Number;
use crate::value::Object;
use crate::value::Value;
const VARIANT_NULL: u32 = 0;
@@ -132,7 +133,7 @@ impl<'a> Serialize for BinarySetRef<'a> {
}
}
struct BinaryObjectRef<'a>(&'a BTreeMap<Value, Value>);
struct BinaryObjectRef<'a>(&'a Object);
impl<'a> Serialize for BinaryObjectRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@@ -140,7 +141,7 @@ impl<'a> Serialize for BinaryObjectRef<'a> {
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for (key, value) in self.0.iter() {
for (key, value) in self.0.iter_sorted() {
seq.serialize_element(&BinaryEntryRef(key, value))?;
}
seq.end()
@@ -261,11 +262,11 @@ impl<'de> Visitor<'de> for BinaryValueVisitor {
}
(BinaryVariant::Object, variant) => {
let entries: Vec<(BinaryValue, BinaryValue)> = variant.newtype_variant()?;
let mut map = BTreeMap::new();
let mut map = Object::new();
for (key, value) in entries {
map.insert(key.into_value(), value.into_value());
}
Ok(BinaryValue(Value::from(map)))
Ok(BinaryValue(Value::Object(crate::Rc::new(map))))
}
(BinaryVariant::Undefined, variant) => {
variant.unit_variant()?;

View File

@@ -6,8 +6,6 @@
// Disable both to keep patterns consistent within this file.
#![allow(clippy::pattern_type_mismatch, clippy::needless_borrowed_reference)]
use alloc::collections::BTreeSet;
use crate::number::Number;
use crate::value::Value;
@@ -32,8 +30,9 @@ impl RegoVM {
match (a, b) {
(&Value::Number(ref x), &Value::Number(ref y)) => Ok(Value::from(x.sub(y)?)),
(&Value::Set(ref left), &Value::Set(ref right)) => {
let diff: BTreeSet<Value> = left.difference(right).cloned().collect();
Ok(Value::from_set(diff))
let diff: alloc::collections::BTreeSet<Value> =
left.difference(right).cloned().collect();
Ok(Value::from(diff))
}
_ => Err(VmError::InvalidSubtraction {
left: a.clone(),

View File

@@ -2,9 +2,9 @@
// Licensed under the MIT License.
use crate::rvm::instructions::{ComprehensionBeginParams, ComprehensionMode};
use crate::value::Object;
use crate::value::Value;
use crate::Rc;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::sync::Arc;
use alloc::vec::Vec;
@@ -34,12 +34,12 @@ impl RegoVM {
let initial_result = match params.mode {
ComprehensionMode::Set => Value::new_set(),
ComprehensionMode::Array => Value::new_array(),
ComprehensionMode::Object => Value::Object(Rc::new(BTreeMap::new())),
ComprehensionMode::Object => Value::Object(Rc::new(Object::new())),
};
self.set_register(params.result_reg, initial_result.clone())?;
let auto_iterate = params.collection_reg != params.result_reg;
let iteration_state = if auto_iterate {
let mut iteration_state = if auto_iterate {
let source_value = self.get_register(params.collection_reg)?.clone();
match source_value {
Value::Array(items) => {
@@ -53,11 +53,9 @@ impl RegoVM {
if obj.is_empty() {
None
} else {
Some(IterationState::Object {
obj,
current_key: None,
first_iteration: true,
})
// O(1) cursor over shared Rc<Object>.
let cursor = obj.cursor();
Some(IterationState::Object { obj, cursor })
}
}
Value::Set(set) => {
@@ -79,7 +77,7 @@ impl RegoVM {
None
};
let has_iteration = if let Some(state) = iteration_state.as_ref() {
let has_iteration = if let Some(state) = iteration_state.as_mut() {
self.setup_next_iteration(state, params.key_reg, params.value_reg)?
} else {
false
@@ -123,12 +121,12 @@ impl RegoVM {
let initial_result = match params.mode {
ComprehensionMode::Set => Value::new_set(),
ComprehensionMode::Array => Value::new_array(),
ComprehensionMode::Object => Value::Object(Rc::new(BTreeMap::new())),
ComprehensionMode::Object => Value::Object(Rc::new(Object::new())),
};
self.set_register(params.result_reg, initial_result.clone())?;
let auto_iterate = params.collection_reg != params.result_reg;
let iteration_state = if auto_iterate {
let mut iteration_state = if auto_iterate {
let source_value = self.get_register(params.collection_reg)?.clone();
match source_value {
Value::Array(items) => {
@@ -142,11 +140,8 @@ impl RegoVM {
if obj.is_empty() {
None
} else {
Some(IterationState::Object {
obj,
current_key: None,
first_iteration: true,
})
let cursor = obj.cursor();
Some(IterationState::Object { obj, cursor })
}
}
Value::Set(set) => {
@@ -168,7 +163,7 @@ impl RegoVM {
None
};
let has_iteration = if let Some(state) = iteration_state.as_ref() {
let has_iteration = if let Some(state) = iteration_state.as_mut() {
self.setup_next_iteration(state, params.key_reg, params.value_reg)?
} else {
false
@@ -255,6 +250,21 @@ impl RegoVM {
};
let result_reg = comprehension_context.result_reg;
// Snapshot the iteration value register BEFORE taking the result
// register: if the comprehension compiler ever allocates
// `result_reg == context.value_reg`, the writeback at the bottom
// of this function would clobber the value register, and a
// post-writeback read here would feed the wrong value into
// `IterationState::Set::current_item`. Only Set needs the snapshot
// (Object uses a self-advancing cursor; Array advances by index).
let set_resume_snapshot = if matches!(
comprehension_context.iteration_state,
Some(IterationState::Set { .. })
) {
Some(self.get_register(comprehension_context.value_reg)?.clone())
} else {
None
};
// Take ownership of the result register so Rc refcount stays at 1,
// allowing Rc::make_mut to mutate in-place instead of deep-cloning.
let mut current_result = self.take_register(result_reg)?;
@@ -292,29 +302,16 @@ impl RegoVM {
self.set_register(result_reg, current_result)?;
if let Some(iter_state) = comprehension_context.iteration_state.as_mut() {
match *iter_state {
IterationState::Object {
ref mut current_key,
..
} => {
let tracked_key =
if comprehension_context.key_reg != comprehension_context.value_reg {
self.get_register(comprehension_context.key_reg)?.clone()
} else {
self.get_register(comprehension_context.value_reg)?.clone()
};
*current_key = Some(tracked_key);
}
IterationState::Set {
ref mut current_item,
..
} => {
*current_item =
Some(self.get_register(comprehension_context.value_reg)?.clone());
}
IterationState::Array { .. } | IterationState::Single { .. } => {}
// Set's `Bound::Excluded(current_item)` resume scheme needs the
// pre-mutation snapshot taken at the top of this function.
// Object uses a self-advancing cursor and needs no snapshot.
if let IterationState::Set {
ref mut current_item,
..
} = *iter_state
{
*current_item = set_resume_snapshot;
}
iter_state.advance();
let has_next = self.setup_next_iteration(
iter_state,
@@ -359,8 +356,7 @@ impl RegoVM {
result_reg_idx,
key_reg_idx,
value_reg_idx,
iteration_key,
iteration_value,
iter_is_set,
) = {
let frame =
self.execution_stack
@@ -382,8 +378,8 @@ impl RegoVM {
let result_reg_idx = context.result_reg;
let mode = context.mode.clone();
let iteration_key = self.get_register(context.key_reg)?.clone();
let iteration_value = self.get_register(context.value_reg)?.clone();
let iter_is_set =
matches!(context.iteration_state, Some(IterationState::Set { .. }));
(
value_to_add,
@@ -392,8 +388,7 @@ impl RegoVM {
result_reg_idx,
context.key_reg,
context.value_reg,
iteration_key,
iteration_value,
iter_is_set,
)
} else {
return Err(VmError::InvalidIteration {
@@ -403,6 +398,18 @@ impl RegoVM {
}
};
// Snapshot the iteration value register BEFORE the result writeback:
// if the compiler ever allocates `result_reg == value_reg_idx`, a
// post-writeback read would feed the result accumulator into
// `IterationState::Set::current_item`, breaking the next iteration.
// Only Set needs this (Object cursor self-advances; Array advances
// by index).
let set_resume_snapshot = if iter_is_set {
Some(self.get_register(value_reg_idx)?.clone())
} else {
None
};
// Take ownership of the result register so Rc refcount stays at 1,
// allowing Rc::make_mut to mutate in-place instead of deep-cloning.
let mut current_result = self.take_register(result_reg_idx)?;
@@ -450,27 +457,13 @@ impl RegoVM {
} = &mut frame.kind
{
if let Some(iter_state) = context.iteration_state.as_mut() {
match *iter_state {
IterationState::Object {
ref mut current_key,
..
} => {
let tracked_key = if context.key_reg != context.value_reg {
iteration_key.clone()
} else {
iteration_value.clone()
};
*current_key = Some(tracked_key);
}
IterationState::Set {
ref mut current_item,
..
} => {
*current_item = Some(iteration_value.clone());
}
IterationState::Array { .. } | IterationState::Single { .. } => {}
if let IterationState::Set {
ref mut current_item,
..
} = *iter_state
{
*current_item = set_resume_snapshot;
}
iter_state.advance();
}
@@ -487,8 +480,21 @@ impl RegoVM {
}
};
if let Some(state) = iteration_state_snapshot.as_ref() {
let has_next = self.setup_next_iteration(state, key_reg_idx, value_reg_idx)?;
if let Some(mut state) = iteration_state_snapshot {
let has_next = self.setup_next_iteration(&mut state, key_reg_idx, value_reg_idx)?;
// `setup_next_iteration` advances Object's internal cursor; the
// owning frame holds the iteration_state, so we must write the
// updated state back. (The Array/Set variants are also unchanged
// by copy, so the writeback is uniform.)
if let Some(frame) = self.execution_stack.get_mut(comprehension_index) {
if let FrameKind::Comprehension {
ref mut context, ..
} = frame.kind
{
context.iteration_state = Some(state);
}
}
if has_next {
if let Some(frame) = self.execution_stack.get_mut(comprehension_index) {
@@ -528,7 +534,6 @@ impl RegoVM {
Ok(false)
}
}
pub(super) fn handle_comprehension_condition_failure_suspendable(&mut self) -> Result<bool> {
if let Some(mut frame) = self.execution_stack.pop() {
let handled = if let &mut FrameKind::Comprehension {
@@ -554,11 +559,16 @@ impl RegoVM {
context: &mut ComprehensionContext,
) -> Result<()> {
if let Some(iter_state) = context.iteration_state.as_mut() {
self.capture_comprehension_iteration_position(
iter_state,
context.key_reg,
context.value_reg,
)?;
// Snapshot the current value into Set's `current_item` so the
// next iteration can resume from `Bound::Excluded(current)`.
// Object uses a self-advancing cursor and needs no snapshot here.
if let IterationState::Set {
ref mut current_item,
..
} = *iter_state
{
*current_item = Some(self.get_register(context.value_reg)?.clone());
}
iter_state.advance();
let has_next =
self.setup_next_iteration(iter_state, context.key_reg, context.value_reg)?;
@@ -575,37 +585,10 @@ impl RegoVM {
Ok(())
}
fn capture_comprehension_iteration_position(
&mut self,
iter_state: &mut IterationState,
key_reg: u8,
value_reg: u8,
) -> Result<()> {
match *iter_state {
IterationState::Object {
ref mut current_key,
..
} => {
let tracked_key = if key_reg != value_reg {
self.get_register(key_reg)?.clone()
} else {
self.get_register(value_reg)?.clone()
};
*current_key = Some(tracked_key);
}
IterationState::Set {
ref mut current_item,
..
} => {
*current_item = Some(self.get_register(value_reg)?.clone());
}
IterationState::Array { .. } | IterationState::Single { .. } => {}
}
Ok(())
}
fn execute_comprehension_end_run_to_completion(&mut self) -> Result<()> {
// `ComprehensionEnd` is reached from a loaded program; an empty stack
// here means malformed user-supplied bytecode, which must still surface
// as a typed error rather than a panic — including in debug builds.
self.comprehension_stack.pop().map_or_else(
|| {
Err(VmError::InvalidIteration {

View File

@@ -3,8 +3,9 @@
use crate::rvm::instructions::{ComprehensionMode, LoopMode};
use crate::value::Value;
use crate::value::{Object, ObjectCursor};
use crate::Rc;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
/// Loop execution context for managing iteration state
@@ -24,7 +25,18 @@ pub struct LoopContext {
pub current_iteration_failed: bool, // Track if current iteration had condition failures
}
/// Iterator state for different collection types
/// Iterator state for different collection types.
///
/// Snapshot independence for `Object` is provided by the shared
/// `Rc<Object>` — `Rc::make_mut` on an aliased Rc allocates a new
/// collection, leaving the iterator's Rc pointing at the original
/// pre-mutation state. The `ObjectCursor` is opaque and resumes in
/// O(log n) for the BTree backend.
///
/// `Set` continues to use the pre-existing snapshot-by-cloned-key
/// approach (`current_item` + `first_iteration`); migration of `Set`
/// to a cursor-based iterator ships with the `Set` storage abstraction
/// in a follow-up PR.
#[derive(Debug, Clone)]
pub enum IterationState {
Array {
@@ -32,9 +44,8 @@ pub enum IterationState {
index: usize,
},
Object {
obj: Rc<BTreeMap<Value, Value>>,
current_key: Option<Value>,
first_iteration: bool,
obj: Rc<Object>,
cursor: ObjectCursor,
},
Set {
items: Rc<BTreeSet<Value>>,
@@ -54,13 +65,21 @@ impl IterationState {
pub(super) const fn advance(&mut self) {
match *self {
Self::Array { ref mut index, .. } => {
// Array iteration uses `usize` as the cursor and advances via
// `saturating_add(1)`. A cursor already at `usize::MAX` here
// means a stuck (non-progressing) iteration was emitted by
// malformed bytecode; assert in debug to surface it loudly.
debug_assert!(
*index < usize::MAX,
"IterationState::Array index already at usize::MAX on advance"
);
*index = index.saturating_add(1);
}
Self::Object {
ref mut first_iteration,
..
}
| Self::Set {
// For Object the cursor advances inside `setup_next_iteration`
// when it pulls the next item via `Object::next`, so `advance`
// is a no-op for the cursor-backed Object variant.
Self::Object { .. } => {}
Self::Set {
ref mut first_iteration,
..
} => {
@@ -69,6 +88,12 @@ impl IterationState {
Self::Single {
ref mut consumed, ..
} => {
// `Single` yields exactly once; advancing a consumed Single
// means the compiler emitted a redundant LoopNext.
debug_assert!(
!*consumed,
"IterationState::Single advanced after consumption"
);
*consumed = true;
}
}
@@ -107,3 +132,71 @@ pub(super) struct ComprehensionContext {
/// Resume location for the parent frame once this comprehension completes
pub(super) resume_pc: usize,
}
#[cfg(test)]
#[allow(
clippy::expect_used,
clippy::unwrap_used,
clippy::unreachable,
clippy::pattern_type_mismatch,
clippy::shadow_unrelated,
clippy::panic
)]
mod tests {
use super::*;
use crate::value::Object;
/// IterationState::Object holds an `Rc<Object>` plus an opaque cursor.
/// Mutating an aliased Rc via `Rc::make_mut` allocates a new collection
/// (CoW) so the in-flight iterator's source is unaffected.
#[test]
fn iteration_state_object_is_snapshot_independent_of_source() {
let mut obj = Object::new();
obj.insert(Value::from("a"), Value::from(1));
obj.insert(Value::from("b"), Value::from(2));
obj.insert(Value::from("c"), Value::from(3));
let source = Value::Object(Rc::new(obj));
let snapshot_obj = match &source {
Value::Object(o) => Rc::clone(o),
_ => unreachable!(),
};
let state = IterationState::Object {
obj: Rc::clone(&snapshot_obj),
cursor: snapshot_obj.cursor(),
};
// Mutate a clone of the source mid-iteration.
let mut alias = source.clone();
let inner = alias.as_object_mut().expect("object");
inner.insert(Value::from("a"), Value::from(999));
inner.insert(Value::from("d"), Value::from(4));
inner.remove(&Value::from("b"));
// Drain the snapshot via the cursor — must still report the original
// 3 entries with original values.
let mut collected: Vec<(Value, Value)> = Vec::new();
if let IterationState::Object {
ref obj,
mut cursor,
} = state
{
while let Some((k, v)) = obj.next(&mut cursor) {
collected.push((k.clone(), v.clone()));
}
} else {
unreachable!();
}
assert_eq!(collected.len(), 3);
assert!(collected.contains(&(Value::from("a"), Value::from(1))));
assert!(collected.contains(&(Value::from("b"), Value::from(2))));
assert!(collected.contains(&(Value::from("c"), Value::from(3))));
assert!(!collected.iter().any(|kv| kv.0 == Value::from("d")));
// The original source Value (untouched) is also unchanged.
let src_obj = source.as_object().expect("object");
assert_eq!(src_obj.len(), 3);
assert_eq!(src_obj.get(&Value::from("a")), Some(&Value::from(1)));
}
}

View File

@@ -4,7 +4,6 @@
use crate::rvm::instructions::{GuardMode, Instruction, LiteralOrRegister};
use crate::rvm::program::Program;
use crate::value::Value;
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use core::mem;
@@ -670,7 +669,7 @@ impl RegoVM {
}
}
SetNew { dest } => {
let empty_set = Value::Set(crate::Rc::new(BTreeSet::new()));
let empty_set = Value::new_set();
self.set_register(dest, empty_set)?;
Ok(InstructionOutcome::Continue)
}
@@ -707,7 +706,7 @@ impl RegoVM {
if any_undefined {
self.set_register(params.dest, Value::Undefined)?;
} else {
let mut set = BTreeSet::new();
let mut set = alloc::collections::BTreeSet::new();
for &reg in params.element_registers() {
set.insert(self.get_register(reg)?.clone());
}

View File

@@ -295,6 +295,13 @@ pub enum VmError {
#[error("Call rule stack underflow during rule finalization (pc={pc})")]
CallRuleStackUnderflow { pc: usize },
#[error("Call rule stack mismatch during rule finalization: expected rule_index {expected}, popped {actual} (pc={pc})")]
CallRuleStackMismatch {
expected: u16,
actual: u16,
pc: usize,
},
#[error("Internal VM error: {message} (pc={pc})")]
Internal { message: String, pc: usize },
}

View File

@@ -117,6 +117,10 @@ impl RegoVM {
let target = self.convert_pc(target, "jump target")?;
self.pc = target;
while self.pc < program.instructions.len() {
// Per-instruction sanity check: every iteration of the dispatch
// loop must re-enter with the VM in a Running/Ready state and the
// working data structures coherent.
self.assert_vm_invariants();
self.memory_check()?;
if self.executed_instructions >= self.max_instructions {
return Err(VmError::InstructionLimitExceeded {
@@ -189,6 +193,9 @@ impl RegoVM {
}
fn execute_suspendable_entry(&mut self, entry_point_pc: usize) -> Result<Value> {
// Precondition: callers (execute_entry_point_by_{index,name}) reset the
// VM before invoking this method, so the VM must be in a clean state.
self.debug_assert_state_is_clean();
self.execution_state = ExecutionState::Running;
self.reset_execution_timer_state();
match self.run_stackless_from(entry_point_pc) {
@@ -201,6 +208,10 @@ impl RegoVM {
}
pub fn resume(&mut self, resume_value: Option<Value>) -> Result<Value> {
// Precondition is enforced below by returning `VmError::InvalidResumeState`
// for any non-`Suspended` state. A `debug_assert!` here would diverge
// debug vs release behavior and, when invoked via FFI, would trip the
// unwind guard and poison the engine on a recoverable misuse.
let (reason, mut last_result) = match self.execution_state.clone() {
ExecutionState::Suspended {
reason,
@@ -289,6 +300,9 @@ impl RegoVM {
fn run_stackless_loop(&mut self, program: &Program, last_result: &mut Value) -> Result<()> {
while !self.execution_stack.is_empty() {
// Per-instruction sanity check: see `assert_vm_invariants` for the
// exact contract. Compiled out in release.
self.assert_vm_invariants();
self.memory_check()?;
self.frame_pc_overridden = false;
let should_finalize_rule = self.execution_stack.last().is_some_and(|frame| {

View File

@@ -3,6 +3,7 @@
use crate::rvm::instructions::LoopMode;
use crate::value::Value;
use crate::Rc;
use super::context::{IterationState, LoopContext};
use super::errors::{Result, VmError};
@@ -89,13 +90,13 @@ impl RegoVM {
) -> Result<()> {
self.set_register(params.result_reg, Value::Bool(false))?;
let iteration_state = match self.resolve_iteration_state(mode, &params)? {
let mut iteration_state = match self.resolve_iteration_state(mode, &params)? {
Some(state) => state,
None => return Ok(()),
};
let has_next =
self.setup_next_iteration(&iteration_state, params.key_reg, params.value_reg)?;
self.setup_next_iteration(&mut iteration_state, params.key_reg, params.value_reg)?;
if !has_next {
self.pc = usize::from(params.loop_end);
return Ok(());
@@ -155,15 +156,10 @@ impl RegoVM {
LoopAction::Continue => {}
}
if let &mut IterationState::Object {
ref mut current_key,
..
} = &mut loop_ctx.iteration_state
{
if loop_ctx.key_reg != loop_ctx.value_reg {
*current_key = Some(self.get_register(loop_ctx.key_reg)?.clone());
}
} else if let &mut IterationState::Set {
// Snapshot the current value for Set so its next iteration can resume
// from `Bound::Excluded(current)`. Object uses a cursor and advances
// inside `setup_next_iteration` itself.
if let &mut IterationState::Set {
ref mut current_item,
..
} = &mut loop_ctx.iteration_state
@@ -173,7 +169,7 @@ impl RegoVM {
loop_ctx.iteration_state.advance();
let has_next = self.setup_next_iteration(
&loop_ctx.iteration_state,
&mut loop_ctx.iteration_state,
loop_ctx.key_reg,
loop_ctx.value_reg,
)?;
@@ -211,13 +207,13 @@ impl RegoVM {
) -> Result<()> {
self.set_register(params.result_reg, Value::Bool(false))?;
let iteration_state = match self.resolve_iteration_state(mode, &params)? {
let mut iteration_state = match self.resolve_iteration_state(mode, &params)? {
Some(state) => state,
None => return Ok(()),
};
let has_next =
self.setup_next_iteration(&iteration_state, params.key_reg, params.value_reg)?;
self.setup_next_iteration(&mut iteration_state, params.key_reg, params.value_reg)?;
if !has_next {
self.pc = usize::from(params.loop_end);
return Ok(());
@@ -316,7 +312,14 @@ impl RegoVM {
Ok(())
}
LoopAction::Continue => {
let (mode, success_count, total_iterations, key_reg, value_reg, iteration_state) = {
let (
mode,
success_count,
total_iterations,
key_reg,
value_reg,
mut iteration_state,
) = {
let (mode, success_count, total_iterations, key_reg, value_reg) = {
let frame = self
.execution_stack
@@ -334,11 +337,6 @@ impl RegoVM {
}
};
let key_value = if key_reg != value_reg {
Some(self.get_register(key_reg)?.clone())
} else {
None
};
let value_value = self.get_register(value_reg)?.clone();
let frame = self
@@ -349,20 +347,16 @@ impl RegoVM {
&mut FrameKind::Loop {
ref mut context, ..
} => {
if let &mut IterationState::Object {
ref mut current_key,
..
} = &mut context.iteration_state
{
if context.key_reg != context.value_reg {
*current_key = key_value;
}
} else if let &mut IterationState::Set {
// Snapshot the current value for Set so its next
// iteration can resume from `Bound::Excluded(current)`.
// Object uses a cursor and advances inside
// `setup_next_iteration` itself.
if let &mut IterationState::Set {
ref mut current_item,
..
} = &mut context.iteration_state
{
*current_item = Some(value_value.clone());
*current_item = Some(value_value);
}
context.iteration_state.advance();
@@ -381,7 +375,21 @@ impl RegoVM {
}
};
let has_next = self.setup_next_iteration(&iteration_state, key_reg, value_reg)?;
let has_next =
self.setup_next_iteration(&mut iteration_state, key_reg, value_reg)?;
// `setup_next_iteration` advances Object's internal cursor;
// the owning frame holds the iteration_state, so we must
// write the updated state back. (Array/Set are unchanged by
// the call, so the writeback is uniform.)
if let Some(frame) = self.execution_stack.last_mut() {
if let FrameKind::Loop {
ref mut context, ..
} = frame.kind
{
context.iteration_state = iteration_state;
}
}
if has_next {
if let Some(frame) = self.execution_stack.last_mut() {
@@ -459,10 +467,14 @@ impl RegoVM {
self.handle_empty_collection(mode, params.result_reg, params.loop_end)?;
return Ok(None);
}
// O(1) resumable cursor over the shared Rc<Object>.
// No eager pair snapshot: avoids O(N) setup, O(N) memory
// floor, and O(N) memory-limit checks. Snapshot
// independence is via the shared Rc (CoW).
let cursor = obj.cursor();
Ok(Some(IterationState::Object {
obj: obj.clone(),
current_key: None,
first_iteration: true,
obj: Rc::clone(obj),
cursor,
}))
}
}
@@ -483,8 +495,15 @@ impl RegoVM {
// over a virtual null element.
Ok(Some(IterationState::Single { consumed: false }))
} else {
// Standard Rego or count/forEach: non-collection → immediate result.
let result = non_collection_result(mode);
// Standard Rego: iterating a non-collection scalar (number,
// string, bool, null, undefined) yields no iterations. For
// `every` this makes the quantifier undefined (it fails) — it
// is NOT vacuously true, which only applies to a genuinely
// empty collection. `any`/`forEach` remain false.
let result = match *mode {
LoopMode::Every => Value::Undefined,
LoopMode::Any | LoopMode::ForEach => Value::Bool(false),
};
self.set_register(params.result_reg, result)?;
self.pc = usize::from(params.loop_end).saturating_sub(1);
Ok(None)
@@ -512,7 +531,7 @@ impl RegoVM {
pub(super) fn setup_next_iteration(
&mut self,
state: &IterationState,
state: &mut IterationState,
key_reg: u8,
value_reg: u8,
) -> Result<bool> {
@@ -538,33 +557,19 @@ impl RegoVM {
}
IterationState::Object {
ref obj,
ref current_key,
ref first_iteration,
ref mut cursor,
} => {
if *first_iteration {
if let Some((key, value)) = obj.iter().next() {
if key_reg != value_reg {
self.set_register(key_reg, key.clone())?;
}
self.set_register(value_reg, value.clone())?;
Ok(true)
} else {
Ok(false)
}
} else if let Some(ref current) = *current_key {
let mut range_iter = obj.range((
core::ops::Bound::Excluded(current),
core::ops::Bound::Unbounded,
));
if let Some((key, value)) = range_iter.next() {
if key_reg != value_reg {
self.set_register(key_reg, key.clone())?;
}
self.set_register(value_reg, value.clone())?;
Ok(true)
} else {
Ok(false)
// Object iterates via a resumable cursor on the shared
// `Rc<Object>`; `next` both yields the current entry and
// advances the cursor. No explicit `current_key` snapshot is
// needed — see the doc on `IterationState`.
if let Some((key, value)) = obj.next(cursor) {
let value = value.clone();
if key_reg != value_reg {
self.set_register(key_reg, key.clone())?;
}
self.set_register(value_reg, value)?;
Ok(true)
} else {
Ok(false)
}

View File

@@ -277,6 +277,19 @@ impl RegoVM {
.call_rule_stack
.pop()
.ok_or(VmError::CallRuleStackUnderflow { pc: self.pc })?;
// Stack discipline: the context we just popped must belong to the
// rule we are finalizing. A mismatch indicates a missing push or an
// extra pop somewhere in this rule's execution and would otherwise
// silently restore the wrong return_pc / rule_type. Surface as a
// typed VmError so the contract holds the same in debug and release
// builds (avoiding FFI poisoning via a debug-only panic).
if rule_index != call_context.rule_index {
return Err(VmError::CallRuleStackMismatch {
expected: rule_index,
actual: call_context.rule_index,
pc: self.pc,
});
}
self.pc = call_context.return_pc;
let result_from_rule = if !rule_failed_due_to_inconsistency {
@@ -831,6 +844,9 @@ impl RegoVM {
self.registers = parent_registers;
// Underflow here means malformed/poisoned program state; surface as a
// typed error rather than a debug-only panic so the public load_program
// contract holds the same in debug and release.
if self.call_rule_stack.pop().is_none() {
return Err(VmError::CallRuleStackUnderflow { pc: self.pc });
}

View File

@@ -34,6 +34,139 @@ impl RegoVM {
// Builtin cache entries only live for a single execution
self.builtins_cache.clear();
// Postcondition: every stack/cache that `reset_execution_state` touches
// must be in its documented "clean" shape. This catches accidental
// omissions in future edits to this function.
self.debug_assert_state_is_clean();
}
/// Debug-only postcondition for `reset_execution_state`.
///
/// Asserts the invariants every caller of `reset_execution_state` relies on
/// before starting a fresh execution. The body is fully gated by
/// `#[cfg(debug_assertions)]` so this is a zero-cost no-op in release.
#[inline]
pub(super) fn debug_assert_state_is_clean(&self) {
#[cfg(debug_assertions)]
{
// --- Stacks: every per-execution stack must be drained. ---
debug_assert!(
self.execution_stack.is_empty(),
"reset_execution_state postcondition: execution_stack must be empty"
);
debug_assert!(
self.loop_stack.is_empty(),
"reset_execution_state postcondition: loop_stack must be empty"
);
debug_assert!(
self.comprehension_stack.is_empty(),
"reset_execution_state postcondition: comprehension_stack must be empty"
);
debug_assert!(
self.call_rule_stack.is_empty(),
"reset_execution_state postcondition: call_rule_stack must be empty"
);
debug_assert!(
self.register_stack.is_empty(),
"reset_execution_state postcondition: register_stack must be empty"
);
// --- Caches: cleared so a new program/input cannot read stale entries. ---
debug_assert!(
self.builtins_cache.is_empty(),
"reset_execution_state postcondition: builtins_cache must be empty"
);
// --- Registers: window resized to the program's base count and zeroed. ---
debug_assert_eq!(
self.registers.len(),
self.base_register_count,
"reset_execution_state postcondition: registers must be sized to base_register_count"
);
debug_assert!(
self.registers.iter().all(|v| matches!(v, Value::Undefined)),
"reset_execution_state postcondition: all registers must be Undefined"
);
// --- Rule cache: sized to the current program and marked uncomputed. ---
debug_assert_eq!(
self.rule_cache.len(),
self.program.rule_infos.len(),
"reset_execution_state postcondition: rule_cache size must match program rule_infos"
);
debug_assert!(
self.rule_cache.iter().all(|entry| !entry.0),
"reset_execution_state postcondition: rule_cache entries must be uncomputed"
);
// --- Counters and execution-state machine: zeroed and back to Ready. ---
debug_assert_eq!(
self.pc, 0,
"reset_execution_state postcondition: pc must be 0"
);
debug_assert_eq!(
self.executed_instructions, 0,
"reset_execution_state postcondition: executed_instructions must be 0"
);
debug_assert!(
matches!(self.execution_state, ExecutionState::Ready),
"reset_execution_state postcondition: execution_state must be Ready"
);
}
}
/// Per-opcode VM invariants checked from the inner dispatch loop.
///
/// These hold every time control re-enters the dispatch loop with another
/// instruction to execute. Only conditions that are *purely VM-internal*
/// (i.e. cannot be made false by any host-supplied program or out-of-order
/// API call) are asserted here — anything reachable from `load_program`
/// input must surface as a typed `VmError` instead, to avoid panicking in
/// debug builds and poisoning the engine across FFI.
///
/// Fully `#[cfg(debug_assertions)]`-gated so the method body compiles out
/// in release.
#[inline]
pub(super) fn assert_vm_invariants(&self) {
#[cfg(debug_assertions)]
{
// The dispatch loop only runs while execution is live. Once the VM
// has transitioned to a terminal state (Suspended/Completed/Error)
// the loop must have exited. Note `Ready` is also valid here because
// some entry points (e.g. `execute_entry_point_by_index` in
// RunToCompletion mode) drive `jump_to` without flipping the state.
// `execution_state` is mutated only inside the VM and is not
// host-controllable.
debug_assert!(
matches!(
self.execution_state,
ExecutionState::Ready | ExecutionState::Running
),
"vm invariant: execution_state must be Ready or Running inside the dispatch loop, was {:?}",
self.execution_state
);
// Rule cache is sized once at reset (against the currently loaded
// program) and the VM does not resize it mid-execution. Any
// mismatch here would indicate an internal accounting bug rather
// than malformed input.
debug_assert_eq!(
self.rule_cache.len(),
self.program.rule_infos.len(),
"vm invariant: rule_cache size must equal program.rule_infos size"
);
// NOTE: `!registers.is_empty()` and an `execution_stack` depth
// ceiling were intentionally *not* asserted here: both can be
// triggered by a host-loaded program (registers via
// `RuleInfo::num_registers == 0`; stack depth via deeply nested
// rules/loops/comprehensions) and would therefore panic in debug
// and poison the engine across FFI. Register access is already
// guarded by `VmError::RegisterIndexOutOfBounds`; runaway recursion
// is bounded in production by `set_max_instructions` and
// `memory_check`.
}
}
/// Return all active objects to their respective pools for reuse

View File

@@ -569,7 +569,7 @@ impl Analyzer {
}
Ok(false)
}
Array { .. } | Object { .. } => Ok(true),
Expr::Array { .. } | Expr::Object { .. } => Ok(true),
_ => Ok(false),
})?;
Ok(true)
@@ -666,7 +666,7 @@ impl Analyzer {
Ok(false)
}
// TODO: key vs value for object binding
Array { .. } | Object { .. } => Ok(true),
Expr::Array { .. } | Expr::Object { .. } => Ok(true),
_ => Ok(false),
})?;
Ok(vars)
@@ -853,7 +853,7 @@ impl Analyzer {
Ok(false)
}
// TODO: Object key/value
Array { .. } | Object { .. } => Ok(true),
Expr::Array { .. } | Expr::Object { .. } => Ok(true),
_ => {
non_vars.push(e.clone());
Ok(false)

View File

@@ -1498,7 +1498,7 @@ fn test_deserialize_object_default_empty_object() {
let s = Schema::from_serde_json_value(schema).unwrap();
match s.as_type() {
Type::Object { default, .. } => {
assert_eq!(default, &Some(Value::Object(Rc::new(BTreeMap::new()))));
assert_eq!(default, &Some(Value::new_object()));
}
_ => panic!("Expected Type::Object"),
}
@@ -1778,8 +1778,11 @@ fn test_deserialize_enum_values_with_object_non_string_keys() {
match s.as_type() {
Type::Enum { values, .. } => match &values[0] {
Value::Object(obj) => {
assert_eq!(obj[&Value::from("1")], Value::from("one"));
assert_eq!(obj[&Value::from("true")], Value::from("bool"));
assert_eq!(*obj.get(&Value::from("1")).expect("1"), Value::from("one"));
assert_eq!(
*obj.get(&Value::from("true")).expect("true"),
Value::from("bool")
);
}
_ => panic!("Expected object in enum values"),
},
@@ -1802,18 +1805,21 @@ fn test_deserialize_enum_values_with_deeply_nested_structures() {
match s.as_type() {
Type::Enum { values, .. } => match &values[0] {
Value::Object(obj) => {
let a = &obj[&Value::from("a")];
let a = obj.get(&Value::from("a")).expect("a");
match a {
Value::Array(arr) => match &arr[0] {
Value::Object(inner) => {
let b = &inner[&Value::from("b")];
let b = inner.get(&Value::from("b")).expect("b");
match b {
Value::Array(barr) => {
assert_eq!(barr[0], Value::from(1));
assert_eq!(barr[1], Value::from(2));
match &barr[2] {
Value::Object(cobj) => {
assert_eq!(cobj[&Value::from("c")], Value::Null);
assert_eq!(
*cobj.get(&Value::from("c")).expect("c"),
Value::Null
);
}
_ => panic!("Expected object for 'c'"),
}
@@ -1886,8 +1892,11 @@ fn test_deserialize_const_value_object() {
match s.as_type() {
Type::Const { value, .. } => match value {
Value::Object(ref obj) => {
assert_eq!(obj[&Value::from("foo")], Value::from("bar"));
assert_eq!(obj[&Value::from("baz")], Value::from(1));
assert_eq!(
*obj.get(&Value::from("foo")).expect("foo"),
Value::from("bar")
);
assert_eq!(*obj.get(&Value::from("baz")).expect("baz"), Value::from(1));
}
_ => panic!("Expected object for const value"),
},
@@ -1940,13 +1949,13 @@ fn test_deserialize_const_value_deeply_nested() {
match s.as_type() {
Type::Const { value, .. } => match value {
Value::Object(ref obj) => {
let a = &obj[&Value::from("a")];
let a = obj.get(&Value::from("a")).expect("a");
match a {
Value::Array(arr) => {
assert_eq!(arr[0], Value::from(1));
match &arr[1] {
Value::Object(inner) => {
let b = &inner[&Value::from("b")];
let b = inner.get(&Value::from("b")).expect("b");
match b {
Value::Array(barr) => {
assert_eq!(barr[0], Value::Null);

View File

@@ -7,6 +7,7 @@
use crate::{
schema::{error::ValidationError, Schema, Type},
value::Object,
*,
};
use alloc::collections::BTreeMap;
@@ -537,7 +538,7 @@ impl SchemaValidator {
}
fn validate_discriminated_subobject_with_base(
object_value: &BTreeMap<Value, Value>,
object_value: &Object,
discriminated_subobject: &crate::schema::DiscriminatedSubobject,
base_properties: &BTreeMap<String, Schema>,
base_additional_properties: Option<&Schema>,
@@ -653,7 +654,7 @@ impl SchemaValidator {
}
fn validate_subobject(
object_value: &BTreeMap<Value, Value>,
object_value: &Object,
subobject: &crate::schema::Subobject,
path: &str,
) -> Result<(), ValidationError> {

View File

@@ -24,6 +24,7 @@ use anyhow::{bail, Result};
use core::num::NonZeroU32;
use core::time::Duration;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
use test_generator::test_resources;
use timer_test_support::{
apply_engine_timer, configure_time_source, reset_time_source, GlobalTimerGuard,
@@ -818,3 +819,446 @@ fn test_get_data() -> Result<()> {
Ok(())
}
#[test]
fn test_add_data_deep_merge() -> Result<()> {
let mut engine = Engine::new();
// Nested objects under a shared top-level key are deep-merged, not replaced.
engine.add_data(Value::from_json_str(r#"{ "a" : { "x" : 1 } }"#)?)?;
engine.add_data(Value::from_json_str(r#"{ "a" : { "y" : 2 } }"#)?)?;
assert_eq!(
engine.get_data(),
Value::from_json_str(r#"{ "a" : { "x" : 1, "y" : 2 } }"#)?
);
Ok(())
}
#[test]
fn test_add_data_deep_merge_multi_level() -> Result<()> {
let mut engine = Engine::new();
// Merging recurses through multiple levels of nesting.
engine.add_data(Value::from_json_str(
r#"{ "a" : { "b" : { "x" : 1 } }, "top" : 0 }"#,
)?)?;
engine.add_data(Value::from_json_str(
r#"{ "a" : { "b" : { "y" : 2 }, "c" : 3 } }"#,
)?)?;
assert_eq!(
engine.get_data(),
Value::from_json_str(r#"{ "a" : { "b" : { "x" : 1, "y" : 2 }, "c" : 3 }, "top" : 0 }"#)?
);
Ok(())
}
#[test]
fn test_add_data_leaf_conflict_errors() -> Result<()> {
let mut engine = Engine::new();
// A genuine leaf conflict (same nested path, different value) is an error.
engine.add_data(Value::from_json_str(r#"{ "a" : { "x" : 1 } }"#)?)?;
assert!(engine
.add_data(Value::from_json_str(r#"{ "a" : { "x" : 2 } }"#)?)
.is_err());
Ok(())
}
#[test]
fn test_add_data_object_vs_scalar_conflict_errors() -> Result<()> {
let mut engine = Engine::new();
// An object cannot be merged with a scalar at the same path.
engine.add_data(Value::from_json_str(r#"{ "a" : { "x" : 1 } }"#)?)?;
assert!(engine
.add_data(Value::from_json_str(r#"{ "a" : 5 }"#)?)
.is_err());
Ok(())
}
#[test]
fn test_add_data_equal_leaf_is_noop() -> Result<()> {
let mut engine = Engine::new();
// Re-adding identical data (including equal nested leaves) is tolerated as a no-op.
engine.add_data(Value::from_json_str(r#"{ "a" : { "x" : 1 } }"#)?)?;
engine.add_data(Value::from_json_str(r#"{ "a" : { "x" : 1 }, "b" : 2 }"#)?)?;
assert_eq!(
engine.get_data(),
Value::from_json_str(r#"{ "a" : { "x" : 1 }, "b" : 2 }"#)?
);
Ok(())
}
#[test]
fn test_add_data_set_union() -> Result<()> {
let mut engine = Engine::new();
// Sets under a shared key are unioned rather than conflicting (consistent with the
// rule-evaluation merge, where partial set rules accumulate elements). JSON cannot express
// sets, so the data documents are built via the `Value` API.
engine.add_data(Value::from(BTreeMap::from([(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(1_u64), Value::from(2_u64)])),
)])))?;
engine.add_data(Value::from(BTreeMap::from([(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(2_u64), Value::from(3_u64)])),
)])))?;
let expected = Value::from(BTreeMap::from([(
Value::from("s"),
Value::from(BTreeSet::from([
Value::from(1_u64),
Value::from(2_u64),
Value::from(3_u64),
])),
)]));
assert_eq!(engine.get_data(), expected);
Ok(())
}
#[test]
fn test_add_data_nested_set_union() -> Result<()> {
let mut engine = Engine::new();
// A set nested under an object key exercises the recursive merge: the outer objects are
// deep-merged and the inner sets are then unioned.
engine.add_data(Value::from(BTreeMap::from([(
Value::from("a"),
Value::from(BTreeMap::from([(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(1_u64)])),
)])),
)])))?;
engine.add_data(Value::from(BTreeMap::from([(
Value::from("a"),
Value::from(BTreeMap::from([(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(2_u64)])),
)])),
)])))?;
let expected = Value::from(BTreeMap::from([(
Value::from("a"),
Value::from(BTreeMap::from([(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(1_u64), Value::from(2_u64)])),
)])),
)]));
assert_eq!(engine.get_data(), expected);
Ok(())
}
#[test]
fn test_add_data_equal_set_is_noop() -> Result<()> {
let mut engine = Engine::new();
// Re-adding an identical set is tolerated as a no-op (not a conflict).
engine.add_data(Value::from(BTreeMap::from([(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(1_u64), Value::from(2_u64)])),
)])))?;
engine.add_data(Value::from(BTreeMap::from([(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(1_u64), Value::from(2_u64)])),
)])))?;
let expected = Value::from(BTreeMap::from([(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(1_u64), Value::from(2_u64)])),
)]));
assert_eq!(engine.get_data(), expected);
Ok(())
}
#[test]
fn test_add_data_failed_merge_is_atomic() -> Result<()> {
let mut engine = Engine::new();
engine.add_data(Value::from_json_str(r#"{ "a" : { "z" : 1 } }"#)?)?;
// Mixes a new key `m` with a conflicting leaf `z` (1 vs 3). Because `m` sorts
// before `z`, a naive in-place merge would insert `m` and only then hit the `z`
// conflict. add_data must be all-or-nothing: the whole call fails AND leaves the
// existing data untouched — `m` must not leak in.
assert!(engine
.add_data(Value::from_json_str(r#"{ "a" : { "m" : 2, "z" : 3 } }"#)?)
.is_err());
assert_eq!(
engine.get_data(),
Value::from_json_str(r#"{ "a" : { "z" : 1 } }"#)?
);
Ok(())
}
#[test]
fn test_add_data_failed_set_merge_is_atomic() -> Result<()> {
let mut engine = Engine::new();
// Existing data: a set `s` alongside a scalar `z` under `a`.
engine.add_data(Value::from(BTreeMap::from([(
Value::from("a"),
Value::from(BTreeMap::from([
(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(1_u64), Value::from(2_u64)])),
),
(Value::from("z"), Value::from(1_u64)),
])),
)])))?;
// This add would union `s` with {3} but conflicts on `z` (1 vs 2). Since `s`
// sorts before `z`, a naive in-place merge would union the set *before* failing
// on `z`, leaking {3} into `s`. The atomic add must reject the whole call and
// leave `s` as {1, 2}.
assert!(engine
.add_data(Value::from(BTreeMap::from([(
Value::from("a"),
Value::from(BTreeMap::from([
(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(3_u64)])),
),
(Value::from("z"), Value::from(2_u64)),
])),
)])))
.is_err());
// `s` must be unchanged ({1, 2}, not {1, 2, 3}) and `z` must still be 1.
let expected = Value::from(BTreeMap::from([(
Value::from("a"),
Value::from(BTreeMap::from([
(
Value::from("s"),
Value::from(BTreeSet::from([Value::from(1_u64), Value::from(2_u64)])),
),
(Value::from("z"), Value::from(1_u64)),
])),
)]));
assert_eq!(engine.get_data(), expected);
Ok(())
}
#[test]
fn test_add_data_failed_array_merge_is_atomic() -> Result<()> {
let mut engine = Engine::new();
engine.add_data(Value::from_json_str(r#"{ "a" : { "arr" : [1, 2] } }"#)?)?;
// Arrays are atomic leaves (never element-merged), so a differing array at the
// same path is a conflict. The new key `aa` sorts before `arr`, so a naive
// in-place merge would insert `aa` and only then hit the `arr` conflict. add_data
// must reject the whole call and leave the data untouched — `aa` must not leak in.
assert!(engine
.add_data(Value::from_json_str(
r#"{ "a" : { "aa" : 5, "arr" : [3] } }"#
)?)
.is_err());
assert_eq!(
engine.get_data(),
Value::from_json_str(r#"{ "a" : { "arr" : [1, 2] } }"#)?
);
Ok(())
}
// The `Value::merge` used by `add_data` is shared with the rule-evaluation path
// (`Interpreter::merge_rule_value`, reached via `with data.* as ...` and rule-value
// materialization). The tests below pin down that making `merge` recursive changed only the
// data-document semantics and left rule evaluation — in particular the `with data.* as ...`
// modifier — behaving exactly as before (an override, never a deep merge).
#[test]
fn test_with_data_modifier_replaces_nested_object() -> Result<()> {
let mut engine = Engine::new();
// Base data provides a nested object with two keys.
engine.add_data(Value::from_json_str(
r#"{ "base" : { "foo" : { "a" : 1, "b" : 2 } } }"#,
)?)?;
engine.add_policy(
"policy.rego".to_string(),
r#"
package test
result := x if {
x := data.base.foo with data.base.foo as {"a": 99}
}
"#
.to_string(),
)?;
// `with data.base.foo as {"a": 99}` REPLACES the whole subtree for the duration of the
// rule; it must NOT deep-merge with the base `{ "a": 1, "b": 2 }`. So `b` is gone.
assert_eq!(
engine
.eval_query("data.test.result".to_string(), false)?
.result[0]
.expressions[0]
.value
.clone(),
Value::from_json_str(r#"{ "a" : 99 }"#)?
);
Ok(())
}
#[test]
fn test_with_data_modifier_replaces_whole_subtree() -> Result<()> {
let mut engine = Engine::new();
engine.add_data(Value::from_json_str(
r#"{ "base" : { "foo" : 1, "bar" : 2 } }"#,
)?)?;
engine.add_policy(
"policy.rego".to_string(),
r#"
package test
result := x if {
x := data.base with data.base as {"only": 3}
}
"#
.to_string(),
)?;
// `with data.base as {...}` replaces the entire `data.base` object; the original
// `foo`/`bar` keys are not merged in.
assert_eq!(
engine
.eval_query("data.test.result".to_string(), false)?
.result[0]
.expressions[0]
.value
.clone(),
Value::from_json_str(r#"{ "only" : 3 }"#)?
);
Ok(())
}
#[test]
fn test_with_data_modifier_nested_replace_preserves_siblings() -> Result<()> {
let mut engine = Engine::new();
// `data.base` has a nested `foo` object AND a sibling `bar`.
engine.add_data(Value::from_json_str(
r#"{ "base" : { "foo" : { "a" : 1, "b" : 2 }, "bar" : 7 } }"#,
)?)?;
engine.add_policy(
"policy.rego".to_string(),
r#"
package test
# `with` targets the nested `data.base.foo`, but the rule observes the PARENT `data.base`.
result := x if {
x := data.base with data.base.foo as {"a": 99}
}
"#
.to_string(),
)?;
// The nested `foo` is deep-replaced (its `b` is gone — `with` never merges), while the
// sibling `bar` under the same parent is preserved.
assert_eq!(
engine
.eval_query("data.test.result".to_string(), false)?
.result[0]
.expressions[0]
.value
.clone(),
Value::from_json_str(r#"{ "foo" : { "a" : 99 }, "bar" : 7 }"#)?
);
Ok(())
}
#[test]
fn test_rule_reads_deep_merged_base_data() -> Result<()> {
let mut engine = Engine::new();
// Two add_data calls deep-merge into a single nested object...
engine.add_data(Value::from_json_str(
r#"{ "base" : { "foo" : { "a" : 1 } } }"#,
)?)?;
engine.add_data(Value::from_json_str(
r#"{ "base" : { "foo" : { "b" : 2 } } }"#,
)?)?;
engine.add_policy(
"policy.rego".to_string(),
r#"
package test
a := data.base.foo.a
b := data.base.foo.b
"#
.to_string(),
)?;
// ...and both merged leaves are visible to rule evaluation.
assert_eq!(
engine.eval_query("data.test".to_string(), false)?.result[0].expressions[0]
.value
.clone(),
Value::from_json_str(r#"{ "a" : 1, "b" : 2 }"#)?
);
Ok(())
}
#[test]
fn test_rule_values_coexist_with_merged_base_data() -> Result<()> {
let mut engine = Engine::new();
// Deep-merged base data under `base`...
engine.add_data(Value::from_json_str(
r#"{ "base" : { "foo" : { "a" : 1 } } }"#,
)?)?;
engine.add_data(Value::from_json_str(
r#"{ "base" : { "foo" : { "b" : 2 } } }"#,
)?)?;
engine.add_policy(
"policy.rego".to_string(),
r#"
package test
computed := data.base.foo.a + data.base.foo.b
"#
.to_string(),
)?;
let data = engine.eval_query("data".to_string(), false)?.result[0].expressions[0]
.value
.clone();
// Base data is preserved and deep-merged...
assert_eq!(
data["base"],
Value::from_json_str(r#"{ "foo" : { "a" : 1, "b" : 2 } }"#)?
);
// ...and the rule-computed value materializes alongside it without disturbing the merge.
assert_eq!(data["test"]["computed"], Value::from(3_u64));
Ok(())
}

View File

@@ -88,7 +88,7 @@ fn analyze_file(regos: &[String], expected_scopes: &[Scope]) -> Result<()> {
}
}
scopes.sort_by(|a, b| a.0.span.line.cmp(&b.0.span.line));
scopes.sort_by_key(|a| a.0.span.line);
for (idx, (_, scope)) in scopes.iter().enumerate() {
if idx > expected_scopes.len() {
bail!("extra scope generated.")

View File

@@ -11,6 +11,24 @@
clippy::as_conversions
)] // value helpers index paths directly for performance
mod object;
mod set;
#[cfg(test)]
mod tests;
#[allow(unused_imports)] // surface for downstream PRs
pub use object::{IntoIter, Iter, IterMut, Object};
#[allow(unused_imports)] // surface for downstream PRs
pub use set::Set;
#[cfg(feature = "rvm")]
#[allow(unused_imports)] // surface for downstream PRs
pub use object::ObjectCursor;
#[cfg(feature = "rvm")]
#[allow(unused_imports)] // surface for downstream PRs
pub use set::SetCursor;
use crate::number::Number;
use alloc::collections::{BTreeMap, BTreeSet};
@@ -23,7 +41,7 @@ use core::str::FromStr;
use anyhow::{anyhow, bail, Result};
use serde::de::{self, Deserializer, Error as DeError, MapAccess, SeqAccess, Visitor};
use serde::ser::{SerializeMap, Serializer};
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};
use crate::*;
@@ -63,7 +81,7 @@ pub enum Value {
/// An object.
/// Unlike JSON, keys can be any value, not just string.
Object(Rc<BTreeMap<Value, Value>>),
Object(Rc<Object>),
/// Undefined value.
/// Used to indicate the absence of a value.
@@ -86,26 +104,15 @@ impl Serialize for Value {
where
S: Serializer,
{
use serde::ser::Error;
match self {
Value::Null => serializer.serialize_unit(),
Value::Bool(b) => serializer.serialize_bool(*b),
Value::String(s) => serializer.serialize_str(s.as_ref()),
Value::Number(n) => n.serialize(serializer),
Value::Array(a) => a.serialize(serializer),
Value::Object(fields) => {
let mut map = serializer.serialize_map(Some(fields.len()))?;
for (k, v) in fields.iter() {
match k {
Value::String(_) => map.serialize_entry(k, v)?,
_ => {
let key_str = serde_json::to_string(k).map_err(Error::custom)?;
map.serialize_entry(&key_str, v)?
}
}
}
map.end()
}
// Delegate to the Object/Set serializers — single canonical path,
// handles non-string-key stringification internally.
Value::Object(fields) => fields.serialize(serializer),
// display set as an array
Value::Set(s) => s.serialize(serializer),
@@ -345,7 +352,7 @@ impl Value {
/// assert_eq!(array[4], Value::from(12345u64));
/// let obj = array[5].as_object().expect("not an object");
/// assert_eq!(obj.len(), 1);
/// assert_eq!(obj[&Value::from("name")], Value::from("regorus"));
/// assert_eq!(obj.get(&Value::from("name")).expect("missing name"), &Value::from("regorus"));
/// # Ok(())
/// # }
/// ```
@@ -800,7 +807,7 @@ impl From<BTreeMap<Value, Value>> for Value {
/// # Ok(())
/// # }
fn from(s: BTreeMap<Value, Value>) -> Self {
Value::Object(Rc::new(s))
Value::Object(Rc::new(Object::from(s)))
}
}
@@ -1279,16 +1286,16 @@ impl Value {
}
}
/// Cast value to [`& BTreeMap<Value, Value>`] if [`Value::Object`].
/// Cast value to [`&Object`] if [`Value::Object`].
/// ```
/// # use regorus::*;
/// # use std::collections::BTreeMap;
/// # use regorus::value::Object;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(
/// [(Value::from("Hello"), Value::from("World"))]
/// .iter()
/// .cloned()
/// .collect::<BTreeMap<Value, Value>>(),
/// .collect::<Object>(),
/// );
/// assert_eq!(
/// v.as_object()?.iter().next(),
@@ -1296,28 +1303,28 @@ impl Value {
/// );
/// # Ok(())
/// # }
pub fn as_object(&self) -> Result<&BTreeMap<Value, Value>> {
pub fn as_object(&self) -> Result<&Object> {
match self {
Value::Object(m) => Ok(m),
_ => Err(anyhow!("not an object")),
}
}
/// Cast value to [`&mut BTreeMap<Value, Value>`] if [`Value::Object`].
/// Cast value to [`&mut Object`] if [`Value::Object`].
/// ```
/// # use regorus::*;
/// # use std::collections::BTreeMap;
/// # use regorus::value::Object;
/// # fn main() -> anyhow::Result<()> {
/// let mut v = Value::from(
/// [(Value::from("Hello"), Value::from("World"))]
/// .iter()
/// .cloned()
/// .collect::<BTreeMap<Value, Value>>(),
/// .collect::<Object>(),
/// );
/// v.as_object_mut()?.insert(Value::from("Good"), Value::from("Bye"));
/// # Ok(())
/// # }
pub fn as_object_mut(&mut self) -> Result<&mut BTreeMap<Value, Value>> {
pub fn as_object_mut(&mut self) -> Result<&mut Object> {
match self {
Value::Object(m) => Ok(Rc::make_mut(m)),
_ => Err(anyhow!("not an object")),
@@ -1325,6 +1332,13 @@ impl Value {
}
}
/// Depth cap for `deep_merge`/`check_mergeable`, set at serde_json's default recursion limit.
///
/// Prevents a stack overflow from adversarially nested data — an uncatchable abort that poisons
/// every engine in an FFI process. At serde_json's limit it only backstops `Value`s built without
/// a parse-time cap: the Python/Ruby native bindings, or programmatic construction.
const MAX_MERGE_DEPTH: usize = 128;
impl Value {
pub(crate) fn make_or_get_value_mut<'a>(&'a mut self, paths: &[&str]) -> Result<&'a mut Value> {
if paths.is_empty() {
@@ -1358,6 +1372,11 @@ impl Value {
}
}
/// Shallow-merge `new` into `self` with strict rule-output semantics.
///
/// Objects merge one level deep: a key on both sides must hold the *same* value or it is a
/// conflict; sets union; equal values are a no-op. Non-recursive by design — data documents
/// use [`Value::deep_merge`] instead.
pub(crate) fn merge(&mut self, mut new: Value) -> Result<()> {
if self == &new {
return Ok(());
@@ -1365,24 +1384,26 @@ impl Value {
match (self, &mut new) {
(v @ Value::Undefined, _) => *v = new,
(Value::Set(ref mut set), Value::Set(new)) => {
Rc::make_mut(set).append(Rc::make_mut(new));
// Enforce allocator limit after merging set entries.
// Union without deep-cloning the RHS set (see `deep_merge`).
let dst = Rc::make_mut(set);
match Rc::try_unwrap(core::mem::take(new)) {
Ok(owned) => dst.extend(owned),
Err(shared) => dst.extend(shared.iter().cloned()),
}
enforce_limit_anyhow()?;
}
(Value::Object(map), Value::Object(new)) => {
for (k, v) in new.iter() {
match map.get(k) {
Some(pv) if *pv != *v => {
bail!(
"value for key `{}` generated multiple times: `{}` and `{}`",
serde_json::to_string_pretty(&k).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&pv).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&v).map_err(anyhow::Error::msg)?,
)
}
// Same key, different value: the rule produced two outputs for one path.
Some(pv) if *pv != *v => bail!(
"value for key `{}` generated multiple times: `{}` and `{}`",
serde_json::to_string_pretty(&k).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&pv).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&v).map_err(anyhow::Error::msg)?,
),
_ => {
Rc::make_mut(map).insert(k.clone(), v.clone());
// Enforce allocator limit after merging object entries.
enforce_limit_anyhow()?;
}
};
@@ -1392,6 +1413,151 @@ impl Value {
};
Ok(())
}
/// Recursively deep-merge `new` into `self` — the data-document merge behind [`Engine::add_data`].
///
/// Objects recurse per-key, sets union, equal values are a no-op, any other differing pair
/// conflicts. Set-union is a regorus extension (OPA data is JSON, which has no sets). Distinct
/// from the strict, non-recursive [`Value::merge`] used for rule outputs — use deep-merge ONLY
/// for data documents.
///
/// [`Engine::add_data`]: crate::Engine::add_data
pub(crate) fn deep_merge(&mut self, new: Value) -> Result<()> {
self.deep_merge_at(new, 0)
}
/// Depth-tracked worker for [`deep_merge`](Value::deep_merge). See [`MAX_MERGE_DEPTH`].
fn deep_merge_at(&mut self, mut new: Value, depth: usize) -> Result<()> {
if depth >= MAX_MERGE_DEPTH {
bail!("data merge exceeds maximum nesting depth of {MAX_MERGE_DEPTH}");
}
if self == &new {
return Ok(());
}
match (self, &mut new) {
(v @ Value::Undefined, _) => *v = new,
(Value::Set(ref mut set), Value::Set(new)) => {
// Union without deep-cloning the RHS set: move elements if uniquely owned,
// else clone only the element handles (`Rc` bumps), never the whole `BTreeSet`.
let dst = Rc::make_mut(set);
match Rc::try_unwrap(core::mem::take(new)) {
Ok(owned) => dst.extend(owned),
Err(shared) => dst.extend(shared.iter().cloned()),
}
enforce_limit_anyhow()?;
}
(Value::Object(map), Value::Object(new)) => {
// What each incoming key requires of the target map. Decided from a read-only
// probe so a no-op or a conflict never triggers `Rc::make_mut` (and never clones
// a shared map); `make_mut` is taken lazily, only when a key actually mutates.
enum Step {
Skip,
Insert,
Recurse,
Conflict,
}
for (k, v) in new.iter() {
let step = match map.get(k) {
None => Step::Insert,
Some(existing) if existing == v => Step::Skip,
Some(existing)
if matches!(
(existing, v),
(Value::Object(_), Value::Object(_))
| (Value::Set(_), Value::Set(_))
) =>
{
Step::Recurse
}
Some(_) => Step::Conflict,
};
match step {
Step::Skip => {}
Step::Insert => {
Rc::make_mut(map).insert(k.clone(), v.clone());
enforce_limit_anyhow()?;
}
// Both sides are containers: recurse so nested objects merge rather than
// the subtree being replaced (OPA data-merge semantics).
Step::Recurse => {
let existing = Rc::make_mut(map).get_mut(k).ok_or_else(|| {
anyhow!("internal error: key vanished during merge")
})?;
existing.deep_merge_at(v.clone(), depth.saturating_add(1))?;
}
Step::Conflict => {
let existing = map.get(k).ok_or_else(|| {
anyhow!("internal error: key vanished during merge")
})?;
bail!(
"value for key `{}` generated multiple times: `{}` and `{}`",
serde_json::to_string_pretty(&k).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&existing)
.map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&v).map_err(anyhow::Error::msg)?,
)
}
}
}
}
_ => bail!("error: could not merge value"),
};
Ok(())
}
/// Read-only check that [`deep_merge`](Value::deep_merge)-ing `other` into `self` would not
/// conflict, without mutating or allocating.
///
/// Lets [`Engine::add_data`] validate before merging in place. Since a conflict is the only
/// way the default-build merge can fail and it depends only on the inputs, a passing scan
/// guarantees the in-place `deep_merge` won't fail — avoiding the alternative of cloning the
/// whole document into a candidate just to validate. Only overlapping keys are walked, so
/// disjoint additions are near-free.
///
/// [`Engine::add_data`]: crate::Engine::add_data
#[cfg(not(feature = "allocator-memory-limits"))]
pub(crate) fn check_mergeable(&self, other: &Value) -> Result<()> {
self.check_mergeable_at(other, 0)
}
/// Depth-tracked worker for [`check_mergeable`](Value::check_mergeable). See [`MAX_MERGE_DEPTH`].
#[cfg(not(feature = "allocator-memory-limits"))]
fn check_mergeable_at(&self, other: &Value, depth: usize) -> Result<()> {
if depth >= MAX_MERGE_DEPTH {
bail!("data merge exceeds maximum nesting depth of {MAX_MERGE_DEPTH}");
}
if self == other {
return Ok(());
}
match (self, other) {
(Value::Undefined, _) => Ok(()),
// Set union never conflicts.
(Value::Set(_), Value::Set(_)) => Ok(()),
(Value::Object(dst), Value::Object(src)) => {
for (k, sv) in src.iter() {
// Only overlapping keys can conflict.
if let Some(dv) = dst.get(k) {
let both_mergeable = matches!(
(dv, sv),
(Value::Object(_), Value::Object(_)) | (Value::Set(_), Value::Set(_))
);
if both_mergeable {
dv.check_mergeable_at(sv, depth.saturating_add(1))?;
} else if dv != sv {
bail!(
"value for key `{}` generated multiple times: `{}` and `{}`",
serde_json::to_string_pretty(&k).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&dv).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&sv).map_err(anyhow::Error::msg)?,
)
}
}
}
Ok(())
}
_ => bail!("error: could not merge value"),
}
}
}
impl ops::Index<&Value> for Value {

Some files were not shown because too many files have changed in this diff Show More