Compare commits

...

47 Commits

Author SHA1 Message Date
Jay Lorch
5112ccf492 Verus verification 2026-04-06 11:31:41 -07:00
Anand Krishnamoorthi
8f740e2f6f feat(azure-policy): add policy rule and policy definition parsers (#660)
Extend the Azure Policy parser to handle complete policyRule and
policyDefinition JSON structures, not just standalone constraints.

Policy rule parser (policy_rule.rs):
- Parse top-level { "if": ..., "then": ... } objects
- Extract effect kind (deny, audit, append, modify, etc.) into typed AST
- Parse "details" structurally when it is an object to pull out
  existenceCondition as a first-class Constraint; fall back to opaque
  JSON for non-object details (e.g. append array form)
- Detect duplicate/missing keys for "if", "then", "effect", "details"

Policy definition parser (policy_definition.rs):
- Handle both wrapped ARM envelope ({ "properties": { ... } }) and
  unwrapped (properties-level keys at top level) forms
- Type-extract displayName, description, mode, metadata, parameters,
  and policyRule; everything else goes into extra
- Parse parameter definitions with type, defaultValue, allowedValues,
  and metadata; detect duplicate parameter names
- Duplicate key detection throughout

Grammar documentation (docs/azure-policy/azurepolicy.ebnf):
- Add formal EBNF grammar covering policy-rule, then-block,
  constraints, conditions, all 19 operators, count expressions,
  JSON values, and ARM template expressions

Test harness changes:
- Add parse_level field to YAML test cases: "constraint" (default),
  "policy_rule", or "policy_definition"
- Un-skip three parse_errors cases that needed policy_rule-level parsing
- Add policy_rule.yaml with 12 cases covering all 9 effect kinds,
  existenceCondition, parameterized effects, complex conditions, and
  extra key handling
- Add policy_definition.yaml with wrapped, unwrapped, parameterized,
  missing-policyRule, and duplicate-key error cases
2026-04-06 11:36:24 -05:00
Anand Krishnamoorthi
687be2850b feat: add Azure Policy constraint parser (#658)
Add constraint.rs module that parses Azure Policy JSON constraints
into span-annotated AST nodes:

- Logical combinators: allOf, anyOf, not
- Leaf conditions: field/value with all 19 operators
- Count blocks: field-count and value-count with where clauses

Public API: parse_constraint() parses a standalone constraint from JSON.

Includes YAML-driven test suite with 6 test files covering operators,
fields, expressions, logical combinators, count, and parse errors.
2026-04-03 19:09:51 -05:00
Anand Krishnamoorthi
95bffcb5f9 feat(rvm): extend program metadata and bump serialization to v6 (#654)
Add typed metadata support to RVM programs so that language frontends
can store language identity and arbitrary annotations alongside the
compiled bytecode.

Program metadata:
- Add `language` field to identify the source language (e.g. "rego",
  "azure_policy") so the VM can adjust semantics at runtime
- Add `annotations` map (BTreeMap<String, MetadataValue>) for
  frontend-specific key-value metadata
- Add MetadataValue enum with String, Bool, Integer, Float, Array,
  and Object variants, plus full serde support
- Add to_value() conversion for runtime access from VM instructions
- Add has_host_await flag with recompute_host_await_presence()

Serialization:
- Bump binary format version from 5 to 6
- Add JSON serialization for the new metadata fields

Assembly listing:
- Display language and annotations in the program header

Compiler:
- Track has_host_await during Rego compilation
2026-04-03 12:53:42 -05:00
dependabot[bot]
db8a9abf13 build(deps): bump minitest in /bindings/ruby in the per-dependency group (#656)
Bumps the per-dependency group in /bindings/ruby with 1 update: [minitest](https://github.com/minitest/minitest).


Updates `minitest` from 6.0.2 to 6.0.3
- [Changelog](https://github.com/minitest/minitest/blob/master/History.rdoc)
- [Commits](https://github.com/minitest/minitest/compare/v6.0.2...v6.0.3)

---
updated-dependencies:
- dependency-name: minitest
  dependency-version: 6.0.3
  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-04-02 13:08:40 -05:00
dependabot[bot]
421ee6af9b build(deps): bump the rust-dependencies group across 2 directories with 3 updates (#657)
Bumps the rust-dependencies group with 2 updates in the / directory: [toml_edit](https://github.com/toml-rs/toml) and [zip](https://github.com/zip-rs/zip2).
Bumps the rust-dependencies group with 1 update in the /bindings/wasm directory: [wasm-bindgen-test](https://github.com/wasm-bindgen/wasm-bindgen).


Updates `toml_edit` from 0.25.8+spec-1.1.0 to 0.25.10+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.25.8...v0.25.10)

Updates `zip` from 8.4.0 to 8.5.0
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v8.4.0...v8.5.0)

Updates `wasm-bindgen-test` from 0.3.66 to 0.3.67
- [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: toml_edit
  dependency-version: 0.25.10+spec-1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: zip
  dependency-version: 8.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: wasm-bindgen-test
  dependency-version: 0.3.67
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-02 13:08:12 -05:00
Anand Krishnamoorthi
64f71dee34 feat: add Azure Policy core JSON parser and expression parser (#655)
Add the foundational parsing infrastructure for Azure Policy JSON:

- ExprParser: ARM template expression parser for "[...]" strings,
  supporting function calls, dot access, index access, and literals
- Parser (core): recursive-descent JSON tokenizer-to-AST parser that
  reads directly from Lexer tokens with no intermediate serde_json step
- ParseError: structured error types with span context for diagnostics
- Helper functions: field classification, operator kind parsing, and
  ARM template expression detection

These components are consumed by the policy-aware parsing modules
(constraint, policy_rule, policy_definition) in a subsequent PR.
2026-04-02 11:42:03 -05:00
Anand Krishnamoorthi
648ba40126 feat: add Azure Policy AST types (#653)
Add span-annotated AST types for Azure Policy conditions and rules.

- PolicyDefinition, PolicyRule with if/then/details structure
- Condition enum: field conditions, value conditions, logical
  combinators (allOf, anyOf, not), and count expressions
- Operator enums for all 19 Azure Policy constraint operators
  (equals, contains, greater, matchInsensitively, etc.)
- Expr enum for field references, literal values (number, string,
  bool), template function calls, and policy function invocations
- Value types with span tracking for error reporting
2026-04-01 11:54:28 -05:00
Anand Krishnamoorthi
126cc12eb5 refactor: consolidate RVM instruction variants and clean up VM internals (#651)
Merge the three separate Assert* instructions (AssertNot, AssertCondition,
AssertNotUndefined) into a single `Guard { register, mode }` instruction
with a GuardMode enum. This cuts duplicated match arms across display,
listing, parser, dispatch, and all compiler emit sites.

Drop the unnecessary `#[repr(C)]` from the Instruction enum. It was never
exposed across FFI, so the C-compatible 4-byte discriminant was pure waste.
Without it Rust picks a 1-byte discriminant, shrinking every instruction
from 8 bytes to 6. A new `instruction_size` unit test locks this at 6.

While touching these files, also clean up several long-standing issues:

- Deduplicate the iteration-state setup in loops.rs by extracting a shared
  resolve_iteration_state() helper -- the stack-based and stackless paths
  had near-identical 40-line blocks.
- Collapse the ExitWithSuccess / ExitWithFailure match arms into one.
- In rules.rs, stop cloning Arc<Program> just to borrow a RuleInfo -- clone
  the small RuleInfo struct directly and extract a get_rule_info() helper.
- Move the memory check into dispatch (runs per instruction) and remove the
  now-dead enforce_memory_check() entry-point calls.
- Apply map_or_else style throughout listing.rs for consistency.
2026-04-01 05:34:33 -05:00
dependabot[bot]
1a8fc08773 build(deps): bump wasm-bindgen-test (#650)
Bumps the rust-dependencies group with 1 update in the /bindings/wasm directory: [wasm-bindgen-test](https://github.com/wasm-bindgen/wasm-bindgen).


Updates `wasm-bindgen-test` from 0.3.65 to 0.3.66
- [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: wasm-bindgen-test
  dependency-version: 0.3.66
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 11:56:14 -05:00
dependabot[bot]
c164917d63 build(deps): bump rb_sys in /bindings/ruby in the per-dependency group (#649)
Bumps the per-dependency group in /bindings/ruby with 1 update: [rb_sys](https://github.com/oxidize-rb/rb-sys).


Updates `rb_sys` from 0.9.124 to 0.9.125
- [Release notes](https://github.com/oxidize-rb/rb-sys/releases)
- [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.124...v0.9.125)

---
updated-dependencies:
- dependency-name: rb_sys
  dependency-version: 0.9.125
  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-03-31 11:55:45 -05:00
dependabot[bot]
6a6cc659b7 build(deps): bump the rust-dependencies group across 3 directories with 4 updates (#647)
Bumps the rust-dependencies group with 2 updates in the / directory: [toml_edit](https://github.com/toml-rs/toml) and [zip](https://github.com/zip-rs/zip2).
Bumps the rust-dependencies group with 1 update in the /bindings/python directory: [ordered-float](https://github.com/reem/rust-ordered-float).
Bumps the rust-dependencies group with 1 update in the /bindings/wasm directory: [wasm-bindgen-test](https://github.com/wasm-bindgen/wasm-bindgen).


Updates `toml_edit` from 0.22.27 to 0.25.8+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.22.27...v0.25.8)

Updates `zip` from 0.6.6 to 8.4.0
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/commits/v8.4.0)

Updates `ordered-float` from 5.2.0 to 5.3.0
- [Release notes](https://github.com/reem/rust-ordered-float/releases)
- [Commits](https://github.com/reem/rust-ordered-float/compare/v5.2.0...v5.3.0)

Updates `wasm-bindgen-test` from 0.3.64 to 0.3.65
- [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: toml_edit
  dependency-version: 0.25.8+spec-1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: zip
  dependency-version: 8.4.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: rust-dependencies
- dependency-name: ordered-float
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: wasm-bindgen-test
  dependency-version: 0.3.65
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 07:16:10 -05:00
dependabot[bot]
a86cf1119f ci(deps): bump the github-actions group across 1 directory with 3 updates (#646)
Bumps the github-actions group with 3 updates in the / directory: [actions/setup-go](https://github.com/actions/setup-go), [github/codeql-action](https://github.com/github/codeql-action) and [ruby/setup-ruby](https://github.com/ruby/setup-ruby).


Updates `actions/setup-go` from 6.3.0 to 6.4.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](4b73464bb3...4a3601121d)

Updates `github/codeql-action` from 4.34.1 to 4.35.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](3869755554...c10b8064de)

Updates `ruby/setup-ruby` from 1.295.0 to 1.299.0
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb)
- [Commits](319994f95f...3ff19f5e2b)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 4.35.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: ruby/setup-ruby
  dependency-version: 1.299.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 07:15:45 -05:00
Anand Krishnamoorthi
989ca6df2e ci(dependabot): restore cargo dependency grouping (#645)
Without grouping, dependabot creates a separate PR per directory for the
same dependency. Each individual PR fails to build due to version skew
across the root workspace and binding crates.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-30 18:44:53 -05:00
Anand Krishnamoorthi
d36f952133 feat(azure-policy): add alias normalization and denormalization (#635)
* feat: add Azure Policy alias normalization/denormalization

Add normalizer and denormalizer for ARM JSON resources, enabling Azure
Policy alias short names to become direct paths into a flat structure.

- Normalizer: flattens properties wrappers, lowercases keys, resolves
  per-alias versioned ARM paths, handles sub-resource array flattening,
  element-level field remaps, and array base renames
- Denormalizer: reverses all transformations with casing restoration
- AliasRegistry: loads production alias catalogs and data policy manifests
- Types: serde deserialization for ARM provider alias formats
- YAML test suite: 13 test files covering normalize, denormalize, round-trip,
  data-plane, edge cases, malformed input, sub-resources, and registry API
- Benchmark suite for normalization performance

* feat: add FFI and C# bindings for alias normalization

- FFI: alias_registry.rs with C-compatible API for loading catalogs,
  normalizing resources, and denormalizing back to ARM JSON
- C#: AliasRegistry wrapper class with NativeMethods P/Invoke bindings
  and integration tests
- Updated Cargo.lock files for new serde_json dependency
2026-03-30 18:44:36 -05:00
Anand Krishnamoorthi
35fb5d5953 Fix build break (#634)
* fix: update bindings and builtins for breaking dependency upgrades

- Update rand 0.10 API: use RngExt trait instead of removed Rng trait
- Update jsonschema 0.45 API: replace removed BasicOutput/apply with
  iter_errors for schema validation
- Update jni 0.22 API: migrate from deprecated JNIEnv to EnvUnowned
  with_env pattern, replace deprecated get_string/new_string/throw
  methods with their modern equivalents
- Update pyo3 0.28 API: replace removed PyObject with Py<PyAny>,
  deprecated downcast with cast, and removed with_gil with attach

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* ci(dependabot): create per-dependency PRs for Cargo updates

Remove the groups.rust-dependencies catch-all group so Dependabot
opens a separate PR for each Cargo dependency update instead of
bundling them all into a single PR.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix: enable getrandom wasm_js feature for wasm32-unknown-unknown builds

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix: address PR review comments

- Stream iter_errors directly into BTreeSet without intermediate Vec
- Use JNI_TRUE/JNI_FALSE for jboolean instead of bool coercion

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-27 12:34:49 -05:00
dependabot[bot]
296b34171a build(deps): bump the rust-dependencies group across 5 directories with 16 updates (#633)
* build(deps): bump the rust-dependencies group across 5 directories with 16 updates

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

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.100` | `1.0.102` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.9.8` | `0.10.0` |
| [regex](https://github.com/rust-lang/regex) | `1.12.2` | `1.12.3` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.20.0` | `1.22.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.30.0` | `0.45.0` |
| [chrono](https://github.com/chronotope/chrono) | `0.4.43` | `0.4.44` |
| [ipnet](https://github.com/krisprice/ipnet) | `2.11.0` | `2.12.0` |
| [rand](https://github.com/rust-random/rand) | `0.9.2` | `0.10.0` |
| [clap](https://github.com/clap-rs/clap) | `4.5.56` | `4.5.60` |
| [criterion](https://github.com/criterion-rs/criterion.rs) | `0.8.1` | `0.8.2` |
| [toml_edit](https://github.com/toml-rs/toml) | `0.22.27` | `0.25.8+spec-1.1.0` |
| [zip](https://github.com/zip-rs/zip2) | `0.6.6` | `8.4.0` |

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

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.100` | `1.0.102` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.9.8` | `0.10.0` |
| [regex](https://github.com/rust-lang/regex) | `1.12.2` | `1.12.3` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.20.0` | `1.22.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.30.0` | `0.45.0` |
| [chrono](https://github.com/chronotope/chrono) | `0.4.43` | `0.4.44` |
| [ipnet](https://github.com/krisprice/ipnet) | `2.11.0` | `2.12.0` |
| [rand](https://github.com/rust-random/rand) | `0.9.2` | `0.10.0` |
| [clap](https://github.com/clap-rs/clap) | `4.5.56` | `4.6.0` |

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

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.100` | `1.0.102` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.9.8` | `0.10.0` |
| [regex](https://github.com/rust-lang/regex) | `1.12.2` | `1.12.3` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.20.0` | `1.22.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.30.0` | `0.45.0` |
| [chrono](https://github.com/chronotope/chrono) | `0.4.43` | `0.4.44` |
| [ipnet](https://github.com/krisprice/ipnet) | `2.11.0` | `2.12.0` |
| [rand](https://github.com/rust-random/rand) | `0.9.2` | `0.10.0` |
| [jni](https://github.com/jni-rs/jni-rs) | `0.21.1` | `0.22.4` |

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

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.100` | `1.0.102` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.9.8` | `0.10.0` |
| [regex](https://github.com/rust-lang/regex) | `1.12.2` | `1.12.3` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.20.0` | `1.22.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.30.0` | `0.45.0` |
| [chrono](https://github.com/chronotope/chrono) | `0.4.43` | `0.4.44` |
| [ipnet](https://github.com/krisprice/ipnet) | `2.11.0` | `2.12.0` |
| [rand](https://github.com/rust-random/rand) | `0.9.2` | `0.10.0` |
| [ordered-float](https://github.com/reem/rust-ordered-float) | `5.1.0` | `5.2.0` |
| [pyo3](https://github.com/pyo3/pyo3) | `0.24.2` | `0.28.2` |

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

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.100` | `1.0.102` |
| [spin](https://github.com/mvdnes/spin-rs) | `0.9.8` | `0.10.0` |
| [regex](https://github.com/rust-lang/regex) | `1.12.2` | `1.12.3` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.20.0` | `1.22.0` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.30.0` | `0.45.0` |
| [chrono](https://github.com/chronotope/chrono) | `0.4.43` | `0.4.44` |
| [ipnet](https://github.com/krisprice/ipnet) | `2.11.0` | `2.12.0` |
| [rand](https://github.com/rust-random/rand) | `0.9.2` | `0.10.0` |
| [wasm-bindgen-test](https://github.com/wasm-bindgen/wasm-bindgen) | `0.3.58` | `0.3.64` |



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

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

Updates `regex` from 1.12.2 to 1.12.3
- [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.2...1.12.3)

Updates `uuid` from 1.20.0 to 1.22.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.20.0...v1.22.0)

Updates `jsonschema` from 0.30.0 to 0.45.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/rust-v0.30.0...ruby-v0.45.0)

Updates `chrono` from 0.4.43 to 0.4.44
- [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.43...v0.4.44)

Updates `ipnet` from 2.11.0 to 2.12.0
- [Release notes](https://github.com/krisprice/ipnet/releases)
- [Changelog](https://github.com/krisprice/ipnet/blob/master/RELEASES.md)
- [Commits](https://github.com/krisprice/ipnet/compare/2.11.0...2.12.0)

Updates `rand` from 0.9.2 to 0.10.0
- [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/rand_core-0.9.2...0.10.0)

Updates `clap` from 4.5.56 to 4.5.60
- [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.5.56...clap_complete-v4.5.60)

Updates `criterion` from 0.8.1 to 0.8.2
- [Release notes](https://github.com/criterion-rs/criterion.rs/releases)
- [Changelog](https://github.com/criterion-rs/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/criterion-rs/criterion.rs/compare/criterion-v0.8.1...criterion-v0.8.2)

Updates `toml_edit` from 0.22.27 to 0.25.8+spec-1.1.0
- [Commits](https://github.com/toml-rs/toml/compare/v0.22.27...v0.25.8)

Updates `zip` from 0.6.6 to 8.4.0
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/commits/v8.4.0)

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

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

Updates `regex` from 1.12.2 to 1.12.3
- [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.2...1.12.3)

Updates `uuid` from 1.20.0 to 1.22.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.20.0...v1.22.0)

Updates `jsonschema` from 0.30.0 to 0.45.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/rust-v0.30.0...ruby-v0.45.0)

Updates `chrono` from 0.4.43 to 0.4.44
- [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.43...v0.4.44)

Updates `ipnet` from 2.11.0 to 2.12.0
- [Release notes](https://github.com/krisprice/ipnet/releases)
- [Changelog](https://github.com/krisprice/ipnet/blob/master/RELEASES.md)
- [Commits](https://github.com/krisprice/ipnet/compare/2.11.0...2.12.0)

Updates `rand` from 0.9.2 to 0.10.0
- [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/rand_core-0.9.2...0.10.0)

Updates `clap` from 4.5.56 to 4.6.0
- [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.5.56...clap_complete-v4.5.60)

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

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

Updates `regex` from 1.12.2 to 1.12.3
- [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.2...1.12.3)

Updates `uuid` from 1.20.0 to 1.22.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.20.0...v1.22.0)

Updates `jsonschema` from 0.30.0 to 0.45.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/rust-v0.30.0...ruby-v0.45.0)

Updates `chrono` from 0.4.43 to 0.4.44
- [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.43...v0.4.44)

Updates `ipnet` from 2.11.0 to 2.12.0
- [Release notes](https://github.com/krisprice/ipnet/releases)
- [Changelog](https://github.com/krisprice/ipnet/blob/master/RELEASES.md)
- [Commits](https://github.com/krisprice/ipnet/compare/2.11.0...2.12.0)

Updates `rand` from 0.9.2 to 0.10.0
- [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/rand_core-0.9.2...0.10.0)

Updates `jni` from 0.21.1 to 0.22.4
- [Release notes](https://github.com/jni-rs/jni-rs/releases)
- [Changelog](https://github.com/jni-rs/jni-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jni-rs/jni-rs/compare/v0.21.1...v0.22.4)

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

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

Updates `regex` from 1.12.2 to 1.12.3
- [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.2...1.12.3)

Updates `uuid` from 1.20.0 to 1.22.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.20.0...v1.22.0)

Updates `jsonschema` from 0.30.0 to 0.45.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/rust-v0.30.0...ruby-v0.45.0)

Updates `chrono` from 0.4.43 to 0.4.44
- [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.43...v0.4.44)

Updates `ipnet` from 2.11.0 to 2.12.0
- [Release notes](https://github.com/krisprice/ipnet/releases)
- [Changelog](https://github.com/krisprice/ipnet/blob/master/RELEASES.md)
- [Commits](https://github.com/krisprice/ipnet/compare/2.11.0...2.12.0)

Updates `rand` from 0.9.2 to 0.10.0
- [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/rand_core-0.9.2...0.10.0)

Updates `ordered-float` from 5.1.0 to 5.2.0
- [Release notes](https://github.com/reem/rust-ordered-float/releases)
- [Commits](https://github.com/reem/rust-ordered-float/compare/v5.1.0...v5.2.0)

Updates `pyo3` from 0.24.2 to 0.28.2
- [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.24.2...v0.28.2)

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

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

Updates `regex` from 1.12.2 to 1.12.3
- [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.2...1.12.3)

Updates `uuid` from 1.20.0 to 1.22.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.20.0...v1.22.0)

Updates `jsonschema` from 0.30.0 to 0.45.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/rust-v0.30.0...ruby-v0.45.0)

Updates `chrono` from 0.4.43 to 0.4.44
- [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.43...v0.4.44)

Updates `ipnet` from 2.11.0 to 2.12.0
- [Release notes](https://github.com/krisprice/ipnet/releases)
- [Changelog](https://github.com/krisprice/ipnet/blob/master/RELEASES.md)
- [Commits](https://github.com/krisprice/ipnet/compare/2.11.0...2.12.0)

Updates `rand` from 0.9.2 to 0.10.0
- [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/rand_core-0.9.2...0.10.0)

Updates `wasm-bindgen-test` from 0.3.58 to 0.3.64
- [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: anyhow
  dependency-version: 1.0.102
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.12.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: ipnet
  dependency-version: 2.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: clap
  dependency-version: 4.5.60
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: criterion
  dependency-version: 0.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: toml_edit
  dependency-version: 0.25.8+spec-1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: zip
  dependency-version: 8.4.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.102
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.12.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: ipnet
  dependency-version: 2.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: clap
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.102
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.12.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: ipnet
  dependency-version: 2.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jni
  dependency-version: 0.22.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.102
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.12.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: ipnet
  dependency-version: 2.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: ordered-float
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: pyo3
  dependency-version: 0.28.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: anyhow
  dependency-version: 1.0.102
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: spin
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: regex
  dependency-version: 1.12.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: uuid
  dependency-version: 1.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: jsonschema
  dependency-version: 0.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: chrono
  dependency-version: 0.4.44
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: rust-dependencies
- dependency-name: ipnet
  dependency-version: 2.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: rand
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: rust-dependencies
- dependency-name: wasm-bindgen-test
  dependency-version: 0.3.64
  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-03-26 12:51:08 -05:00
Anand Krishnamoorthi
f9d54cd436 ci(dependabot): fix cargo config quoting (#632) 2026-03-26 11:18:09 -05:00
Anand Krishnamoorthi
5b60daabd9 feat: add Azure Policy builtins with YAML test suite (#630)
* feat: add Azure Policy builtins with YAML test suite

Implement ARM template functions for Azure Policy evaluation:

Builtins:
- String: indexOf, lastIndexOf, trim, format, split, startsWith, endsWith,
  padLeft, concat, replace, toLower, toUpper, substring, guid, uniqueString
- DateTime: dateTimeAdd, dateTimeFromEpoch, dateTimeToEpoch, addDays
- Collection: intersection, union, take, skip, first, last, min, max,
  range, items, tryGet, tryIndexFromEnd, empty, array, createObject
- Encoding: base64, base64ToString, base64ToJson, uri, uriComponent,
  uriComponentToString, dataUri, dataUriToString
- Numeric: int, float, intDiv, intMod
- Misc: json, join, bool, string, coalesce, if, getParameter, resolveField
- Logic: logicAll, logicAny

Key implementation details:
- Unicode case-insensitive search via ICU4X case folding with single-pass
  fold_with_char_map() for indexOf/lastIndexOf
- .NET composite formatting (System.String.Format) with alignment, standard
  and custom datetime format specifiers, numeric format specifiers
- DateTime round-trip preserves input shape (Z vs +00:00, T vs space,
  fractional seconds) when no explicit output format is supplied
- Zero-cost as_str() helper borrows directly from Value::String(Rc<str>)
- BTreeSet<&Value> in array union avoids redundant cloning

Test suite:
- 53 YAML test files exercising all builtins via direct BUILTINS registry
- Coverage for edge cases: empty inputs, Unicode, fractional seconds,
  invalid alignment, unknown format specifiers, RFC3339 offset shapes

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix: address PR review comments

- Fix percent_encode to only uppercase hex digits, not entire string
- Remove guid/uniqueString (unsupported); delete custom SHA-1 impl
- Replace unwrap_or(0) with proper error in format placeholder parsing
- Hoist CaseMapper into static CaseMapperBorrowed for zero per-call overhead
- Pre-allocate Vec in range() with_capacity
- Update bindings/ffi and bindings/ruby Cargo.lock
- Fix uri_component test expectations for correct case preservation

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix: address second round of PR review comments

- float(): return Undefined when as_f64() fails instead of leaking
  the original non-f64 representation
- createObject(): reject odd number of arguments with an error
  (ARM-template parity)
- format(): error on unknown numeric format specifiers instead of
  silently passing through (matches .NET FormatException behavior)
- format(): cap alignment width at 10,000 to prevent DoS from
  user-controlled format strings like {0,1000000000}
- Add YAML test cases for all new error behaviors

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix: address third round of PR review comments

- percent_decode: reject incomplete % escapes (e.g. "%", "%2") instead
  of treating them as literal characters
- parse_iso8601_duration: reject leftover digits without a unit designator
  at T boundary and end-of-input (e.g. "P1", "P1T2H")
- yaml_to_value: panic on unsupported YAML numeric representations instead
  of silently mapping to Null
- Revert unused src/languages/mod.rs changes (module is defined inline in
  lib.rs)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix: add missing edge-case tests and fix empty-delimiter panic

- fn_split: return input as single-element array for empty string
  delimiter instead of panicking (Rust's str::split("") panics)
- format: add test for F3 higher precision ({0:F3} + 1.23456 → 1.235)
- format: add test for N2 float with thousands separator
- format: add test for negative index error ({-1})
- split: add test for empty-string delimiter
- uri: add tests for query string and fragment in relative URI
- createObject: add test for non-string (numeric) keys

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix: address fourth round of PR review comments

- Add MAX_VARIADIC_ARGS (64) constant for variadic builtin arity
  instead of registering with 0 (logic_all, logic_any, min, max,
  format, intersection, union, coalesce, createObject); set
  dateTimeAdd to exact arity 3

- Switch indexOf/lastIndexOf to UTF-16 code-unit indices to match
  .NET String.IndexOf semantics (track ch.len_utf16() in
  fold_with_char_map, use encode_utf16().count() for empty-needle
  lastIndexOf)

- Use DateTime::<Utc>::from_timestamp for explicit timezone type

- Remove stale docs/azure-policy/casing.md link from module doc

- Fix misleading comment in want_error test branch (code bails on
  Undefined, not accepts it)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-25 17:32:39 -05:00
Anand Krishnamoorthi
f69974dc1b ci(dependabot): fix cargo workspace updates and refresh lockfiles (#629)
* ci(dependabot): fix cargo workspace updates and refresh lockfiles

Remove nested Cargo workspace members from Dependabot's cargo directories to avoid manifest resolution failures during grouped updates.

Add a Dependabot-only workflow that refreshes affected Cargo lockfiles, including the no_std target-specific resolution path, so CI can continue enforcing --locked and --frozen builds.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* ci(dependabot): address workflow review comments

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* ci(dependabot): address workflow permission and toolchain comments

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* ci(dependabot): stage no-std lockfile refresh

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* ci(dependabot): harden refresh workflow

* ci(dependabot): refine workflow gating and staging

* ci(dependabot): harden workflow git operations

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-25 16:58:29 -05:00
dependabot[bot]
942dd47163 build(deps): bump rubocop in /bindings/ruby in the per-dependency group (#622)
Bumps the per-dependency group in /bindings/ruby with 1 update: [rubocop](https://github.com/rubocop/rubocop).


Updates `rubocop` from 1.85.0 to 1.85.1
- [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.85.0...v1.85.1)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.85.1
  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-03-24 12:37:06 -05:00
dependabot[bot]
ac701b4933 ci(deps): bump the github-actions group with 11 updates (#628)
Bumps the github-actions group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `4` | `6` |
| [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) | `2.8.2` | `2.9.1` |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.2.0` | `6.3.0` |
| [actions/setup-dotnet](https://github.com/actions/setup-dotnet) | `5.1.0` | `5.2.0` |
| [actions/setup-node](https://github.com/actions/setup-node) | `6.2.0` | `6.3.0` |
| [github/codeql-action](https://github.com/github/codeql-action) | `4.32.4` | `4.34.1` |
| [ruby/setup-ruby](https://github.com/ruby/setup-ruby) | `1.288.0` | `1.295.0` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `6.0.0` | `7.0.0` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `7.0.0` | `8.0.1` |
| [PyO3/maturin-action](https://github.com/pyo3/maturin-action) | `1.50.0` | `1.50.1` |
| [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action) | `0.5.127` | `0.5.128` |


Updates `actions/checkout` from 4 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

Updates `Swatinem/rust-cache` from 2.8.2 to 2.9.1
- [Release notes](https://github.com/swatinem/rust-cache/releases)
- [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md)
- [Commits](779680da71...c19371144d)

Updates `actions/setup-go` from 6.2.0 to 6.3.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](7a3fe6cf4c...4b73464bb3)

Updates `actions/setup-dotnet` from 5.1.0 to 5.2.0
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](baa11fbfe1...c2fa09f4bd)

Updates `actions/setup-node` from 6.2.0 to 6.3.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](6044e13b5d...53b83947a5)

Updates `github/codeql-action` from 4.32.4 to 4.34.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](89a39a4e59...3869755554)

Updates `ruby/setup-ruby` from 1.288.0 to 1.295.0
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb)
- [Commits](09a7688d3b...319994f95f)

Updates `actions/upload-artifact` from 6.0.0 to 7.0.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](b7c566a772...bbbca2ddaa)

Updates `actions/download-artifact` from 7.0.0 to 8.0.1
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](37930b1c2a...3e5f45b2cf)

Updates `PyO3/maturin-action` from 1.50.0 to 1.50.1
- [Release notes](https://github.com/pyo3/maturin-action/releases)
- [Commits](b1bd829e37...04ac600d27)

Updates `MarcoIeni/release-plz-action` from 0.5.127 to 0.5.128
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](f708778669...1528104d2c)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: Swatinem/rust-cache
  dependency-version: 2.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/setup-go
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/setup-dotnet
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 4.34.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: ruby/setup-ruby
  dependency-version: 1.295.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: PyO3/maturin-action
  dependency-version: 1.50.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: MarcoIeni/release-plz-action
  dependency-version: 0.5.128
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-24 12:35:38 -05:00
Anand Krishnamoorthi
86088d2049 Consolidate Dependabot, fix #595 (mimalloc + indexmap), add feature-matrix CI (#627)
* build: consolidate dependabot cargo entries and add commit prefixes

Consolidate all 9 separate cargo ecosystem entries into a single entry
using the 'directories' key. This ensures Dependabot creates one PR per
dependency update across the root workspace and all bindings, preventing
version skew that caused build failures.

Also add semantic commit-message prefixes to all ecosystem entries:
- build(deps) for cargo, gomod, maven, nuget, pip, bundler
- ci(deps) for github-actions

Rename the cargo group to 'rust-dependencies' and the github-actions
group to 'github-actions' for clarity.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix: remove mimalloc from default features, fix indexmap/std propagation

Address #595: the vendored mimalloc allocator should not be imposed on
library consumers. Remove allocator-memory-limits and mimalloc from the
full-opa feature so that users of regorus as a library can choose their
own global allocator.

Bindings (ffi, java, python, ruby) that ship as standalone artifacts
continue to opt in to regorus/allocator-memory-limits explicitly so they
retain the performant allocator.

Also propagate indexmap/std via the std feature (using the indexmap?/std
weak-dependency syntax) so that users enabling std + rvm without default
features no longer hit 'IndexMap takes 3 generic arguments' errors.

Closes #595

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* ci: add feature-combination checks to PR CI and weekly matrix

PR CI (xtask): add cargo check for 5 non-default feature combos in
run_ci_suite(). These run on every PR and catch compile failures from
feature-gating issues (e.g. #595) with near-zero overhead.

Weekly workflow: new feature-matrix.yml runs cargo build + cargo test
across 9 feature combinations every Monday. Uses a GitHub Actions matrix
with fail-fast: false so all combos are tested even if one fails.

Combinations tested weekly:
- std,arc (minimal library)
- std,arc,rvm (common library usage)
- std,arc,full-opa (full-opa without mimalloc)
- std,arc,full-opa,allocator-memory-limits (binding-style)
- std,arc,rvm,regex,time,semver,cache (cherry-picked builtins)
- std,arc,rvm,coverage,cache (observability)
- std,arc,full-opa,azure_policy (Azure Policy)
- std,arc,full-opa,azure-rbac (Azure RBAC)
- arc,opa-no-std (no_std codepath)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix: gate benchmark memory-limit calls behind allocator-memory-limits feature

The set_global_memory_limit function is only available when the
allocator-memory-limits feature is enabled. After removing mimalloc
from the default feature set, the rvm_benchmark failed to compile.

Add #[cfg(feature = "allocator-memory-limits")] guards around the
call sites and the MEMORY_LIMIT_BYTES constant.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-24 11:54:01 -05:00
Anand Krishnamoorthi
83891d7782 RVM compiler & runtime optimizations: caching, instruction fusion, constant hoisting, and correctness fixes (#626)
* perf!: add LRU caches for compiled regex and glob patterns

Add bounded LRU caches for compiled regex and glob patterns used by
Rego builtins, avoiding repeated recompilation of the same patterns
during policy evaluation.

New `cache` feature (included in `full-opa` and `opa-no-std`) backed by
the `lru` crate (no_std compatible) with `spin::Mutex` for thread safety.

- `src/cache.rs`: generic `LruCache<V>` wrapper, global `REGEX_CACHE`
  (default capacity 256) and `GLOB_CACHE` (default capacity 128)
- `src/builtins/regex.rs`: all regex builtins route through the cache
- `src/builtins/glob.rs`: glob.match routes through the cache
- Public API: `regorus::cache::{Config, configure, clear}`

Compilation costs avoided per cache hit:
  regex  10-55 µs  (simple to complex patterns)
  glob   10-12 µs
  LRU hit   ~10 ns

BREAKING CHANGE: new `cache` Cargo feature added to `full-opa` and
`opa-no-std` feature sets; adds `lru` as a dependency.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(vm): amortize per-instruction memory and time limit checks

Deduplicate per-instruction memory_check calls by hoisting them to the
main dispatch loop, and amortize monotonic_now() syscalls in the
execution timer by checking elapsed time every N instructions instead
of on every tick.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix(vm): correct object membership to check values only, not keys

The Contains instruction for objects was checking both keys and values:

    object_fields.contains_key(v) || object_fields.values().any(|v| ...)

Per the Rego specification, `x in obj` tests whether x is a VALUE of
the object, not a key. The two-argument form `k, v in obj` is needed
to access keys. The interpreter already implemented this correctly
(values-only scan), but the RVM had the extra contains_key() check
which would incorrectly return true when the search value happened to
match a key name.

Remove the contains_key() branch so the behavior matches the interpreter
and the Rego spec. Add two regression tests:
- object_membership_checks_values_not_keys: "foo" in {"foo": "bar"}
  must be false (key, not a value)
- object_membership_finds_value: "bar" in {"foo": "bar"} must be true

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(compiler): hoist all-constant collection literals to the literal table

When an array, set, or object literal consists entirely of compile-time
constant expressions (numbers, strings, bools, null, and nested constant
collections), the compiler now evaluates them at compile time and emits a
single Load instruction from the literal table instead of generating
per-element instructions at runtime.

Previously, a Rego expression like `x in [1, 2, 3]` would emit
ArrayCreate + three Load + three ArrayAppend instructions, allocating a
new Vec and Rc on every evaluation. With this change, the entire array
is built once during compilation and loaded as a single constant.

This optimization applies to all three collection types:
- Array literals: avoids ArrayCreate + N x (Load + ArrayAppend)
- Set literals: avoids SetCreate + N x (Load + SetAdd)
- Object literals: avoids ObjectCreate + N x (Load + Load + ObjectInsert)

The implementation adds a try_eval_const() helper that recursively
evaluates an AST expression as a constant Value, returning None if any
sub-expression is non-constant. Each compile method for collection
literals attempts the all-constant fast path first and falls through to
the existing instruction-by-instruction codegen otherwise.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(compiler): fuse Eq + AssertCondition into AssertEq instruction

Add a new `AssertEq { left, right }` instruction that combines equality
comparison and condition assertion into a single operation. This replaces
the previous two-instruction pattern of `Eq { dest, left, right }` followed
by `AssertCondition { condition: dest }`, saving one instruction and one
register per equality assertion.

The fused instruction checks two registers for equality and directly calls
handle_condition with the result, avoiding the intermediate boolean
register entirely. If either operand is undefined or the values differ,
the condition fails and the rule/loop backtracks.

The optimization applies to four destructuring sites:
- EqualityCheck (assignment re-binding with `x = expr; x = expr`)
- EqualityExpr (destructuring against an expression)
- EqualityValue (destructuring against a literal value)
- assert_array_length (array length validation in destructuring)

In soft_assert_mode the compiler still emits the original Eq instruction
since the boolean result register is needed by callers.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(compiler): fuse Not + AssertCondition into AssertNot instruction

Add a new `AssertNot { operand }` instruction that combines logical
negation and condition assertion into a single operation. This replaces
the previous two-instruction pattern of `Not { dest, operand }` followed
by `AssertCondition { condition: dest }`, saving one instruction and one
register allocation.

The fused instruction checks the operand register and passes the
condition if the value is false or undefined (per Rego semantics where
`not expr` succeeds when the expression has no results or is false),
and fails the condition if the value is true or any non-boolean truthy
value.

This was the only emission site for the Not+AssertCondition pair,
occurring in the compilation of `Literal::NotExpr` statements.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(vm): early exit for same-value multi-definition rules

When a rule has multiple definitions that all produce the same value
(e.g. implicit true, or identical literal), set early_exit_on_first_success
on RuleInfo so the VM can stop after the first successful definition.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* feat!: expose cache configuration API to all language bindings

Add `set_cache_config` and `clear_cache` functions to every binding
so callers can tune or reset the global regex/glob pattern caches
introduced in the cache feature.

Bindings updated:
- FFI (C): `regorus_set_cache_config`, `regorus_clear_cache`
- C++ header: free functions `regorus::set_cache_config`, `regorus::clear_cache`
- Python: module-level `set_cache_config(*, regex, glob)`, `clear_cache()`
- Java: static methods on new `CacheConfig` class
- Go: package-level `SetCacheConfig`, `ClearCache`
- Ruby: module functions `Regorus.set_cache_config`, `Regorus.clear_cache`
- WASM: free functions `setCacheConfig`, `clearCache`
- C#: static methods `Engine.SetCacheConfig`, `Engine.ClearCache`

BREAKING CHANGE: Bump SERIALIZATION_VERSION from 4 to 5 due to new
AssertEq and AssertNot instruction variants added in the instruction
fusion commits. Programs serialized with version 5 cannot be loaded
by older versions of regorus.

* fix: address PR review feedback

Cache subsystem:
- Gate REGEX_CACHE and related imports behind #[cfg(feature = "regex")]
  so that building with --features cache without regex compiles correctly.
- Gate LruCache struct behind #[cfg(any(feature = "regex", feature = "glob"))].
- Add Config::MAX_CAPACITY (2^16) hard upper bound; clamp values in
  configure() to prevent unbounded cache growth.
- Use parking_lot::Mutex for std builds and spin::Mutex for no_std to
  avoid CPU spinning under contention in tight regex/glob eval loops.
- Narrow lock scopes in regex/glob builtins: release the mutex before
  compiling a pattern, then re-acquire to insert.

Java JNI binding:
- Fix cache config overflow: negative jlong values now saturate to 0
  and positive overflow saturates to usize::MAX (then clamped by
  MAX_CAPACITY) instead of silently disabling the cache.
- Gate JNI cache config/clear functions behind #[cfg(feature = "cache")].

Compiler:
- Refactor static_value_of_expr to delegate to try_eval_const,
  gaining support for negated numbers and constant collections.
- Make try_eval_const pub(in crate::languages::rego::compiler) and
  re-export through expressions.rs.
- Handle Expr::UnaryExpr with numeric literals in try_eval_const so
  collections containing negated numbers (e.g. [-1, 2]) are hoisted.

VM correctness:
- Fix Not instruction to follow Rego semantics: not expr yields
  true when expr is undefined or false, false for any other defined
  value (including non-booleans) -- no longer errors on non-boolean
  operands.
- Add enforce_memory_check() call at execute_suspendable_entry to
  ensure memory limits are checked before the first instruction.
- Update AssertNot listing comment to "exit if any defined truthy
  value" to match actual VM behaviour.
- Add doc comment on Not instruction clarifying Rego negation
  semantics.

Bindings:
- Fix C++ header indentation for set_cache_config / clear_cache.
- Propagate Cargo.lock parking_lot addition across ffi, java, python,
  and wasm binding lockfiles.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-23 21:00:51 -05:00
antmhs
898643129e feat: make policy length limits configurable per engine (#624)
- Add PolicyLengthConfig struct with max_col, max_file_bytes, and
  max_lines fields, replacing hardcoded constants in the lexer.
- Add Engine::set_policy_length_config and clear_policy_length_config
  to allow callers to override the default limits.
- Add Source::from_contents_with_limits and from_file_with_limits for
  direct Source construction with custom limits; existing from_contents
  and from_file signatures are preserved using defaults.
- Add tests for default rejection, custom limits, and engine plumbing.
- Add bindings for C, C++, Python, WASM/JS, Java, Ruby, C#, Go
2026-03-13 12:19:57 -05:00
Anand Krishnamoorthi
50c0215fdb Rvm optimizations (#620)
* perf(rvm): fix O(n²) comprehension yield by mutating in-place

Instead of cloning the entire accumulator collection on every yield
iteration, use take_register + Rc::make_mut to get exclusive ownership
and mutate in-place. This reduces comprehension yield from O(n²) to O(n)
for both run-to-completion and suspendable execution modes.

- Add RegoVM::take_register() helper that swaps register with Undefined
- Comprehension yield now takes the accumulator, mutates via Rc::make_mut,
  and writes back — avoiding deep clones when refcount == 1

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(rvm): use take_register for ObjectSet, ArrayPush, SetAdd

These instructions were cloning the container register (bumping Rc to 2),
then calling as_object_mut/as_array_mut/as_set_mut which invokes
Rc::make_mut — deep-cloning the entire collection since refcount > 1.

Use take_register instead so the Rc refcount stays at 1, making
Rc::make_mut a no-op and allowing in-place mutation.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(rvm): remove unnecessary clones in rule caching

- execute_call_rule_common: move final_value into cache instead of
  cloning, since it is not used afterwards
- finalize_rule_frame_data: add comment clarifying the clone is needed
  because the value is both cached and returned
- Remove unnecessary .clone() on result_from_rule when setting register

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* rvm: avoid RuleInfo clone per rule call

Replace RuleInfo.clone() (which heap-allocates name, destructuring_blocks, and
potentially function_info) with a cheap Arc<Program> clone (atomic refcount
bump) followed by borrowing &RuleInfo from the local Arc. This eliminates
per-rule-call heap allocations.

Sites changed:
- execute_call_rule_common: Arc clone + borrow
- execute_call_rule_suspendable: Arc clone + borrow
- finalize_rule_frame_data: Arc clone + borrow
- handle_rule_break_event: inline Arc clone + borrow (was get_rule_info)
- handle_rule_error_event: inline Arc clone + borrow (was get_rule_info)
- Removed now-unused get_rule_info method

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* rvm: replace bincode with postcard for serialization

Remove unlinked bincode dependency. Use postcard (already a dep for rvm feature)
for all binary serialization/deserialization in program serialization and tests.

Also adds rvm_benchmark benchmark.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(rvm): cache dummy Span/Expr for builtin calls

Every builtin call was allocating a Source (via from_contents), a Span, and
N Ref<Expr> wrappers just to satisfy the builtin function signature. These
dummy values are only used for error reporting context.

Cache the dummy Span and Vec<Ref<Expr>> on the RegoVM struct. The Source and
Span are created once on first builtin call; dummy Expr entries grow as
needed and are reused across calls via mem::take/put-back pattern.

This eliminates per-builtin-call heap allocations for Source (Rc + String +
Vec<lines>), Span clones, and Rc<Expr> wrappers.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* perf(rvm): round 2 allocation reduction in builtins, entry points, virtual data

- Cache builtin args Vec on RegoVM (mem::take/clear/put-back pattern)
- Restructure builtins_cache as two-level map for clone-free lookup
- Use IndexMap::get_index() in execute_entry_point_by_index
- Use mutable Vec path stack in traverse_rule_tree_subobject (push/pop)
- Walk data tree and rule-result paths by reference, clone only leaf
- Use mem::replace in resume() instead of cloning ExecutionState

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* fix(rvm): address PR review feedback

- Restore cached_builtin_args on all error/early-return paths in
  execute_builtin_call to preserve allocation reuse
- Use 1-based line/col and \"<builtin>\" filename in dummy span for
  clearer diagnostics
- Restore result register before returning errors in comprehension
  mode-mismatch branches (both run-to-completion and suspendable)
- Avoid clone in resume() invalid-state error path by formatting
  debug string before moving state back

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-11 21:39:58 -05:00
Anand Krishnamoorthi
ee3dff9a3d fix(ci): skip mimalloc FFI and disable isolation for Miri (#621)
- Add cfg(not(miri)) guards to mimalloc module, global allocator, and
  allocator-memory-limits code paths so Miri falls back to the default
  system allocator instead of calling unsupported FFI functions.
- Set MIRIFLAGS="-Zmiri-disable-isolation" in the workflow so tests
  that perform file I/O can run under Miri.
- Skip units/parse tests under Miri due to Float-vs-BigInt Number
  representation mismatch with Miri's soft-float emulation.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-11 15:13:51 -05:00
dependabot[bot]
b8e15f46f3 build(deps): bump rubocop in /bindings/ruby in the per-dependency group (#618)
Bumps the per-dependency group in /bindings/ruby with 1 update: [rubocop](https://github.com/rubocop/rubocop).


Updates `rubocop` from 1.84.2 to 1.85.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.84.2...v1.85.0)

---
updated-dependencies:
- dependency-name: rubocop
  dependency-version: 1.85.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  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-03-05 15:10:45 -06:00
Anand Krishnamoorthi
37144968c8 chore(ci): add miri workflow (#581)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-05 13:18:25 -06:00
Anand Krishnamoorthi
7ee503ccdc chore(ci): add cargo audit and deny (#580)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-05 13:18:00 -06:00
Anand Krishnamoorthi
006e819d52 rvm: switch binary serialization to postcard (#582)
Move RVM binary encoding from bincode to postcard and bump the format version. Update test helpers, docs, changelog, and refresh lockfiles after the swap.

Closes #575

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-03-03 15:09:45 -06:00
dependabot[bot]
b6f11c5602 build(deps-dev): bump org.apache.maven.plugins:maven-surefire-plugin (#605)
Bumps the per-dependency group in /bindings/java with 1 update: [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire).


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

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-version: 3.5.5
  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-02-27 16:52:44 -06:00
dependabot[bot]
72033e77da build(deps): bump bytes (#569)
Bumps the cargo group with 1 update in the /bindings/java directory: [bytes](https://github.com/tokio-rs/bytes).


Updates `bytes` from 1.11.0 to 1.11.1
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 16:52:14 -06:00
dependabot[bot]
be34063dba build(deps): bump the per-dependency group with 2 updates (#603)
Bumps the per-dependency group with 2 updates: [github/codeql-action](https://github.com/github/codeql-action) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `github/codeql-action` from 4.32.2 to 4.32.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](45cbd0c69e...9e907b5e64)

Updates `MarcoIeni/release-plz-action` from 0.5.126 to 0.5.127
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](52440b50d3...f708778669)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: per-dependency
- dependency-name: MarcoIeni/release-plz-action
  dependency-version: 0.5.127
  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-02-27 16:51:21 -06:00
dependabot[bot]
04bf417c06 build(deps): bump the per-dependency group across 1 directory with 3 updates (#607)
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 [rubocop-minitest](https://github.com/rubocop/rubocop-minitest).


Updates `minitest` from 6.0.1 to 6.0.2
- [Changelog](https://github.com/minitest/minitest/blob/master/History.rdoc)
- [Commits](https://github.com/minitest/minitest/compare/v6.0.1...v6.0.2)

Updates `rubocop` from 1.84.1 to 1.84.2
- [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.84.1...v1.84.2)

Updates `rubocop-minitest` from 0.38.2 to 0.39.1
- [Release notes](https://github.com/rubocop/rubocop-minitest/releases)
- [Changelog](https://github.com/rubocop/rubocop-minitest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-minitest/compare/v0.38.2...v0.39.1)

---
updated-dependencies:
- dependency-name: minitest
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: per-dependency
- dependency-name: rubocop
  dependency-version: 1.84.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: per-dependency
- dependency-name: rubocop-minitest
  dependency-version: 0.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  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-02-27 16:50:52 -06:00
Paulo Lieuthier
bc23cd08ac Python: boolean mapping (#612)
* fix(python): boolean and integer mapping
2026-02-27 15:55:28 -06:00
Paulo Lieuthier
1c607dc1d3 feat: implement add_extension in Python binding (#596)
Add support for registering custom Python functions as Rego extensions,
allowing users to call Python callables directly from Rego policies.

The implementation:
- Converts Rego values to Python types on call, and back on return
- Validates that the extension is callable at registration time
- Wraps errors with the extension name for easier debugging
- Documents clone semantics (shared callable reference across clones)

Tests cover: basic execution, type conversions (int, float, bool, None,
list, dict, set), zero-arg extensions, wrong arity, exception
propagation, non-callable rejection, and duplicate registration.

Contributed by @paulolieuthier
2026-02-25 15:55:52 -06:00
Anand Krishnamoorthi
47cc27ff49 feat(rbac)!: add Azure RBAC engine, FFI API, and cross-language tests (#577)
- add Azure RBAC condition interpreter and builtin evaluation in core (expressions, parser updates, evaluator, and test harness)
- introduce comprehensive RBAC YAML test suites and coverage for i
  - action/suboperation
  - strings
  - numbers
  - bools
  - IP
  - GUID
  - dates
  - times
  - lists
  - quantifiers (ForAnyOfAnyValues, ForAllOfAllValues)
- expose RBAC evaluation through FFI with an `rbac` feature flag enabled by default
- add C# `RbacEngine` wrapper + P/Invoke entrypoint and document usage in C# README
- expand C# tests to execute all RBAC YAML cases with per-case logging
- wire test assets into C# test output and centralize YAML dependency versions

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-02-19 15:30:03 -06:00
dependabot[bot]
8814eda0ae Bump the per-dependency group with 1 update (#587)
Bumps Microsoft.Build.NoTargets from 3.7.56 to 3.7.134

---
updated-dependencies:
- dependency-name: Microsoft.Build.NoTargets
  dependency-version: 3.7.134
  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-02-12 16:47:12 -06:00
dependabot[bot]
b4a69a13ba build(deps): bump the per-dependency group (#585)
---
updated-dependencies:
- dependency-name: magnus
  dependency-version: 0.8.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: regorus
  dependency-version: 0.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: serde_magnus
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  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-02-12 09:53:36 -06:00
dependabot[bot]
e83a47497a build(deps): bump the per-dependency group (#586)
Bumps the per-dependency group in /bindings/ruby with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [minitest](https://github.com/minitest/minitest) | `5.25.4` | `6.0.1` |
| [rake](https://github.com/ruby/rake) | `13.2.1` | `13.3.1` |
| [rake-compiler](https://github.com/rake-compiler/rake-compiler) | `1.2.9` | `1.3.1` |
| [rake-compiler-dock](https://github.com/rake-compiler/rake-compiler-dock) | `1.9.1` | `1.11.0` |
| [rubocop](https://github.com/rubocop/rubocop) | `1.73.2` | `1.84.1` |
| [rubocop-minitest](https://github.com/rubocop/rubocop-minitest) | `0.37.1` | `0.38.2` |
| [rb_sys](https://github.com/oxidize-rb/rb-sys) | `0.9.111` | `0.9.124` |


Updates `minitest` from 5.25.4 to 6.0.1
- [Changelog](https://github.com/minitest/minitest/blob/master/History.rdoc)
- [Commits](https://github.com/minitest/minitest/compare/v5.25.4...v6.0.1)

Updates `rake` from 13.2.1 to 13.3.1
- [Release notes](https://github.com/ruby/rake/releases)
- [Changelog](https://github.com/ruby/rake/blob/master/History.rdoc)
- [Commits](https://github.com/ruby/rake/compare/v13.2.1...v13.3.1)

Updates `rake-compiler` from 1.2.9 to 1.3.1
- [Release notes](https://github.com/rake-compiler/rake-compiler/releases)
- [Changelog](https://github.com/rake-compiler/rake-compiler/blob/master/History.md)
- [Commits](https://github.com/rake-compiler/rake-compiler/compare/v1.2.9...v1.3.1)

Updates `rake-compiler-dock` from 1.9.1 to 1.11.0
- [Release notes](https://github.com/rake-compiler/rake-compiler-dock/releases)
- [Changelog](https://github.com/rake-compiler/rake-compiler-dock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rake-compiler/rake-compiler-dock/compare/v1.9.1...v1.11.0)

Updates `rubocop` from 1.73.2 to 1.84.1
- [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.73.2...v1.84.1)

Updates `rubocop-minitest` from 0.37.1 to 0.38.2
- [Release notes](https://github.com/rubocop/rubocop-minitest/releases)
- [Changelog](https://github.com/rubocop/rubocop-minitest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rubocop/rubocop-minitest/compare/v0.37.1...v0.38.2)

Updates `rb_sys` from 0.9.111 to 0.9.124
- [Release notes](https://github.com/oxidize-rb/rb-sys/releases)
- [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.111...v0.9.124)

---
updated-dependencies:
- dependency-name: minitest
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: rake
  dependency-version: 13.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: rake-compiler
  dependency-version: 1.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: rake-compiler-dock
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: rubocop
  dependency-version: 1.84.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: rubocop-minitest
  dependency-version: 0.38.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: rb_sys
  dependency-version: 0.9.124
  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-02-12 09:53:00 -06:00
dependabot[bot]
241c1d445b build(deps-dev): bump the per-dependency group (#583)
Bumps the per-dependency group in /bindings/java with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [junit:junit](https://github.com/junit-team/junit4) | `3.8.1` | `4.13.2` |
| [com.google.code.gson:gson](https://github.com/google/gson) | `2.10.1` | `2.13.2` |
| [org.codehaus.mojo:exec-maven-plugin](https://github.com/mojohaus/exec-maven-plugin) | `3.1.0` | `3.6.3` |
| [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) | `3.2.5` | `3.5.4` |
| [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) | `3.6.3` | `3.12.0` |
| [org.apache.maven.plugins:maven-source-plugin](https://github.com/apache/maven-source-plugin) | `3.3.0` | `3.4.0` |


Updates `junit:junit` from 3.8.1 to 4.13.2
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.2.md)
- [Commits](https://github.com/junit-team/junit4/commits/r4.13.2)

Updates `com.google.code.gson:gson` from 2.10.1 to 2.13.2
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.10.1...gson-parent-2.13.2)

Updates `org.codehaus.mojo:exec-maven-plugin` from 3.1.0 to 3.6.3
- [Release notes](https://github.com/mojohaus/exec-maven-plugin/releases)
- [Commits](https://github.com/mojohaus/exec-maven-plugin/compare/exec-maven-plugin-3.1.0...3.6.3)

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

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

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

---
updated-dependencies:
- dependency-name: junit:junit
  dependency-version: 4.13.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: com.google.code.gson:gson
  dependency-version: 2.13.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: org.codehaus.mojo:exec-maven-plugin
  dependency-version: 3.6.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-version: 3.5.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: org.apache.maven.plugins:maven-javadoc-plugin
  dependency-version: 3.12.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: per-dependency
- dependency-name: org.apache.maven.plugins:maven-source-plugin
  dependency-version: 3.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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-02-12 09:46:33 -06:00
dependabot[bot]
4054d1b6b6 build(deps): bump the per-dependency group with 12 updates (#593)
Bumps the per-dependency group with 12 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `4.3.1` | `6.0.2` |
| [actions/setup-python](https://github.com/actions/setup-python) | `5.6.0` | `6.2.0` |
| [actions/setup-java](https://github.com/actions/setup-java) | `4.8.0` | `5.2.0` |
| [actions/setup-go](https://github.com/actions/setup-go) | `5.1.0` | `6.2.0` |
| [actions/setup-dotnet](https://github.com/actions/setup-dotnet) | `4.1.0` | `5.1.0` |
| [actions/setup-node](https://github.com/actions/setup-node) | `4.4.0` | `6.2.0` |
| [github/codeql-action](https://github.com/github/codeql-action) | `3.32.2` | `4.32.2` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4.6.2` | `6.0.0` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `5.0.0` | `7.0.0` |
| [PyO3/maturin-action](https://github.com/pyo3/maturin-action) | `63b75c597b83e247fbf4fb7719801cc4220ae9f3` | `b1bd829e37fef14c63f19162034228a2f3dc1021` |
| [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action) | `0.5.108` | `0.5.126` |
| [oxidize-rb/actions](https://github.com/oxidize-rb/actions) | `1.2.6` | `1.4.4` |


Updates `actions/checkout` from 4.3.1 to 6.0.2
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](34e114876b...de0fac2e45)

Updates `actions/setup-python` from 5.6.0 to 6.2.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](a26af69be9...a309ff8b42)

Updates `actions/setup-java` from 4.8.0 to 5.2.0
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4.8.0...be666c2fcd27ec809703dec50e508c2fdc7f6654)

Updates `actions/setup-go` from 5.1.0 to 6.2.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5.1.0...7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5)

Updates `actions/setup-dotnet` from 4.1.0 to 5.1.0
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4.1.0...baa11fbfe1d6520db94683bd5c7a3818018e4309)

Updates `actions/setup-node` from 4.4.0 to 6.2.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](49933ea528...6044e13b5d)

Updates `github/codeql-action` from 3.32.2 to 4.32.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3.32.2...45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2)

Updates `actions/upload-artifact` from 4.6.2 to 6.0.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](ea165f8d65...b7c566a772)

Updates `actions/download-artifact` from 5.0.0 to 7.0.0
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](634f93cb29...37930b1c2a)

Updates `PyO3/maturin-action` from 63b75c597b83e247fbf4fb7719801cc4220ae9f3 to b1bd829e37fef14c63f19162034228a2f3dc1021
- [Release notes](https://github.com/pyo3/maturin-action/releases)
- [Commits](63b75c597b...b1bd829e37)

Updates `MarcoIeni/release-plz-action` from 0.5.108 to 0.5.126
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](8724d33cd9...52440b50d3)

Updates `oxidize-rb/actions` from 1.2.6 to 1.4.4
- [Release notes](https://github.com/oxidize-rb/actions/releases)
- [Commits](7ca44a16e2...e5f9a49a78)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: actions/setup-python
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: actions/setup-java
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: actions/setup-go
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: actions/setup-dotnet
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: actions/setup-node
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: github/codeql-action
  dependency-version: 4.32.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: actions/upload-artifact
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: actions/download-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: per-dependency
- dependency-name: PyO3/maturin-action
  dependency-version: b1bd829e37fef14c63f19162034228a2f3dc1021
  dependency-type: direct:production
  dependency-group: per-dependency
- dependency-name: MarcoIeni/release-plz-action
  dependency-version: 0.5.126
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: per-dependency
- dependency-name: oxidize-rb/actions
  dependency-version: 1.4.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  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-02-12 06:30:19 -06:00
Anand Krishnamoorthi
8f7ca44bdf chore(dependabot): expand coverage and pin workflows (#579)
- Expand dependabot coverage across Rust subcrates and other ecosystems.

- Group updates per dependency and ignore vendored mimalloc crates.

- Pin GitHub Actions to exact SHAs in existing workflows.
2026-02-11 17:43:48 -06:00
Anand Krishnamoorthi
96360fa9d8 fix(bindings): add SafeHandleWrapper + memory growth checks; bump 0.9.1 (#571)
- Introduce SafeHandleWrapper with gating, short drain wait, and deferred release on last in-flight exit.
- Wire Engine/Program/Rvm/CompiledPolicy to wrapper (centralized handle use, interop helper).
- Add C# memory growth tests (using/finalizer paths) and extend xtask C# runner options.
- Add pooled marshalling utilities, ResultHelpers, and API cleanups; update versions/changelog.

Fixes #570. Closes #554

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-02-09 12:22:05 -06:00
Anand Krishnamoorthi
455d2aa588 chore(nuget): Add support for macosx (#553)
Additionally
- Include more metadata in nuget package
- Also generate snupkg for native symbols.
  We intentionally don't add the symbols for native rust shared library
  to the nuget package since that could increase the size of the nuget.
  We will revisit that later.
- update licenses of all the bindings.

closes #551

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-02-03 00:09:52 +05:30
Elijah Koulaxis
0e5fe9b9ac feat: add tests for number semantics (#555) 2026-01-31 07:27:51 +05:30
367 changed files with 38097 additions and 3560 deletions

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
@@ -5,7 +7,95 @@
version: 2
updates:
# All Rust/Cargo directories are grouped into a single entry so that
# when a dependency is updated, Dependabot bumps it across the root
# workspace AND every binding, preventing version skew.
- package-ecosystem: "cargo"
directory: "/" # Location of package manifests
directories:
- "/"
- "/bindings/ffi"
- "/bindings/java"
- "/bindings/python"
- "/bindings/ruby"
- "/bindings/wasm"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
# Bundle all Cargo dependency updates into a single PR. Without this,
# dependabot creates a separate PR per directory for the same dependency,
# and each individual PR fails to build due to version skew.
rust-dependencies:
patterns:
- "*"
# Ignore vendored mimalloc crates; updates are managed manually.
ignore:
- dependency-name: "regorus-mimalloc"
- dependency-name: "regorus-mimalloc-sys"
- package-ecosystem: "gomod"
directory: "/bindings/go"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "maven"
directory: "/bindings/java"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "nuget"
directory: "/bindings/csharp"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "pip"
directory: "/bindings/python"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "bundler"
directory: "/bindings/ruby"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "ci(deps)"
groups:
github-actions:
patterns:
- "*"

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: "CodeQL Security Analysis"
on:
@@ -60,14 +62,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Setup language-specific dependencies BEFORE CodeQL init for proper tracing setup
- name: Setup Rust
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
@@ -84,26 +86,26 @@ jobs:
- name: Setup Python
if: matrix.language == 'python'
uses: actions/setup-python@v5
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.10'
- name: Setup Java
if: matrix.language == 'java-kotlin'
uses: actions/setup-java@v4
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'corretto'
java-version: '8'
- name: Setup Go
if: matrix.language == 'go'
uses: actions/setup-go@v5
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: '1.21'
- name: Setup .NET
if: matrix.language == 'csharp'
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
global-json-file: ./bindings/csharp/global.json
@@ -113,12 +115,12 @@ jobs:
- name: Setup Node.js
if: matrix.language == 'javascript-typescript'
uses: actions/setup-node@v4
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '18'
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -139,7 +141,7 @@ jobs:
- name: Setup Ruby
if: matrix.language == 'rust' && contains(matrix.working-directory, 'ruby')
uses: ruby/setup-ruby@v1
uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1.299.0
with:
ruby-version: '3.4.2'
bundler-cache: true
@@ -186,6 +188,6 @@ jobs:
run: cargo xtask build-wasm --release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
category: "/language:${{matrix.language}}"

View File

@@ -0,0 +1,129 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: dependabot/refresh-cargo-lockfiles
on:
pull_request_target:
types: [opened, synchronize, reopened]
branches: ["main"]
concurrency:
group: dependabot-refresh-cargo-lockfiles-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
refresh-cargo-lockfiles:
permissions:
contents: write
if: >-
github.event.pull_request.user.login == 'dependabot[bot]' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
persist-credentials: false
- name: Setup Rust toolchain
run: |
rustup toolchain install 1.92.0 --profile minimal
rustup override set 1.92.0
cargo --version
rustc --version
- name: Refresh affected Cargo lockfiles
shell: bash
run: |
set -euo pipefail
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.event.pull_request.head.sha }}"
mapfile -t changed_files < <(git diff --name-only "$base_sha" "$head_sha" -- ':(glob)**/Cargo.toml' ':(glob)**/Cargo.lock')
if [ "${#changed_files[@]}" -eq 0 ]; then
echo "No Cargo manifest or lockfile changes detected."
exit 0
fi
declare -A manifests=()
for path in "${changed_files[@]}"; do
case "$path" in
bindings/ffi/*)
manifests["bindings/ffi/Cargo.toml"]=1
;;
bindings/java/*)
manifests["bindings/java/Cargo.toml"]=1
;;
bindings/python/*)
manifests["bindings/python/Cargo.toml"]=1
;;
bindings/ruby/*)
manifests["bindings/ruby/Cargo.toml"]=1
;;
bindings/wasm/*)
manifests["bindings/wasm/Cargo.toml"]=1
;;
*)
manifests["Cargo.toml"]=1
;;
esac
done
for manifest in "${!manifests[@]}"; do
echo "Refreshing lockfile for $manifest"
cargo metadata \
--config 'build.rustc="rustc"' \
--config 'build.rustc-wrapper=""' \
--config 'build.rustc-workspace-wrapper=""' \
--format-version 1 \
--all-features \
--manifest-path "$manifest" > /dev/null
done
if [[ -n "${manifests[Cargo.toml]+x}" ]]; then
echo "Refreshing lockfile for tests/ensure_no_std/Cargo.toml (thumbv7m-none-eabi)"
cargo metadata \
--config 'build.rustc="rustc"' \
--config 'build.rustc-wrapper=""' \
--config 'build.rustc-workspace-wrapper=""' \
--format-version 1 \
--manifest-path tests/ensure_no_std/Cargo.toml \
--filter-platform thumbv7m-none-eabi > /dev/null
fi
- name: Commit lockfile refresh
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mapfile -t lockfiles < <(git ls-files -m -o --exclude-standard -- ':(glob)**/Cargo.lock')
for lockfile in "${lockfiles[@]}"; do
git add "$lockfile"
done
if git diff --cached --quiet; then
echo "No Cargo lockfile changes required."
exit 0
fi
auth_header=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')
trap 'git config --unset-all http.https://github.com/.extraheader' EXIT
git config http.https://github.com/.extraheader "AUTHORIZATION: basic ${auth_header}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "build(deps): refresh Cargo lockfiles"
git push origin HEAD:${{ github.event.pull_request.head.ref }}

66
.github/workflows/dependency-audit.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Dependency Audits
on:
pull_request:
push:
branches: ["main"]
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
permissions:
contents: read
jobs:
cargo-audit:
name: Cargo Audit (${{ matrix.lockfile }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
lockfile:
- Cargo.lock
- bindings/ffi/Cargo.lock
- bindings/java/Cargo.lock
- bindings/python/Cargo.lock
- bindings/ruby/Cargo.lock
- bindings/wasm/Cargo.lock
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Run cargo audit
uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
lockfile: ${{ matrix.lockfile }}
cargo-deny:
name: Cargo Deny (${{ matrix.manifest }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
manifest:
- Cargo.toml
- bindings/ffi/Cargo.toml
- bindings/java/Cargo.toml
- bindings/python/Cargo.toml
- bindings/ruby/Cargo.toml
- bindings/ruby/ext/regorusrb/Cargo.toml
- bindings/wasm/Cargo.toml
- tests/ensure_no_std/Cargo.toml
- xtask/Cargo.toml
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Rust
uses: ./.github/actions/toolchains/rust
- name: Run cargo deny
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check
command-arguments: advisories bans
manifest-path: ${{ matrix.manifest }}

82
.github/workflows/feature-matrix.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# Thorough weekly test of non-default feature combinations.
# Catches regressions from dependency updates and feature-gating issues
# that the fast PR CI checks (cargo check only) would miss at runtime.
name: tests/feature-matrix
on:
workflow_dispatch:
schedule:
# Run at 3:42 AM UTC every Saturday.
- cron: "42 3 * * 6"
env:
CARGO_TERM_COLOR: always
jobs:
feature-matrix:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# Bare minimum: validates that the core interpreter works
# without any builtins or optional subsystems.
- name: minimal (std + arc)
features: std,arc
# Common library usage pattern (issue #595): consumer enables
# std + arc + rvm and relies on indexmap/std propagation.
- name: library (std + arc + rvm)
features: std,arc,rvm
# New default after removing mimalloc from full-opa.
# Ensures all builtins compile without the allocator.
- name: full-opa (no mimalloc)
features: std,arc,full-opa
# Binding-style usage: full-opa with the vendored allocator.
# Mirrors how ffi/java/python/ruby bindings are built.
- name: full-opa + allocator
features: std,arc,full-opa,allocator-memory-limits
# Selective builtins without full-opa: validates that popular
# features can be cherry-picked independently.
- name: cherry-picked builtins
features: std,arc,rvm,regex,time,semver,cache
# Observability features only: coverage + cache without the
# heavier builtins (regex, time, etc.).
- name: observability
features: std,arc,rvm,coverage,cache
# Azure Policy adds jsonschema + dashmap; test it compiles
# and runs on top of full-opa.
- name: azure-policy
features: std,arc,full-opa,azure_policy
# Azure RBAC adds regex + time + net on top of full-opa.
- name: azure-rbac
features: std,arc,full-opa,azure-rbac
# no_std with the OPA-compatible feature set: exercises the
# spin_no_std codepath and absence of std-only dependencies.
- name: no_std
features: arc,opa-no-std
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus-features
- name: Fetch dependencies
run: cargo fetch --locked
- name: Build
run: cargo build --no-default-features --features "${{ matrix.features }}" --frozen
- name: Test
run: cargo test --no-default-features --features "${{ matrix.features }}" --frozen

29
.github/workflows/miri.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: miri
on:
workflow_dispatch:
schedule:
# Run at 6:30 AM UTC every Wednesday
- cron: "30 6 * * 3"
jobs:
miri-test:
name: miri (nightly)
runs-on: ubuntu-latest
env:
MIRIFLAGS: "-Zmiri-disable-isolation"
steps:
- name: Checkout repository
uses: actions/checkout@v6
- uses: ./.github/actions/toolchains/rust
with:
toolchain: nightly
components: miri rust-src
- name: Set up Miri
run: cargo miri setup
- name: Run Miri tests
run: cargo miri test -p regorus
- name: Run Miri ACI tests
run: cargo miri test -p regorus --test aci
- name: Run Miri kata tests
run: cargo miri test -p regorus --test kata

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: tests/release-extensions
on:
@@ -18,11 +20,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: tests/release
on:
@@ -18,11 +20,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies

View File

@@ -1,6 +1,9 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: publish-java
on: workflow_dispatch
on:
workflow_dispatch:
permissions:
contents: read
@@ -32,10 +35,10 @@ jobs:
os: windows-latest
extension: dll
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: 8
distribution: "corretto"
@@ -43,7 +46,7 @@ jobs:
with:
targets: ${{ matrix.target }}
- if: ${{ matrix.build_cmd == 'zigbuild' }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- if: ${{ matrix.build_cmd == 'zigbuild' }}
@@ -53,7 +56,7 @@ jobs:
- run: cargo ${{ matrix.build_cmd || 'build' }} --release --frozen --target ${{ matrix.target }}${{ matrix.glibc && format('.{0}', matrix.glibc) || '' }} --manifest-path ./bindings/java/Cargo.toml
- run: mkdir -p native/${{ matrix.target }}
- run: mv target/${{ matrix.target }}/release/*.${{ matrix.extension }} ./native/${{ matrix.target }}/
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: native-libraries-${{ matrix.target }}
path: native/
@@ -63,24 +66,24 @@ jobs:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: 8
distribution: "corretto"
server-id: ossrh
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: native-libraries-*
merge-multiple: true
path: ./bindings/java/native/
- run: mvn package
working-directory: ./bindings/java
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: built-jars
path: ./bindings/java/target/regorus-java-*.jar

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# This file is autogenerated by maturin v1.4.0
# To update, run
#
@@ -18,8 +20,8 @@ jobs:
matrix:
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.10'
- uses: ./.github/actions/toolchains/rust
@@ -32,14 +34,14 @@ jobs:
working-directory: bindings/python
- name: Build wheels
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: wheels-linux-${{ matrix.target }}
path: dist
@@ -50,8 +52,8 @@ jobs:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.10'
architecture: ${{ matrix.target }}
@@ -65,13 +67,13 @@ jobs:
working-directory: bindings/python
- name: Build wheels
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml --frozen --strip
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: wheels-windows-${{ matrix.target }}
path: dist
@@ -82,8 +84,8 @@ jobs:
matrix:
target: [x86_64, aarch64, universal2-apple-darwin]
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.10'
- uses: ./.github/actions/toolchains/rust
@@ -96,13 +98,13 @@ jobs:
working-directory: bindings/python
- name: Build wheels
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: wheels-macos-${{ matrix.host.target }}
path: dist
@@ -114,13 +116,13 @@ jobs:
# if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos]
steps:
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: wheels-*
merge-multiple: true
path: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:

View File

@@ -1,10 +1,13 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: publish-wasm
permissions:
pull-requests: write
contents: write
on: workflow_dispatch
on:
workflow_dispatch:
jobs:
publish-wasm:
@@ -12,11 +15,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'

View File

@@ -1,10 +1,13 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: Release-plz
permissions:
pull-requests: write
contents: write
on: workflow_dispatch
on:
workflow_dispatch:
jobs:
release-plz:
@@ -14,13 +17,13 @@ jobs:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Run release-plz
uses: MarcoIeni/release-plz-action@8724d33cd97b8295051102e2e19ca592962238f5 #v0.5.108
uses: MarcoIeni/release-plz-action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
@@ -30,12 +32,12 @@ jobs:
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
steps:
- name: Checkout code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
@@ -47,10 +49,10 @@ jobs:
- name: Run rust-clippy
run: cargo xtask clippy --sarif rust-clippy-results.sarif
continue-on-error: true
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@c298edae2d512d807fe4bdc57c0ac5a036f61501 # v3.29.11
if: ${{ hashFiles('rust-clippy-results.sarif') != '' }}
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.11
with:
sarif_file: rust-clippy-results.sarif
wait-for-processing: true

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/c-cpp
on:
@@ -14,13 +16,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/csharp
on:
@@ -31,19 +33,20 @@ jobs:
target: x86_64-unknown-linux-gnu
libpath: |
**/release/libregorus_ffi.so
# Disabled for now
#- os: macos-latest
# target: aarch64-apple-darwin
# libpath: |
# **/release/libregorus_ffi.dylib
- os: macos-latest
target: aarch64-apple-darwin
libpath: |
**/release/libregorus_ffi.dylib
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
with:
targets: ${{ matrix.runtime.target }}
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
@@ -56,7 +59,7 @@ jobs:
run: cargo xtask build-ffi --release --target ${{ matrix.runtime.target }}
- name: Upload regorus ffi shared library
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: regorus-ffi-artifacts-${{ matrix.runtime.target }}
# Note: The full path of each artifact relative to . is preserved.
@@ -70,18 +73,18 @@ jobs:
needs: build-ffi
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
global-json-file: ./bindings/csharp/global.json
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
@@ -89,7 +92,7 @@ jobs:
run: cargo fetch --locked
- name: Download regorus ffi shared libraries
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: regorus-ffi-artifacts-*
merge-multiple: true
@@ -99,13 +102,15 @@ jobs:
run: ls -R ./bindings/csharp/Regorus/tmp
- name: Build Regorus nuget via xtask
run: cargo xtask build-csharp --release --clean --artifacts-dir ./bindings/csharp/Regorus/tmp/bindings/ffi/target --enforce-artifacts
run: cargo xtask build-csharp --release --clean --artifacts-dir ./bindings/csharp/Regorus/tmp/bindings/ffi/target --enforce-artifacts --repository-commit ${{ github.sha }} --include-symbols
- name: Upload Regorus nuget
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: regorus-nuget
path: bindings/csharp/Regorus/bin/Release/Regorus*.nupkg
path: |
bindings/csharp/Regorus/bin/Release/Regorus*.nupkg
bindings/csharp/Regorus/bin/Release/Regorus*.snupkg
if-no-files-found: error
retention-days: 1
@@ -122,24 +127,24 @@ jobs:
target: x86_64-pc-windows-msvc
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
#- os: macos-latest
# target: aarch64-apple-darwin
- os: macos-latest
target: aarch64-apple-darwin
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
global-json-file: ./bindings/csharp/global.json
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
@@ -147,7 +152,7 @@ jobs:
run: cargo fetch --locked
- name: Download regorus nuget
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: regorus-nuget
path: ./bindings/csharp/Regorus/bin/Release

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/ffi
on:
@@ -14,12 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/go
on:
@@ -14,12 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
@@ -28,7 +30,7 @@ jobs:
- name: Fetch FFI crate dependencies
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
architecture: x64

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/java
on:
@@ -14,17 +16,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: 8
distribution: "corretto"
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: musl
on:
@@ -18,12 +20,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/toolchains/rust
with:
targets: x86_64-unknown-linux-musl
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/no-std
on:
@@ -18,12 +20,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/toolchains/rust
with:
targets: thumbv7m-none-eabi
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/python
on:
@@ -21,14 +23,14 @@ jobs:
runs-on: ${{ matrix.host.name }}
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
with:
targets: ${{ matrix.host.target }}
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
@@ -37,7 +39,7 @@ jobs:
- name: Fetch Python crate dependencies
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml --target ${{ matrix.host.target }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.10"
architecture: x64
@@ -49,7 +51,7 @@ jobs:
run: cargo xtask build-python --release --target ${{ matrix.host.target }} --target-dir bindings/python/dist --frozen
- name: Upload wheel artefacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: regorus-wheel-${{ matrix.host.name }}
path: bindings/python/dist/regorus-*.whl
@@ -63,12 +65,12 @@ jobs:
runs-on: ${{ matrix.host }}
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
@@ -77,7 +79,7 @@ jobs:
- name: Fetch Python crate dependencies
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
architecture: x64

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/ruby
on:
@@ -12,12 +14,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup Ruby and Rust
uses: oxidize-rb/actions/setup-ruby-and-rust@7ca44a16e287e5ff7dd72ab53f4bd41cbf34a571 #v1.26
uses: oxidize-rb/actions/setup-ruby-and-rust@e5f9a49a7812a078584072f6e3f657ad247c8771 # v1.26
with:
bundler: 2.6.5
rubygems: 3.6.5
@@ -28,7 +30,7 @@ jobs:
working-directory: "bindings/ruby"
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/wasm
on:
@@ -14,14 +16,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
@@ -31,7 +33,7 @@ jobs:
run: cargo fetch --locked --manifest-path bindings/wasm/Cargo.toml
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22

View File

@@ -1,3 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: tests/debug
on:
@@ -18,11 +20,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies

9
.gitignore vendored
View File

@@ -25,6 +25,12 @@ bindings/ffi/regorus.ffi.hpp
bindings/*/target
# Temporary commit message files
.commit-msg.txt
# Local planning docs
docs/plans/
# C# build folders
**bin
**obj
@@ -48,3 +54,6 @@ bindings/ruby/bin/
bindings/java/.classpath
bindings/java/.project
bindings/java/.settings/
# Emacs temporary files
*~

View File

@@ -6,6 +6,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Azure RBAC condition interpreter with builtin evaluation coverage and YAML test suite, including quantifier (ForAnyOfAnyValues/ForAllOfAllValues), datetime (DateTimeEquals), IP (IpInRange), GUID (GuidEquals), list (ListContains), and string (StringEquals) semantics.
- FFI surface for Azure RBAC condition evaluation (see bindings changelog for language-specific wrappers).
### Changed
- [**breaking**] Switch RVM binary serialization to postcard, bump the format to v4, and mark v1-3 loads as partial (recompile required).
## [0.9.1](https://github.com/microsoft/regorus/compare/regorus-v0.9.0...regorus-v0.9.1) - 2026-02-06
### Fixed
- Release native C# handles reliably to avoid memory growth ([#571](https://github.com/microsoft/regorus/pull/571)).
- Centralize C# handle gating with a short dispose wait and deferred release to avoid leaks while blocking new calls ([#571](https://github.com/microsoft/regorus/pull/571)).
### Added
- Manual C# memory growth tests for both `using` and finalizer paths ([#571](https://github.com/microsoft/regorus/pull/571)).
- C# test runner options for filtered tests, console logging, and skipping sample apps ([#571](https://github.com/microsoft/regorus/pull/571)).
## [0.5.0](https://github.com/microsoft/regorus/compare/regorus-v0.4.0...regorus-v0.5.0) - 2025-07-08
### Added

796
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,17 @@ members = [
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.9.0"
version = "0.9.1"
edition = "2021"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
repository = "https://github.com/microsoft/regorus"
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
# Enable verification with Verus
[package.metadata.verus]
verify = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
@@ -24,8 +29,8 @@ default = ["full-opa", "arc", "rvm"]
arc = []
ast = []
azure_policy = ["dep:jsonschema", "arc", "dashmap"]
azure-rbac = []
azure_policy = ["dep:jsonschema", "dep:chrono", "dep:ipnet", "dep:icu_casemap", "dep:hashbrown", "arc", "dashmap"]
azure-rbac = ["regex", "time", "net"]
base64 = ["dep:data-encoding"]
base64url = ["dep:data-encoding"]
coverage = []
@@ -39,10 +44,11 @@ net = ["dep:ipnet"]
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
rvm = ["dep:bincode", "dep:indexmap"]
cache = ["dep:lru"]
rvm = ["dep:postcard", "dep:indexmap"]
semver = ["dep:semver"]
allocator-memory-limits = ["std", "mimalloc", "mimalloc/allocator-memory-limits"]
std = ["rand/std", "rand/std_rng", "serde_json/std", "msvc_spectre_libs" ]
std = ["rand/std", "rand/std_rng", "serde_json/std", "indexmap?/std", "msvc_spectre_libs", "dep:parking_lot" ]
time = ["dep:chrono", "dep:chrono-tz"]
uuid = ["dep:uuid"]
urlquery = ["dep:url"]
@@ -56,11 +62,10 @@ full-opa = [
"hex",
"http",
"jsonschema",
"allocator-memory-limits",
"mimalloc",
"net",
"opa-runtime",
"regex",
"cache",
"semver",
"std",
"time",
@@ -96,42 +101,49 @@ opa-testutil = []
rand = ["dep:rand"]
[dependencies]
anyhow = { version = "1.0.45", default-features = false }
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 }
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-traits = { version = "0.2", default-features = false }
spin = { version = "0.9.8", default-features = false, features = ["mutex", "spin_mutex"] }
parking_lot = { version = "0.12", optional = true }
spin = { version = "0.10.0", default-features = false, features = ["mutex", "spin_mutex"] }
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
regex = {version = "1.11.1", optional = true, default-features = false }
regex = {version = "1.12.3", optional = true, default-features = false }
semver = {version = "1.0.25", optional = true, default-features = false }
url = { version = "2.5.4", optional = true }
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.30.0", default-features = false, optional = true }
chrono = { version = "0.4.40", optional = true }
uuid = { version = "1.22.0", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.45.0", default-features = false, optional = true }
chrono = { version = "0.4.44", optional = true }
chrono-tz = { version = "0.10.1", optional = true }
ipnet = { version = "2.11.0", optional = true, default-features = false }
ipnet = { version = "2.12.0", optional = true, default-features = false }
icu_casemap = { version = "2.1", optional = true, default-features = false, features = ["compiled_data"] }
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
# Specify thread_rng for in order to use random_range
rand = { version = "0.9.0", default-features = false, features = ["thread_rng"], optional = true }
rand = { version = "0.10.0", default-features = false, features = ["thread_rng"], optional = true }
# 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 }
# rvm related deps
indexmap = { version = "2.12.1", default-features = false, features = ["serde"], optional = true }
bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"], optional = true }
postcard = { version = "1.1.3", default-features = false, features = ["alloc"], optional = true }
# Use Verus for verification
vstd = { version = "0.0.0-2026-03-17-2326" }
[dev-dependencies]
anyhow = "1.0.45"
anyhow = "1.0.102"
cfg-if = "1.0.0"
clap = { version = "4.5.53", features = ["derive"] }
prettydiff = { version = "0.9.0", default-features = false }
@@ -189,6 +201,16 @@ harness = false
name = "aci_benchmark"
harness = false
[[bench]]
name = "rvm_benchmark"
harness = false
required-features = ["rvm"]
[[bench]]
name = "normalization_benchmark"
harness = false
required-features = ["azure_policy"]
[[example]]
name="regorus"
harness=false
@@ -200,3 +222,6 @@ doctest=false
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(verus_keep_ghost)'] }

View File

@@ -0,0 +1,560 @@
use std::hint::black_box;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use regorus::languages::azure_policy::aliases::{denormalizer, normalizer, AliasRegistry};
use regorus::Value;
use serde_json::json;
// ─── Alias catalog (reused across benchmarks) ───────────────────────────────
const ALIASES_JSON: &str = r#"[
{
"namespace": "Microsoft.Network",
"resourceTypes": [
{
"resourceType": "networkSecurityGroups",
"aliases": [
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
"defaultPath": "properties.securityRules[*].properties.protocol",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
"defaultPath": "properties.securityRules[*].properties.access",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].priority",
"defaultPath": "properties.securityRules[*].properties.priority",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].direction",
"defaultPath": "properties.securityRules[*].properties.direction",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].sourceAddressPrefix",
"defaultPath": "properties.securityRules[*].properties.sourceAddressPrefix",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].destinationPortRange",
"defaultPath": "properties.securityRules[*].properties.destinationPortRange",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
"defaultPath": "properties.securityRules[*].name",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules[*].protocol",
"defaultPath": "properties.defaultSecurityRules[*].properties.protocol",
"paths": []
}
]
}
]
},
{
"namespace": "Microsoft.Storage",
"resourceTypes": [
{
"resourceType": "storageAccounts",
"aliases": [
{
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"defaultPath": "properties.supportsHttpsTrafficOnly",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/accessTier",
"defaultPath": "properties.accessTier",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/isHnsEnabled",
"defaultPath": "properties.isHnsEnabled",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/minimumTlsVersion",
"defaultPath": "properties.minimumTlsVersion",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess",
"defaultPath": "properties.allowBlobPublicAccess",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/sku.name",
"defaultPath": "sku.name",
"paths": []
}
]
}
]
}
]"#;
fn build_registry() -> AliasRegistry {
let mut reg = AliasRegistry::new();
reg.load_from_json(ALIASES_JSON).unwrap();
reg
}
/// Convert a serde_json::Value to regorus::Value.
fn to_regorus(v: serde_json::Value) -> Value {
Value::from(v)
}
// ─── Input resources ────────────────────────────────────────────────────────
fn simple_storage_resource() -> Value {
to_regorus(json!({
"name": "myStorageAccount",
"type": "Microsoft.Storage/storageAccounts",
"location": "westus2",
"kind": "StorageV2",
"sku": { "name": "Standard_LRS", "tier": "Standard" },
"tags": { "environment": "production", "team": "platform" },
"properties": {
"supportsHttpsTrafficOnly": true,
"accessTier": "Hot",
"isHnsEnabled": false,
"minimumTlsVersion": "TLS1_2",
"allowBlobPublicAccess": false
}
}))
}
fn nsg_resource(rule_count: usize) -> Value {
let rules: Vec<serde_json::Value> = (0..rule_count)
.map(|i| {
json!({
"name": format!("rule-{}", i),
"properties": {
"protocol": "Tcp",
"access": if i % 2 == 0 { "Allow" } else { "Deny" },
"priority": 100 + i,
"direction": "Inbound",
"sourceAddressPrefix": format!("10.0.{}.0/24", i % 256),
"destinationPortRange": format!("{}", 80 + i)
}
})
})
.collect();
to_regorus(json!({
"name": "myNsg",
"type": "Microsoft.Network/networkSecurityGroups",
"location": "eastus",
"properties": {
"securityRules": rules
}
}))
}
// ─── Benchmarks ─────────────────────────────────────────────────────────────
fn bench_normalize_simple(c: &mut Criterion) {
let registry = build_registry();
let resource = simple_storage_resource();
c.bench_function("normalize/simple_storage", |b| {
b.iter(|| normalizer::normalize(black_box(&resource), Some(&registry), None))
});
}
fn bench_normalize_no_aliases(c: &mut Criterion) {
let resource = simple_storage_resource();
c.bench_function("normalize/simple_no_aliases", |b| {
b.iter(|| normalizer::normalize(black_box(&resource), None, None))
});
}
fn bench_normalize_nsg_scaling(c: &mut Criterion) {
let registry = build_registry();
let mut group = c.benchmark_group("normalize/nsg_rules");
for rule_count in [5, 20, 100] {
let resource = nsg_resource(rule_count);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&resource,
|b, res| b.iter(|| normalizer::normalize(black_box(res), Some(&registry), None)),
);
}
group.finish();
}
fn bench_denormalize_simple(c: &mut Criterion) {
let registry = build_registry();
let resource = simple_storage_resource();
let normalized = normalizer::normalize(&resource, Some(&registry), None);
c.bench_function("denormalize/simple_storage", |b| {
b.iter(|| denormalizer::denormalize(black_box(&normalized), Some(&registry), None))
});
}
fn bench_denormalize_nsg_scaling(c: &mut Criterion) {
let registry = build_registry();
let mut group = c.benchmark_group("denormalize/nsg_rules");
for rule_count in [5, 20, 100] {
let resource = nsg_resource(rule_count);
let normalized = normalizer::normalize(&resource, Some(&registry), None);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&normalized,
|b, norm| b.iter(|| denormalizer::denormalize(black_box(norm), Some(&registry), None)),
);
}
group.finish();
}
fn bench_round_trip(c: &mut Criterion) {
let registry = build_registry();
let resource = nsg_resource(20);
c.bench_function("round_trip/nsg_20_rules", |b| {
b.iter(|| {
let n = normalizer::normalize(black_box(&resource), Some(&registry), None);
denormalizer::denormalize(&n, Some(&registry), None)
})
});
}
fn bench_normalize_and_wrap(c: &mut Criterion) {
let registry = build_registry();
let resource = nsg_resource(20);
let context = to_regorus(json!({"resourceGroup": {"name": "rg1"}}));
let parameters = to_regorus(json!({"env": "prod"}));
c.bench_function("normalize_and_wrap/nsg_20_rules", |b| {
b.iter(|| {
registry.normalize_and_wrap(
black_box(&resource),
None,
Some(context.clone()),
Some(parameters.clone()),
)
})
});
}
fn bench_registry_load(c: &mut Criterion) {
c.bench_function("registry/load_from_json", |b| {
b.iter(|| {
let mut reg = AliasRegistry::new();
reg.load_from_json(black_box(ALIASES_JSON)).unwrap();
reg
})
});
}
// ─── Large-payload benchmarks ───────────────────────────────────────────────
//
// These stress the hot paths identified in the performance analysis:
// - Nested set helpers (alias-heavy catalog with deep properties)
// - Array element remap/cleanup/rewrap (large sub-resource arrays)
// - Scalar denormalization lookups (many aliases × many fields)
/// Build a large alias catalog with `n` scalar aliases for storage accounts.
/// Each alias maps to a nested `properties.section_i.field_j` path, creating
/// deep nested-set workloads.
fn large_alias_catalog(n: usize) -> String {
let mut aliases = Vec::new();
for i in 0..n {
let section = i / 10;
let field = i % 10;
aliases.push(format!(
r#"{{
"name": "Microsoft.Storage/storageAccounts/section{section}Field{field}",
"defaultPath": "properties.section{section}.field{field}",
"paths": []
}}"#,
));
}
format!(
r#"[{{
"namespace": "Microsoft.Storage",
"resourceTypes": [{{
"resourceType": "storageAccounts",
"aliases": [{aliases}]
}}]
}}]"#,
aliases = aliases.join(",")
)
}
/// Build a storage account resource whose `properties` contain nested sections
/// matching the large alias catalog.
fn large_storage_resource(alias_count: usize) -> Value {
let mut sections = serde_json::Map::new();
for i in 0..alias_count {
let section = i / 10;
let field = i % 10;
let section_key = format!("section{section}");
let section_obj = sections
.entry(section_key)
.or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
if let serde_json::Value::Object(m) = section_obj {
m.insert(format!("field{field}"), serde_json::Value::from(i));
}
}
Value::from(json!({
"name": "bigStorage",
"type": "Microsoft.Storage/storageAccounts",
"location": "westus2",
"properties": sections
}))
}
fn bench_normalize_large_catalog(c: &mut Criterion) {
let mut group = c.benchmark_group("normalize/large_catalog");
for alias_count in [50, 200] {
let catalog_json = large_alias_catalog(alias_count);
let mut reg = AliasRegistry::new();
reg.load_from_json(&catalog_json).unwrap();
let resource = large_storage_resource(alias_count);
group.bench_with_input(
BenchmarkId::from_parameter(alias_count),
&(reg, resource),
|b, (reg, res)| b.iter(|| normalizer::normalize(black_box(res), Some(reg), None)),
);
}
group.finish();
}
fn bench_denormalize_large_catalog(c: &mut Criterion) {
let mut group = c.benchmark_group("denormalize/large_catalog");
for alias_count in [50, 200] {
let catalog_json = large_alias_catalog(alias_count);
let mut reg = AliasRegistry::new();
reg.load_from_json(&catalog_json).unwrap();
let resource = large_storage_resource(alias_count);
let normalized = normalizer::normalize(&resource, Some(&reg), None);
group.bench_with_input(
BenchmarkId::from_parameter(alias_count),
&(reg, normalized),
|b, (reg, norm)| b.iter(|| denormalizer::denormalize(black_box(norm), Some(reg), None)),
);
}
group.finish();
}
fn bench_nsg_large_subarrays(c: &mut Criterion) {
let registry = build_registry();
let mut group = c.benchmark_group("round_trip/nsg_sub_resource");
for rule_count in [50, 200, 500] {
let resource = nsg_resource(rule_count);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&resource,
|b, res| {
b.iter(|| {
let n = normalizer::normalize(black_box(res), Some(&registry), None);
denormalizer::denormalize(&n, Some(&registry), None)
})
},
);
}
group.finish();
}
// ─── Versioned-path benchmarks ──────────────────────────────────────────────
//
// Exercise the precomputed versioned-path aggregates by building a catalog
// where wildcard (array) aliases have version-specific paths that differ from
// the default, then running normalize/denormalize with an explicit api_version.
/// NSG-like alias catalog where wildcard aliases have versioned paths that
/// differ from the default. This forces the normalize/denormalize path through
/// the versioned aggregate lookup rather than the default-aggregate fast path.
const VERSIONED_ALIASES_JSON: &str = r#"[
{
"namespace": "Microsoft.Network",
"resourceTypes": [
{
"resourceType": "networkSecurityGroups",
"aliases": [
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
"defaultPath": "properties.securityRules[*].properties.protocol",
"paths": [
{ "path": "properties.securityRules[*].properties.transportProtocol", "apiVersions": ["2020-01-01"] },
{ "path": "properties.securityRules[*].properties.protocol", "apiVersions": ["2022-01-01"] }
]
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
"defaultPath": "properties.securityRules[*].properties.access",
"paths": [
{ "path": "properties.securityRules[*].properties.accessLevel", "apiVersions": ["2020-01-01"] },
{ "path": "properties.securityRules[*].properties.access", "apiVersions": ["2022-01-01"] }
]
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].priority",
"defaultPath": "properties.securityRules[*].properties.priority",
"paths": [
{ "path": "properties.securityRules[*].properties.rulePriority", "apiVersions": ["2020-01-01"] },
{ "path": "properties.securityRules[*].properties.priority", "apiVersions": ["2022-01-01"] }
]
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].direction",
"defaultPath": "properties.securityRules[*].properties.direction",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].sourceAddressPrefix",
"defaultPath": "properties.securityRules[*].properties.sourceAddressPrefix",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].destinationPortRange",
"defaultPath": "properties.securityRules[*].properties.destinationPortRange",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
"defaultPath": "properties.securityRules[*].name",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/provisioningState",
"defaultPath": "properties.provisioningState",
"paths": [
{ "path": "properties.state", "apiVersions": ["2020-01-01"] },
{ "path": "properties.provisioningState", "apiVersions": ["2022-01-01"] }
]
}
]
}
]
}
]"#;
fn build_versioned_registry() -> AliasRegistry {
let mut reg = AliasRegistry::new();
reg.load_from_json(VERSIONED_ALIASES_JSON).unwrap();
reg
}
/// Build an NSG resource for versioned-path benchmarks.
/// Uses the 2020-01-01 field names (`transportProtocol`, `accessLevel`,
/// `rulePriority`) so that versioned path resolution actually differs from
/// the default.
fn nsg_versioned_resource(rule_count: usize) -> Value {
let rules: Vec<serde_json::Value> = (0..rule_count)
.map(|i| {
json!({
"name": format!("rule-{}", i),
"properties": {
"transportProtocol": "Tcp",
"accessLevel": if i % 2 == 0 { "Allow" } else { "Deny" },
"rulePriority": 100 + i,
"direction": "Inbound",
"sourceAddressPrefix": format!("10.0.{}.0/24", i % 256),
"destinationPortRange": format!("{}", 80 + i)
}
})
})
.collect();
to_regorus(json!({
"name": "myNsg",
"type": "Microsoft.Network/networkSecurityGroups",
"location": "eastus",
"properties": {
"state": "Succeeded",
"securityRules": rules
}
}))
}
fn bench_normalize_versioned(c: &mut Criterion) {
let registry = build_versioned_registry();
let mut group = c.benchmark_group("normalize_versioned/nsg_rules");
for rule_count in [5, 20, 100] {
let resource = nsg_versioned_resource(rule_count);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&resource,
|b, res| {
b.iter(|| {
normalizer::normalize(black_box(res), Some(&registry), Some("2020-01-01"))
})
},
);
}
group.finish();
}
fn bench_denormalize_versioned(c: &mut Criterion) {
let registry = build_versioned_registry();
let mut group = c.benchmark_group("denormalize_versioned/nsg_rules");
for rule_count in [5, 20, 100] {
let resource = nsg_versioned_resource(rule_count);
let normalized = normalizer::normalize(&resource, Some(&registry), Some("2020-01-01"));
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&normalized,
|b, norm| {
b.iter(|| {
denormalizer::denormalize(black_box(norm), Some(&registry), Some("2020-01-01"))
})
},
);
}
group.finish();
}
fn bench_round_trip_versioned(c: &mut Criterion) {
let registry = build_versioned_registry();
let mut group = c.benchmark_group("round_trip_versioned/nsg_rules");
for rule_count in [20, 100] {
let resource = nsg_versioned_resource(rule_count);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&resource,
|b, res| {
b.iter(|| {
let n =
normalizer::normalize(black_box(res), Some(&registry), Some("2020-01-01"));
denormalizer::denormalize(&n, Some(&registry), Some("2020-01-01"))
})
},
);
}
group.finish();
}
criterion_group!(
normalization_benches,
bench_normalize_simple,
bench_normalize_no_aliases,
bench_normalize_nsg_scaling,
bench_denormalize_simple,
bench_denormalize_nsg_scaling,
bench_round_trip,
bench_normalize_and_wrap,
bench_registry_load,
bench_normalize_large_catalog,
bench_denormalize_large_catalog,
bench_nsg_large_subarrays,
bench_normalize_versioned,
bench_denormalize_versioned,
bench_round_trip_versioned,
);
criterion_main!(normalization_benches);

680
benches/rvm_benchmark.rs Normal file
View File

@@ -0,0 +1,680 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Comprehensive RVM benchmarks covering all aspects of the Rego Virtual Machine.
//!
//! # Policy families
//!
//! | Family | Source | Policies | Inputs/policy |
//! |------------|-------------------------------|----------|---------------|
//! | Synthetic | `benches/evaluation/test_data`| 9 | 3 each |
//! | ACI | `tests/aci` | 9 | 1 each |
//!
//! # Benchmark groups
//!
//! | Group | What it measures |
//! |--------------------------|-------------------------------------------------------|
//! | `cold/{case}/{config}` | Cold: new VM + load + data + input + execute |
//! | `hot/{case}/{config}` | Hot: set_input + execute (VM reused across iters) |
//! | `compilation` | Rego CompiledPolicy → RVM Program |
//! | `serialization` | Program binary serialize / deserialize roundtrip |
//! | `startup` | Isolated VM creation & setup overhead |
//! | `stats` | Instruction/literal counts (reported as throughput) |
//! | `end_to_end` | Full roundtrip: compile → serialize → deserialize → eval |
//!
//! # Running subsets
//!
//! ```sh
//! cargo bench --bench rvm_benchmark # everything
//! cargo bench --bench rvm_benchmark -- cold # all cold eval
//! cargo bench --bench rvm_benchmark -- hot # all hot eval
//! cargo bench --bench rvm_benchmark -- regular_with_limits # one config across cases
//! cargo bench --bench rvm_benchmark -- cold/aci/ # all ACI cold benchmarks
//! cargo bench --bench rvm_benchmark -- rbac # one policy family
//! cargo bench --bench rvm_benchmark -- compilation # compilation only
//! cargo bench --bench rvm_benchmark -- serialization # serialization only
//! cargo bench --bench rvm_benchmark -- startup # startup overhead
//! ```
use std::hint::black_box;
use std::num::NonZeroU32;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
use regorus::languages::rego::compiler::Compiler;
use regorus::rvm::program::Program;
use regorus::rvm::vm::{ExecutionMode, RegoVM};
use regorus::utils::limits::ExecutionTimerConfig;
use regorus::{Engine, Rc, Value};
// ---------------------------------------------------------------------------
// Limit constants generous ceilings that still exercise the limit-checking
// hot path (memory_check, execution_timer_tick, instruction-limit compare).
// ---------------------------------------------------------------------------
#[cfg(feature = "allocator-memory-limits")]
const MEMORY_LIMIT_BYTES: u64 = 256 * 1024 * 1024;
const TIME_LIMIT: Duration = Duration::from_secs(30);
const TIMER_CHECK_INTERVAL: NonZeroU32 = NonZeroU32::new(16).unwrap();
const INSTRUCTION_LIMIT: usize = 10_000_000;
#[derive(Clone, Copy)]
struct EvalConfig {
name: &'static str,
mode: ExecutionMode,
limits: bool,
}
const EVAL_CONFIGS: [EvalConfig; 4] = [
EvalConfig {
name: "regular_no_limits",
mode: ExecutionMode::RunToCompletion,
limits: false,
},
EvalConfig {
name: "regular_with_limits",
mode: ExecutionMode::RunToCompletion,
limits: true,
},
EvalConfig {
name: "suspendable_no_limits",
mode: ExecutionMode::Suspendable,
limits: false,
},
EvalConfig {
name: "suspendable_with_limits",
mode: ExecutionMode::Suspendable,
limits: true,
},
];
// ---------------------------------------------------------------------------
// Data types
// ---------------------------------------------------------------------------
/// A compiled benchmark program ready for RVM execution.
struct BenchmarkProgram {
/// Human-readable name (e.g. "rbac_policy" or "aci/create_container").
name: String,
/// Pre-compiled RVM program.
program: Arc<Program>,
/// Compiled policy (kept for compilation benchmarks).
compiled_policy: regorus::CompiledPolicy,
/// Entry-point rule path.
entry_point: String,
/// Data object (Some for policies that require external data like ACI).
data: Option<Value>,
/// Named inputs for this policy.
inputs: Vec<(String, Value)>,
}
// ---------------------------------------------------------------------------
// ACI YAML types
// ---------------------------------------------------------------------------
#[derive(Serialize, Deserialize, Debug)]
struct AciTestCase {
note: String,
data: Value,
input: Value,
modules: Vec<String>,
query: String,
want_result: Value,
}
#[derive(Serialize, Deserialize, Debug)]
struct AciYamlTest {
cases: Vec<AciTestCase>,
}
// ---------------------------------------------------------------------------
// Synthetic policy loading
// ---------------------------------------------------------------------------
/// Policy ↔ input file mapping for synthetic policies.
const SYNTHETIC_POLICIES: &[(&str, &str, &[&str])] = &[
(
"rbac_policy",
"rbac_policy.rego",
&["rbac_input.json", "rbac_input2.json", "rbac_input3.json"],
),
(
"api_access",
"api_access_policy.rego",
&[
"api_access_input.json",
"api_access_input2.json",
"api_access_input3.json",
],
),
(
"data_sensitivity",
"data_sensitivity_policy.rego",
&[
"data_sensitivity_input.json",
"data_sensitivity_input2.json",
"data_sensitivity_input3.json",
],
),
(
"time_based",
"time_based_policy.rego",
&[
"time_based_input.json",
"time_based_input2.json",
"time_based_input3.json",
],
),
(
"data_processing",
"data_processing_policy.rego",
&[
"data_processing_input.json",
"data_processing_input2.json",
"data_processing_input3.json",
],
),
(
"azure_vm",
"azure_vm_policy.rego",
&[
"azure_vm_input.json",
"azure_vm_input2.json",
"azure_vm_input3.json",
],
),
(
"azure_storage",
"azure_storage_policy.rego",
&[
"azure_storage_input.json",
"azure_storage_input2.json",
"azure_storage_input3.json",
],
),
(
"azure_keyvault",
"azure_keyvault_policy.rego",
&[
"azure_keyvault_input.json",
"azure_keyvault_input2.json",
"azure_keyvault_input3.json",
],
),
(
"azure_nsg",
"azure_nsg_policy.rego",
&[
"azure_nsg_input.json",
"azure_nsg_input2.json",
"azure_nsg_input3.json",
],
),
];
/// Compile synthetic Rego policies into RVM programs.
fn compile_synthetic_programs() -> Vec<BenchmarkProgram> {
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("benches")
.join("evaluation")
.join("test_data");
let entry_point = "data.bench.allow";
let entry_point_rc: Rc<str> = entry_point.into();
SYNTHETIC_POLICIES
.iter()
.map(|(name, policy_file, input_files)| {
let policy_path = base_dir.join("policies").join(policy_file);
let policy_content = std::fs::read_to_string(&policy_path)
.unwrap_or_else(|e| panic!("Failed to read {policy_path:?}: {e}"));
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy_content)
.expect("failed to add policy");
let compiled_policy = engine
.compile_with_entrypoint(&entry_point_rc)
.expect("failed to compile policy");
let program = Compiler::compile_from_policy(&compiled_policy, &[entry_point])
.expect("failed to compile to RVM program");
let inputs: Vec<(String, Value)> = input_files
.iter()
.map(|input_file| {
let input_path = base_dir.join("inputs").join(input_file);
let json = std::fs::read_to_string(&input_path)
.unwrap_or_else(|e| panic!("Failed to read {input_path:?}: {e}"));
let value = Value::from_json_str(&json).expect("failed to parse input JSON");
let display = input_file.trim_end_matches(".json").to_string();
(display, value)
})
.collect();
BenchmarkProgram {
name: name.to_string(),
program,
compiled_policy,
entry_point: entry_point.to_string(),
data: None,
inputs,
}
})
.collect()
}
// ---------------------------------------------------------------------------
// ACI policy loading
// ---------------------------------------------------------------------------
/// Load all ACI test cases from YAML files.
fn load_aci_cases(dir: &Path) -> Vec<AciTestCase> {
let mut cases = Vec::new();
for entry in WalkDir::new(dir)
.sort_by_file_name()
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if !path.to_string_lossy().ends_with(".yaml") {
continue;
}
let yaml = std::fs::read(path).expect("failed to read yaml");
let yaml = String::from_utf8_lossy(&yaml);
let test: AciYamlTest = serde_yaml::from_str(&yaml).expect("failed to deserialize yaml");
cases.extend(test.cases);
}
cases
}
/// Build an Engine with policies loaded for a given ACI test case.
fn build_aci_engine(dir: &Path, case: &AciTestCase) -> Engine {
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_data(case.data.clone())
.expect("failed to add data");
engine.set_input(case.input.clone());
for (idx, rego) in case.modules.iter().enumerate() {
if rego.ends_with(".rego") {
engine
.add_policy_from_file(dir.join(rego).to_str().expect("invalid path"))
.expect("failed to add policy");
} else {
engine
.add_policy(format!("rego{idx}.rego"), rego.clone())
.expect("failed to add policy");
}
}
engine
}
/// Compile ACI test cases into RVM programs.
fn compile_aci_programs() -> Vec<BenchmarkProgram> {
let dir = Path::new("tests/aci");
load_aci_cases(dir)
.into_iter()
.map(|case| {
let mut engine = build_aci_engine(dir, &case);
let rule = case.query.replace("=x", "");
let rule_rc: Rc<str> = rule.clone().into();
let compiled_policy = engine
.compile_with_entrypoint(&rule_rc)
.expect("failed to compile");
let program = Compiler::compile_from_policy(&compiled_policy, &[rule.as_str()])
.expect("failed to compile to RVM");
BenchmarkProgram {
name: format!("aci/{}", case.note),
program,
compiled_policy,
entry_point: rule,
data: Some(case.data),
inputs: vec![("input".to_string(), case.input)],
}
})
.collect()
}
// ---------------------------------------------------------------------------
// Compile all policies
// ---------------------------------------------------------------------------
/// Compile all policies (synthetic + ACI) into RVM programs.
fn compile_all_programs() -> Vec<BenchmarkProgram> {
let mut programs = compile_synthetic_programs();
programs.extend(compile_aci_programs());
programs
}
// ---------------------------------------------------------------------------
// Limit helpers
// ---------------------------------------------------------------------------
/// Apply or remove production-style limits based on a boolean flag.
fn configure_limits(vm: &mut RegoVM, limits: bool) {
if limits {
#[cfg(feature = "allocator-memory-limits")]
regorus::set_global_memory_limit(Some(MEMORY_LIMIT_BYTES));
vm.set_execution_timer_config(Some(ExecutionTimerConfig {
limit: TIME_LIMIT,
check_interval: TIMER_CHECK_INTERVAL,
}));
vm.set_max_instructions(INSTRUCTION_LIMIT);
} else {
#[cfg(feature = "allocator-memory-limits")]
regorus::set_global_memory_limit(None);
vm.set_execution_timer_config(None);
vm.set_max_instructions(usize::MAX);
}
}
// ---------------------------------------------------------------------------
// Cold evaluation — new VM per iteration (full setup + execute)
//
// Benchmarks are registered case-first so each workload is shown with all
// config variants adjacent to one another, making per-case comparisons easier.
// ---------------------------------------------------------------------------
fn bench_cold(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("cold");
for bp in &programs {
for (input_name, input_value) in &bp.inputs {
let case_id = if bp.inputs.len() == 1 {
bp.name.clone()
} else {
format!("{}/{}", bp.name, input_name)
};
let program = bp.program.clone();
let data = bp.data.clone();
let input = input_value.clone();
for config in EVAL_CONFIGS {
group.bench_function(BenchmarkId::new(&case_id, config.name), |b| {
b.iter(|| {
let mut vm = RegoVM::new();
vm.set_execution_mode(config.mode);
vm.load_program(black_box(program.clone()));
if let Some(ref d) = data {
vm.set_data(black_box(d.clone())).unwrap();
}
vm.set_input(black_box(input.clone()));
configure_limits(&mut vm, config.limits);
black_box(vm.execute().unwrap())
})
});
}
}
}
group.finish();
}
// ---------------------------------------------------------------------------
// Hot evaluation — VM reused across iterations
//
// The VM is created once with program, data, mode, and limits. Each
// iteration only calls set_input + execute, measuring pure execution
// overhead with minimal setup. A warm-up execution fills the register
// window pool so all iterations benefit from pooled allocations.
// ---------------------------------------------------------------------------
fn bench_hot(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("hot");
for bp in &programs {
let program = bp.program.clone();
let data = bp.data.clone();
let inputs: Vec<Value> = bp.inputs.iter().map(|(_, v)| v.clone()).collect();
let num_inputs = inputs.len();
for config in EVAL_CONFIGS {
group.bench_function(BenchmarkId::new(&bp.name, config.name), |b| {
let mut vm = RegoVM::new();
vm.set_execution_mode(config.mode);
vm.load_program(program.clone());
if let Some(ref d) = data {
vm.set_data(d.clone()).unwrap();
}
configure_limits(&mut vm, config.limits);
// Warm up: fill register window pools, caches, etc.
vm.set_input(inputs[0].clone());
vm.execute().expect("warm-up failed");
let mut i = 0usize;
b.iter(|| {
let input = &inputs[i % num_inputs];
vm.set_input(black_box(input.clone()));
black_box(vm.execute().unwrap());
i += 1;
})
});
}
}
group.finish();
}
// ---------------------------------------------------------------------------
// Compilation — Rego CompiledPolicy → RVM Program
// ---------------------------------------------------------------------------
fn bench_compilation(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("compilation");
for bp in &programs {
let entry_point: &str = &bp.entry_point;
group.bench_with_input(
BenchmarkId::new("rego_to_rvm", &bp.name),
&bp.compiled_policy,
|b, compiled_policy| {
b.iter(|| {
Compiler::compile_from_policy(
black_box(compiled_policy),
black_box(&[entry_point]),
)
.unwrap();
})
},
);
}
group.finish();
}
// ---------------------------------------------------------------------------
// Serialization — binary serialize / deserialize roundtrip
// ---------------------------------------------------------------------------
fn bench_serialization(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("serialization");
for bp in &programs {
let program = &bp.program;
let serialized = program
.serialize_binary()
.expect("failed to serialize program");
let byte_len = serialized.len() as u64;
group.throughput(Throughput::Bytes(byte_len));
group.bench_function(BenchmarkId::new("serialize", &bp.name), |b| {
b.iter(|| black_box(program.serialize_binary().unwrap()))
});
group.throughput(Throughput::Bytes(byte_len));
group.bench_function(BenchmarkId::new("deserialize", &bp.name), |b| {
b.iter(|| black_box(Program::deserialize_binary(black_box(&serialized)).unwrap()))
});
}
group.finish();
}
// ---------------------------------------------------------------------------
// Startup — isolated VM creation & setup overhead
// ---------------------------------------------------------------------------
fn bench_startup(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("startup");
// Use the first program as representative for startup overhead.
let bp = &programs[0];
let program = bp.program.clone();
let input = bp.inputs[0].1.clone();
// Bare VM creation
group.bench_function("new", |b| b.iter(|| black_box(RegoVM::new())));
// load_program (Arc clone + internal setup)
group.bench_function("load_program", |b| {
b.iter(|| {
let mut vm = RegoVM::new();
vm.load_program(black_box(program.clone()));
black_box(&vm);
})
});
// set_input
group.bench_function("set_input", |b| {
let mut vm = RegoVM::new();
vm.load_program(program.clone());
b.iter(|| {
vm.set_input(black_box(input.clone()));
})
});
group.finish();
}
// ---------------------------------------------------------------------------
// Stats — instruction / literal counts (reported as throughput)
// ---------------------------------------------------------------------------
fn bench_stats(c: &mut Criterion) {
let programs = compile_all_programs();
eprintln!();
eprintln!(
"{:<30} {:>8} {:>8} {:>8} {:>10}",
"program", "instrs", "lits", "entries", "bytes"
);
eprintln!("{}", "-".repeat(70));
let mut group = c.benchmark_group("stats");
for bp in &programs {
let serialized = bp.program.serialize_binary().expect("serialize failed");
let byte_len = serialized.len();
let instr_count = bp.program.instructions.len();
let lit_count = bp.program.literals.len();
let entry_count = bp.program.entry_points.len();
eprintln!(
"{:<30} {:>8} {:>8} {:>8} {:>10}",
bp.name, instr_count, lit_count, entry_count, byte_len,
);
group.throughput(Throughput::Elements(instr_count as u64));
group.bench_function(BenchmarkId::new("serialize", &bp.name), |b| {
b.iter(|| black_box(bp.program.serialize_binary().unwrap()))
});
}
group.finish();
}
// ---------------------------------------------------------------------------
// End-to-end roundtrip (compile + serialize + deserialize + eval)
//
// Only runs for synthetic policies where we have direct access to rego
// source files. ACI policies are loaded from YAML with module references
// which makes the setup pipeline different.
// ---------------------------------------------------------------------------
fn bench_end_to_end(c: &mut Criterion) {
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("benches")
.join("evaluation")
.join("test_data");
let entry_point = "data.bench.allow";
let entry_point_rc: Rc<str> = entry_point.into();
let mut group = c.benchmark_group("end_to_end");
for &(name, policy_file, input_files) in SYNTHETIC_POLICIES {
let policy_path = base_dir.join("policies").join(policy_file);
let policy_content = std::fs::read_to_string(&policy_path)
.unwrap_or_else(|e| panic!("Failed to read {policy_path:?}: {e}"));
// Use just the first input for end-to-end
let input_path = base_dir.join("inputs").join(input_files[0]);
let input_json = std::fs::read_to_string(&input_path)
.unwrap_or_else(|e| panic!("Failed to read {input_path:?}: {e}"));
group.bench_function(BenchmarkId::new("roundtrip", name), |b| {
b.iter(|| {
// 1. Engine + parse
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy_content.clone())
.unwrap();
// 2. Compile to CompiledPolicy
let compiled_policy = engine.compile_with_entrypoint(&entry_point_rc).unwrap();
// 3. Compile to RVM Program
let program =
Compiler::compile_from_policy(&compiled_policy, &[entry_point]).unwrap();
// 4. Serialize
let bytes = program.serialize_binary().unwrap();
// 5. Deserialize
let deserialized = Program::deserialize_binary(&bytes).unwrap();
let program = match deserialized {
regorus::rvm::program::DeserializationResult::Complete(p) => Arc::new(p),
regorus::rvm::program::DeserializationResult::Partial(p) => {
Arc::new(Program::compile_from_partial(p).unwrap())
}
};
// 6. Execute
let mut vm = RegoVM::new();
vm.load_program(program);
let input = Value::from_json_str(&input_json).unwrap();
vm.set_input(input);
black_box(vm.execute().unwrap());
})
});
}
group.finish();
}
// ---------------------------------------------------------------------------
// Criterion groups — organised for selective runs
// ---------------------------------------------------------------------------
criterion_group!(cold_benches, bench_cold);
criterion_group!(hot_benches, bench_hot);
criterion_group!(
misc_benches,
bench_compilation,
bench_serialization,
bench_startup,
bench_stats,
bench_end_to_end,
);
criterion_main!(cold_benches, hot_benches, misc_benches);

View File

@@ -11,6 +11,20 @@ int main() {
if (r.status != Ok)
goto error;
// Configure the global pattern caches.
RegorusCacheConfig cache_config = { .regex = 256, .glob = 128 };
r = regorus_set_cache_config(cache_config);
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Raise the default col limit to 2000
RegorusPolicyLengthConfig len_config = { .max_col = 2000, .max_file_bytes = 1048576, .max_lines = 20000 };
r = regorus_engine_set_policy_length_config(engine, len_config);
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Load policies.
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
if (r.status != Ok)

View File

@@ -6,8 +6,19 @@ void example()
// Create engine
regorus::Engine engine;
// Configure the global pattern caches.
RegorusCacheConfig cache_config = { 256, 128 };
regorus::set_cache_config(cache_config);
engine.set_rego_v0(true);
engine.set_enable_coverage(true);
RegorusPolicyLengthConfig len_config;
// Raise the default col limit to 2000
len_config.max_col = 2000;
len_config.max_file_bytes = 1048576;
len_config.max_lines = 20000;
engine.set_policy_length_config(len_config);
// Add policies.
engine.add_policy("objects.rego",R"(package objects

View File

@@ -131,7 +131,15 @@ namespace regorus {
Result get_coverage_report_pretty() {
return Result(regorus_engine_get_coverage_report_pretty(engine));
}
Result set_policy_length_config(RegorusPolicyLengthConfig config) {
return Result(regorus_engine_set_policy_length_config(engine, config));
}
Result clear_policy_length_config() {
return Result(regorus_engine_clear_policy_length_config(engine));
}
~Engine() {
regorus_engine_drop(engine);
}
@@ -150,6 +158,14 @@ namespace regorus {
Engine& operator=(const Engine&) = delete;
};
inline Result set_cache_config(RegorusCacheConfig config) {
return Result(regorus_set_cache_config(config));
}
inline Result clear_cache() {
return Result(regorus_clear_cache());
}
class CompiledPolicy {
public:
explicit CompiledPolicy(RegorusCompiledPolicy* p) : policy(p) {}

View File

@@ -12,7 +12,7 @@ namespace Benchmarks
public class CompiledPolicyEvaluationBenchmark
{
private static readonly string TestDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
Directory.GetCurrentDirectory(),
"..", "..", "..",
"benches", "evaluation", "test_data"
);
@@ -33,7 +33,7 @@ namespace Benchmarks
private static readonly string[] PolicyNames = new[]
{
"rbac_policy",
"api_access_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
@@ -46,21 +46,21 @@ namespace Benchmarks
private static List<(string Policy, string[] Inputs)> LoadPoliciesWithInputs()
{
var result = new List<(string Policy, string[] Inputs)>();
foreach (var (policyFile, inputFiles) in PolicyInputFiles)
{
var policyPath = Path.Combine(TestDataPath, "policies", policyFile);
var policy = File.ReadAllText(policyPath);
var inputs = inputFiles.Select(inputFile =>
{
var inputPath = Path.Combine(TestDataPath, "inputs", inputFile);
return File.ReadAllText(inputPath);
}).ToArray();
result.Add((policy, inputs));
}
return result;
}
@@ -68,14 +68,14 @@ namespace Benchmarks
{
var policiesWithInputs = LoadPoliciesWithInputs();
var compiledPolicies = new List<CompiledPolicy>();
foreach (var (policy, _) in policiesWithInputs)
{
var modules = new[] { new PolicyModule { Id = "policy.rego", Content = policy } };
var modules = new[] { new PolicyModule("policy.rego", policy) };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
compiledPolicies.Add(compiled);
}
return compiledPolicies;
}
@@ -84,13 +84,13 @@ namespace Benchmarks
var cpuCount = Environment.ProcessorCount;
var maxThreads = cpuCount * 2;
var threadCounts = new List<int> { 1, 2 };
// Add even numbers from 4 to maxThreads
for (int i = 4; i <= maxThreads; i += 2)
{
threadCounts.Add(i);
}
Console.WriteLine($"Running compiled policy benchmark with max_threads: {maxThreads}");
Console.WriteLine($"Testing with thread counts: {string.Join(", ", threadCounts)}");
Console.WriteLine();
@@ -120,19 +120,19 @@ namespace Benchmarks
const int durationSeconds = 3;
var policiesWithInputs = LoadPoliciesWithInputs();
List<CompiledPolicy>? compiledPolicies = null;
if (useSharedPolicies)
{
compiledPolicies = PrepareSharedCompiledPolicies();
}
Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");
// Warmup phase
var (_, _, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: true);
Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");
// Actual benchmark phase
var (totalEvaluations, evaluationTime, policyCounters, allocatedBytes) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: false);
@@ -155,7 +155,7 @@ namespace Benchmarks
{
foreach (var policy in compiledPolicies)
{
policy.Dispose();
DisposeCompiledPolicy(policy);
}
}
@@ -173,8 +173,8 @@ namespace Benchmarks
}
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters, long allocatedBytes) RunBenchmarkPhase(
int threads,
int durationSeconds,
int threads,
int durationSeconds,
List<(string Policy, string[] Inputs)> policiesWithInputs,
List<CompiledPolicy>? compiledPolicies,
bool useSharedPolicies,
@@ -208,10 +208,10 @@ namespace Benchmarks
}
barrier.SignalAndWait();
int evaluationCount = 0;
var localEvaluationTime = TimeSpan.Zero;
while (!stopExecution)
{
// Use different policy for each iteration
@@ -226,23 +226,29 @@ namespace Benchmarks
{
// Measure only the evaluation call
var evalStopwatch = Stopwatch.StartNew();
if (useSharedPolicies)
{
var result = compiledPolicies![policyIdx].EvalWithInput(input);
}
else
{
// Compile policy in each iteration
var modules = new[] { new PolicyModule { Id = "policy.rego", Content = policy } };
// Compile policy in each iteration.
var modules = new[] { new PolicyModule("policy.rego", policy) };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
var result = compiled.EvalWithInput(input);
compiled.Dispose();
try
{
var result = compiled.EvalWithInput(input);
}
finally
{
DisposeCompiledPolicy(compiled);
}
}
evalStopwatch.Stop();
localEvaluationTime += evalStopwatch.Elapsed;
// Track successful evaluations (only during actual benchmark, not warmup)
if (!isWarmup)
{
@@ -256,10 +262,10 @@ namespace Benchmarks
{
// Ignore evaluation errors for benchmarking purposes
}
evaluationCount++;
}
// Store the actual evaluation time for this thread
if (!isWarmup)
{
@@ -284,11 +290,23 @@ namespace Benchmarks
var totalEvaluations = policyCounters.Values.Sum();
var totalEvaluationTime = evaluationTimes.Values.Aggregate(TimeSpan.Zero, (sum, time) => sum + time);
// Use pure evaluation time (consistent with Rust benchmark)
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;
return (totalEvaluations, evaluationTime, policyCounters, allocatedBytes);
}
private static void DisposeCompiledPolicy(CompiledPolicy policy)
{
try
{
policy.Dispose();
}
catch (TimeoutException ex)
{
Console.WriteLine($"Warning: {ex.Message}");
}
}
}
}

View File

@@ -12,7 +12,7 @@ namespace Benchmarks
public class EngineEvaluationBenchmark
{
private static readonly string TestDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
Directory.GetCurrentDirectory(),
"..", "..", "..",
"benches", "evaluation", "test_data"
);
@@ -33,7 +33,7 @@ namespace Benchmarks
private static readonly string[] PolicyNames = new[]
{
"rbac_policy",
"api_access_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
@@ -46,21 +46,21 @@ namespace Benchmarks
private static List<(string Policy, string[] Inputs)> LoadPoliciesWithInputs()
{
var result = new List<(string Policy, string[] Inputs)>();
foreach (var (policyFile, inputFiles) in PolicyInputFiles)
{
var policyPath = Path.Combine(TestDataPath, "policies", policyFile);
var policy = File.ReadAllText(policyPath);
var inputs = inputFiles.Select(inputFile =>
{
var inputPath = Path.Combine(TestDataPath, "inputs", inputFile);
return File.ReadAllText(inputPath);
}).ToArray();
result.Add((policy, inputs));
}
return result;
}
@@ -68,12 +68,12 @@ namespace Benchmarks
{
var policiesWithInputs = LoadPoliciesWithInputs();
var engines = new List<Engine>();
foreach (var (policy, _) in policiesWithInputs)
{
var engine = new Engine();
engine.AddPolicy("policy.rego", policy);
// Warm up the engine to ensure it's fully prepared for evaluation
// This prevents each cloned engine from repeating preparation work
engine.SetInputJson("{}");
@@ -85,10 +85,10 @@ namespace Benchmarks
{
// Ignore warmup errors
}
engines.Add(engine);
}
return engines;
}
@@ -97,13 +97,13 @@ namespace Benchmarks
var cpuCount = Environment.ProcessorCount;
var maxThreads = cpuCount * 2;
var threadCounts = new List<int> { 1, 2 };
// Add even numbers from 4 to maxThreads
for (int i = 4; i <= maxThreads; i += 2)
{
threadCounts.Add(i);
}
Console.WriteLine($"Running engine benchmark with max_threads: {maxThreads}");
Console.WriteLine($"Testing with thread counts: {string.Join(", ", threadCounts)}");
Console.WriteLine();
@@ -132,14 +132,14 @@ namespace Benchmarks
const int warmupSeconds = 3;
const int durationSeconds = 3;
var policiesWithInputs = LoadPoliciesWithInputs();
Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");
// Warmup phase
var (_, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, useClonedEngines, isWarmup: true);
Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");
// Actual benchmark phase
var (totalEvaluations, evaluationTime, policyCounters) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, useClonedEngines, isWarmup: false);
@@ -165,8 +165,8 @@ namespace Benchmarks
}
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters) RunBenchmarkPhase(
int threads,
int durationSeconds,
int threads,
int durationSeconds,
List<(string Policy, string[] Inputs)> policiesWithInputs,
bool useClonedEngines,
bool isWarmup)
@@ -199,10 +199,10 @@ namespace Benchmarks
tasks[threadId] = Task.Run(() =>
{
barrier.SignalAndWait();
int evaluationCount = 0;
var localEvaluationTime = TimeSpan.Zero;
while (!stopExecution)
{
// Use different policy for each iteration
@@ -217,7 +217,7 @@ namespace Benchmarks
{
// Measure only the engine operations
var evalStopwatch = Stopwatch.StartNew();
Engine engine;
if (useClonedEngines)
{
@@ -228,14 +228,14 @@ namespace Benchmarks
engine = new Engine();
engine.AddPolicy("policy.rego", policy);
}
engine.SetInputJson(input);
var result = engine.EvalRule("data.bench.allow");
engine.Dispose();
evalStopwatch.Stop();
localEvaluationTime += evalStopwatch.Elapsed;
// Track successful evaluations (only during actual benchmark, not warmup)
if (!isWarmup)
{
@@ -249,10 +249,10 @@ namespace Benchmarks
{
// Ignore evaluation errors for benchmarking purposes
}
evaluationCount++;
}
// Store the actual evaluation time for this thread
if (!isWarmup)
{
@@ -283,10 +283,10 @@ namespace Benchmarks
var totalEvaluations = policyCounters.Values.Sum();
var totalEvaluationTime = evaluationTimes.Values.Aggregate(TimeSpan.Zero, (sum, time) => sum + time);
// Use pure evaluation time (consistent with Rust benchmark)
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;
return (totalEvaluations, evaluationTime, policyCounters);
}
}

View File

@@ -7,7 +7,7 @@ namespace Benchmarks
static void Main(string[] args)
{
Console.WriteLine("=== Regorus C# Benchmarks ===\n");
try
{
Console.WriteLine("Running Engine Evaluation Benchmark...");
@@ -17,9 +17,9 @@ namespace Benchmarks
{
Console.WriteLine($"Engine benchmark failed: {ex.Message}");
}
Console.WriteLine("\n" + new string('=', 80) + "\n");
try
{
Console.WriteLine("Running Compiled Policy Evaluation Benchmark...");
@@ -29,7 +29,7 @@ namespace Benchmarks
{
Console.WriteLine($"Compiled policy benchmark failed: {ex.Message}");
}
Console.WriteLine("\n=== Benchmarks Complete ===");
}
}

View File

@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<RegorusPackageVersion>0.9.0</RegorusPackageVersion>
<RegorusPackageVersion>0.9.1</RegorusPackageVersion>
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
@@ -10,5 +10,6 @@
<PackageVersion Include="Regorus" Version="$(RegorusPackageVersion)$(RegorusPackageVersionSuffix)" />
<PackageVersion Include="MSTest" Version="3.8.2" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="YamlDotNet" Version="13.7.0" />
</ItemGroup>
</Project>

View File

@@ -104,3 +104,49 @@ vm.SetInputJson(Input);
var result = vm.Execute();
Console.WriteLine($"allow: {result}");
```
## Azure RBAC Condition Evaluation
Evaluate Azure RBAC condition expressions directly with a JSON evaluation context:
```csharp
using Regorus;
const string Condition = "@Resource[owner] StringEquals 'alice'";
const string ContextJson = """
{
"principal": {
"id": "user-1",
"principal_type": "User",
"custom_security_attributes": {}
},
"resource": {
"id": "/subscriptions/s1",
"resource_type": "Microsoft.Storage/storageAccounts",
"scope": "/subscriptions/s1",
"attributes": {
"owner": "alice",
"confidential": true
}
},
"request": {
"action": "Microsoft.Storage/storageAccounts/read",
"data_action": null,
"attributes": {
"clientIP": "10.0.0.1"
}
},
"environment": {
"is_private_link": null,
"private_endpoint": null,
"subnet": null,
"utc_now": "2023-05-01T12:00:00Z"
},
"action": "Microsoft.Storage/storageAccounts/read",
"suboperation": null
}
""";
var allowed = RbacEngine.EvaluateCondition(Condition, ContextJson);
Console.WriteLine($"RBAC condition allowed: {allowed}");
```

View File

@@ -0,0 +1,191 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
namespace Regorus.Tests;
[TestClass]
public class AliasRegistryTests
{
private const string AliasesJson = @"[{
""namespace"": ""Microsoft.Storage"",
""resourceTypes"": [{
""resourceType"": ""storageAccounts"",
""aliases"": [{
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
""paths"": []
}, {
""name"": ""Microsoft.Storage/storageAccounts/accessTier"",
""defaultPath"": ""properties.accessTier"",
""paths"": []
}]
}]
}]";
private const string ManifestJson = @"{
""dataNamespace"": ""Microsoft.KeyVault.Data"",
""aliases"": [],
""resourceTypeAliases"": [{
""resourceType"": ""vaults/certificates"",
""aliases"": [{
""name"": ""Microsoft.KeyVault.Data/vaults/certificates/keySize"",
""paths"": [{ ""path"": ""keySize"", ""apiVersions"": [""7.0""] }]
}]
}]
}";
[TestMethod]
public void Create_and_dispose_succeeds()
{
using var registry = new AliasRegistry();
Assert.AreEqual(0, registry.Length);
}
[TestMethod]
public void LoadJson_populates_registry()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
Assert.AreEqual(1, registry.Length);
}
[TestMethod]
public void LoadManifest_populates_registry()
{
using var registry = new AliasRegistry();
registry.LoadManifest(ManifestJson);
Assert.AreEqual(1, registry.Length);
}
[TestMethod]
public void NormalizeAndWrap_produces_envelope()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
}";
var result = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
Assert.IsNotNull(result);
var envelope = JsonNode.Parse(result!)!;
Assert.IsNotNull(envelope["resource"]);
Assert.IsNotNull(envelope["parameters"]);
Assert.IsNotNull(envelope["context"]);
// Normalized resource should have lowercased alias field names
var res = envelope["resource"]!;
Assert.AreEqual(true, res["supportshttpstrafficonly"]?.GetValue<bool>());
Assert.AreEqual("Hot", res["accesstier"]?.GetValue<string>());
Assert.AreEqual("acct1", res["name"]?.GetValue<string>());
}
[TestMethod]
public void NormalizeAndWrap_with_context_and_parameters()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": { ""supportsHttpsTrafficOnly"": true }
}";
var context = @"{""resourceGroup"": {""name"": ""rg1""}}";
var parameters = @"{""env"": ""prod""}";
var result = registry.NormalizeAndWrap(resource, "2023-01-01", context, parameters);
Assert.IsNotNull(result);
var envelope = JsonNode.Parse(result!)!;
Assert.AreEqual("rg1", envelope["context"]!["resourceGroup"]!["name"]?.GetValue<string>());
Assert.AreEqual("prod", envelope["parameters"]!["env"]?.GetValue<string>());
}
[TestMethod]
public void Denormalize_restores_properties()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
var normalized = @"{
""name"": ""acct1"",
""type"": ""Microsoft.Storage/storageAccounts"",
""supportshttpstrafficonly"": true,
""accesstier"": ""Hot""
}";
var result = registry.Denormalize(normalized, "2023-01-01");
Assert.IsNotNull(result);
var arm = JsonNode.Parse(result!)!;
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
}
[TestMethod]
public void Round_trip_normalize_then_denormalize()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
}";
// Normalize
var envelopeJson = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
Assert.IsNotNull(envelopeJson);
var envelope = JsonNode.Parse(envelopeJson!)!;
var normalizedResource = envelope["resource"]!.ToJsonString();
// Denormalize
var armJson = registry.Denormalize(normalizedResource, "2023-01-01");
Assert.IsNotNull(armJson);
var arm = JsonNode.Parse(armJson!)!;
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
}
[TestMethod]
public void DataPlane_manifest_normalize()
{
using var registry = new AliasRegistry();
registry.LoadManifest(ManifestJson);
var resource = @"{
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
""keySize"": 2048
}";
var result = registry.NormalizeAndWrap(resource, "7.0", "{}", "{}");
Assert.IsNotNull(result);
var envelope = JsonNode.Parse(result!)!;
Assert.AreEqual(2048, envelope["resource"]!["keysize"]?.GetValue<int>());
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void LoadJson_invalid_throws()
{
using var registry = new AliasRegistry();
registry.LoadJson("not valid json");
}
}

View File

@@ -0,0 +1,320 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
namespace Regorus.Tests;
[TestClass]
[DoNotParallelize]
public class MemoryGrowthTests
{
private static int Iterations =>
int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_ITERS"), out var value) ? value : 50_000;
private static int LogEvery =>
int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_LOG_EVERY"), out var value) ? value : 500;
private static int GcEvery
{
get
{
if (!int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_GC_EVERY"), out var value))
{
value = LogEvery;
}
return value <= 0 ? LogEvery : value;
}
}
private static long? MaxWorkingSetDeltaBytes
{
get
{
if (!long.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_MAX_DELTA_MB"), out var mb))
{
mb = 32;
}
if (mb <= 0)
{
return null;
}
return mb * 1024L * 1024L;
}
}
private static ulong? GlobalRegorusMemoryLimitBytes
{
get
{
if (!ulong.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_GLOBAL_REGORUS_LIMIT_MB"), out var mb))
{
return null;
}
if (mb == 0)
{
return null;
}
return mb * 1024UL * 1024UL;
}
}
private static void WithOptionalGlobalRegorusMemoryLimit(Action action)
{
var priorLimit = MemoryLimits.GetGlobalMemoryLimit();
try
{
if (GlobalRegorusMemoryLimitBytes is { } limit)
{
MemoryLimits.SetGlobalMemoryLimit(limit);
}
action();
}
finally
{
MemoryLimits.SetGlobalMemoryLimit(priorLimit);
}
}
private static void ForceFullGc()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
[TestMethod]
public void Engine_create_eval_dispose_does_not_grow_working_set()
{
WithOptionalGlobalRegorusMemoryLimit(() =>
{
var process = Process.GetCurrentProcess();
process.Refresh();
var baseline = process.WorkingSet64;
var maxDelta = 0L;
var baselineManaged = GC.GetTotalMemory(false);
var maxManagedDelta = 0L;
for (var i = 1; i <= Iterations; i++)
{
using (var engine = new Engine())
{
engine.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`");
_ = engine.EvalRule("data.test.message");
}
if (i % LogEvery == 0)
{
process.Refresh();
var workingSet = process.WorkingSet64;
var managed = GC.GetTotalMemory(false);
var delta = workingSet - baseline;
var managedDelta = managed - baselineManaged;
if (delta > maxDelta)
{
maxDelta = delta;
}
if (managedDelta > maxManagedDelta)
{
maxManagedDelta = managedDelta;
}
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
}
}
if (MaxWorkingSetDeltaBytes is { } limit)
{
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
Assert.IsTrue(
maxDelta <= limit,
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
}
});
}
[TestMethod]
public void Engine_create_eval_finalize_does_not_grow_working_set()
{
WithOptionalGlobalRegorusMemoryLimit(() =>
{
var process = Process.GetCurrentProcess();
process.Refresh();
var baseline = process.WorkingSet64;
var maxDelta = 0L;
var baselineManaged = GC.GetTotalMemory(false);
var maxManagedDelta = 0L;
for (var i = 1; i <= Iterations; i++)
{
var engine = new Engine();
engine.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`");
_ = engine.EvalRule("data.test.message");
if (i % GcEvery == 0)
{
ForceFullGc();
}
if (i % LogEvery == 0)
{
process.Refresh();
var workingSet = process.WorkingSet64;
var managed = GC.GetTotalMemory(false);
var delta = workingSet - baseline;
var managedDelta = managed - baselineManaged;
if (delta > maxDelta)
{
maxDelta = delta;
}
if (managedDelta > maxManagedDelta)
{
maxManagedDelta = managedDelta;
}
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
}
}
if (MaxWorkingSetDeltaBytes is { } limit)
{
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
Assert.IsTrue(
maxDelta <= limit,
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
}
});
}
[TestMethod]
public void Rvm_rehydrate_execute_dispose_does_not_grow_working_set()
{
WithOptionalGlobalRegorusMemoryLimit(() =>
{
var modules = new[]
{
new PolicyModule("test.rego", "package test\nallow = true"),
};
using var compiled = Program.CompileFromModules("{}", modules, new[] { "data.test.allow" });
var serialized = compiled.SerializeBinary();
var process = Process.GetCurrentProcess();
process.Refresh();
var baseline = process.WorkingSet64;
var maxDelta = 0L;
var baselineManaged = GC.GetTotalMemory(false);
var maxManagedDelta = 0L;
for (var i = 1; i <= Iterations; i++)
{
using (var vm = new Rvm())
using (var program = Program.DeserializeBinary(serialized, out _))
{
vm.LoadProgram(program);
vm.SetDataJson("{}");
vm.SetInputJson("{}");
_ = vm.ExecuteEntryPoint(0);
}
if (i % LogEvery == 0)
{
process.Refresh();
var workingSet = process.WorkingSet64;
var managed = GC.GetTotalMemory(false);
var delta = workingSet - baseline;
var managedDelta = managed - baselineManaged;
if (delta > maxDelta)
{
maxDelta = delta;
}
if (managedDelta > maxManagedDelta)
{
maxManagedDelta = managedDelta;
}
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
}
}
if (MaxWorkingSetDeltaBytes is { } limit)
{
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
Assert.IsTrue(
maxDelta <= limit,
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
}
});
}
[TestMethod]
public void Rvm_rehydrate_execute_finalize_does_not_grow_working_set()
{
WithOptionalGlobalRegorusMemoryLimit(() =>
{
var modules = new[]
{
new PolicyModule("test.rego", "package test\nallow = true"),
};
using var compiled = Program.CompileFromModules("{}", modules, new[] { "data.test.allow" });
var serialized = compiled.SerializeBinary();
var process = Process.GetCurrentProcess();
process.Refresh();
var baseline = process.WorkingSet64;
var maxDelta = 0L;
var baselineManaged = GC.GetTotalMemory(false);
var maxManagedDelta = 0L;
for (var i = 1; i <= Iterations; i++)
{
var vm = new Rvm();
var program = Program.DeserializeBinary(serialized, out _);
vm.LoadProgram(program);
vm.SetDataJson("{}");
vm.SetInputJson("{}");
_ = vm.ExecuteEntryPoint(0);
if (i % GcEvery == 0)
{
ForceFullGc();
}
if (i % LogEvery == 0)
{
process.Refresh();
var workingSet = process.WorkingSet64;
var managed = GC.GetTotalMemory(false);
var delta = workingSet - baseline;
var managedDelta = managed - baselineManaged;
if (delta > maxDelta)
{
maxDelta = delta;
}
if (managedDelta > maxManagedDelta)
{
maxManagedDelta = managedDelta;
}
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
}
}
if (MaxWorkingSetDeltaBytes is { } limit)
{
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
Assert.IsTrue(
maxDelta <= limit,
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
}
});
}
}

View File

@@ -0,0 +1,374 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
using YamlDotNet.Serialization;
namespace Regorus.Tests;
[TestClass]
public class RbacEngineTests
{
public TestContext? TestContext { get; set; }
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = false
};
private const string BaseContextJson = """
{
"principal": {
"id": "user-1",
"principal_type": "User",
"custom_security_attributes": {
"department": "eng",
"levels": ["L1", "L2"]
}
},
"resource": {
"id": "/subscriptions/s1",
"resource_type": "Microsoft.Storage/storageAccounts",
"scope": "/subscriptions/s1",
"attributes": {
"owner": "alice",
"tags": ["a", "b"],
"count": 5,
"enabled": false,
"ip": "10.0.0.5",
"guid": "a1b2c3d4-0000-0000-0000-000000000000"
}
},
"request": {
"action": "Microsoft.Storage/storageAccounts/read",
"data_action": "Microsoft.Storage/storageAccounts/read",
"attributes": {
"owner": "alice",
"text": "HelloWorld",
"tags": ["prod", "gold"],
"count": 10,
"ratio": 2.5,
"enabled": true,
"ip": "10.0.0.8",
"guid": "A1B2C3D4-0000-0000-0000-000000000000",
"time": "12:30:15",
"date": "2023-05-01T12:00:00Z",
"numbers": [1, 2, 3],
"letters": ["a", "b"]
}
},
"environment": {
"is_private_link": false,
"private_endpoint": null,
"subnet": null,
"utc_now": "2023-05-01T12:00:00Z"
},
"action": "Microsoft.Storage/storageAccounts/read",
"suboperation": "sub/read"
}
""";
[TestMethod]
public void Rbac_engine_evaluates_all_yaml_cases()
{
var cases = LoadEvalTestCases().ToList();
Assert.IsTrue(cases.Count > 0, "No RBAC test cases were loaded.");
foreach (var testCase in cases)
{
TestContext?.WriteLine($"RBAC case: {testCase.Name} -> {testCase.Condition}");
var context = BuildBaseContext();
if (testCase.Context != null)
{
ApplyOverrides(context, testCase.Context);
}
var contextJson = context.ToJsonString(JsonOptions);
var result = RbacEngine.EvaluateCondition(testCase.Condition, contextJson);
Assert.AreEqual(
testCase.Expected,
result,
$"RBAC test '{testCase.Name}' failed for condition '{testCase.Condition}'.");
}
}
private static JsonObject BuildBaseContext()
{
var node = JsonNode.Parse(BaseContextJson) as JsonObject;
if (node is null)
{
throw new InvalidOperationException("Failed to parse base context JSON.");
}
return node;
}
private static void ApplyOverrides(JsonObject context, EvalContextOverrides overrides)
{
var principal = (JsonObject?)context["principal"]
?? throw new InvalidOperationException("Missing principal section.");
var resource = (JsonObject?)context["resource"]
?? throw new InvalidOperationException("Missing resource section.");
var request = (JsonObject?)context["request"]
?? throw new InvalidOperationException("Missing request section.");
var environment = (JsonObject?)context["environment"]
?? throw new InvalidOperationException("Missing environment section.");
if (!string.IsNullOrEmpty(overrides.Action))
{
context["action"] = overrides.Action;
}
if (!string.IsNullOrEmpty(overrides.Suboperation))
{
context["suboperation"] = overrides.Suboperation;
}
if (!string.IsNullOrEmpty(overrides.RequestAction))
{
request["action"] = overrides.RequestAction;
}
if (!string.IsNullOrEmpty(overrides.DataAction))
{
request["data_action"] = overrides.DataAction;
}
if (!string.IsNullOrEmpty(overrides.PrincipalId))
{
principal["id"] = overrides.PrincipalId;
}
if (!string.IsNullOrEmpty(overrides.PrincipalType))
{
principal["principal_type"] = overrides.PrincipalType;
}
if (!string.IsNullOrEmpty(overrides.ResourceId))
{
resource["id"] = overrides.ResourceId;
}
if (!string.IsNullOrEmpty(overrides.ResourceType))
{
resource["resource_type"] = overrides.ResourceType;
}
if (!string.IsNullOrEmpty(overrides.ResourceScope))
{
resource["scope"] = overrides.ResourceScope;
}
if (overrides.RequestAttributes != null)
{
request["attributes"] = ConvertToJsonNode(overrides.RequestAttributes);
}
if (overrides.ResourceAttributes != null)
{
resource["attributes"] = ConvertToJsonNode(overrides.ResourceAttributes);
}
if (overrides.PrincipalCustomSecurityAttributes != null)
{
principal["custom_security_attributes"] = ConvertToJsonNode(overrides.PrincipalCustomSecurityAttributes);
}
if (overrides.Environment != null)
{
if (overrides.Environment.IsPrivateLink.HasValue)
{
environment["is_private_link"] = overrides.Environment.IsPrivateLink.Value;
}
if (!string.IsNullOrEmpty(overrides.Environment.PrivateEndpoint))
{
environment["private_endpoint"] = overrides.Environment.PrivateEndpoint;
}
if (!string.IsNullOrEmpty(overrides.Environment.Subnet))
{
environment["subnet"] = overrides.Environment.Subnet;
}
if (!string.IsNullOrEmpty(overrides.Environment.UtcNow))
{
environment["utc_now"] = overrides.Environment.UtcNow;
}
}
}
private static IEnumerable<EvalTestCase> LoadEvalTestCases()
{
var baseDir = Path.Combine(AppContext.BaseDirectory, "test_cases");
if (!Directory.Exists(baseDir))
{
throw new DirectoryNotFoundException($"RBAC test case directory not found: {baseDir}");
}
var deserializer = new DeserializerBuilder()
.IgnoreUnmatchedProperties()
.Build();
var files = Directory.EnumerateFiles(baseDir, "*.yaml")
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase);
foreach (var file in files)
{
var yaml = File.ReadAllText(file);
var suite = deserializer.Deserialize<EvalTestSuite>(yaml);
if (suite?.TestCases is null)
{
continue;
}
foreach (var testCase in suite.TestCases)
{
yield return testCase;
}
}
}
private static JsonNode? ConvertToJsonNode(object? value)
{
if (value is null)
{
return null;
}
switch (value)
{
case JsonNode node:
return node;
case string text:
return JsonValue.Create(text);
case bool boolean:
return JsonValue.Create(boolean);
case int intValue:
return JsonValue.Create(intValue);
case long longValue:
return JsonValue.Create(longValue);
case double doubleValue:
return JsonValue.Create(doubleValue);
case float floatValue:
return JsonValue.Create(floatValue);
case decimal decimalValue:
return JsonValue.Create(decimalValue);
case DateTime dateTime:
return JsonValue.Create(dateTime.ToString("O"));
case IDictionary dictionary:
{
var obj = new JsonObject();
foreach (DictionaryEntry entry in dictionary)
{
var key = entry.Key?.ToString() ?? string.Empty;
obj[key] = ConvertToJsonNode(entry.Value);
}
return obj;
}
case IEnumerable enumerable:
{
if (value is string)
{
return JsonValue.Create(value.ToString());
}
var array = new JsonArray();
foreach (var item in enumerable)
{
array.Add(ConvertToJsonNode(item));
}
return array;
}
default:
return JsonValue.Create(value.ToString());
}
}
private sealed class EvalTestSuite
{
[YamlMember(Alias = "test_cases")]
public List<EvalTestCase> TestCases { get; set; } = new();
}
private sealed class EvalTestCase
{
[YamlMember(Alias = "name")]
public string Name { get; set; } = string.Empty;
[YamlMember(Alias = "condition")]
public string Condition { get; set; } = string.Empty;
[YamlMember(Alias = "expected")]
public bool Expected { get; set; }
[YamlMember(Alias = "context")]
public EvalContextOverrides? Context { get; set; }
}
private sealed class EvalContextOverrides
{
[YamlMember(Alias = "action")]
public string? Action { get; set; }
[YamlMember(Alias = "suboperation")]
public string? Suboperation { get; set; }
[YamlMember(Alias = "request_action")]
public string? RequestAction { get; set; }
[YamlMember(Alias = "data_action")]
public string? DataAction { get; set; }
[YamlMember(Alias = "principal_id")]
public string? PrincipalId { get; set; }
[YamlMember(Alias = "principal_type")]
public string? PrincipalType { get; set; }
[YamlMember(Alias = "resource_id")]
public string? ResourceId { get; set; }
[YamlMember(Alias = "resource_type")]
public string? ResourceType { get; set; }
[YamlMember(Alias = "resource_scope")]
public string? ResourceScope { get; set; }
[YamlMember(Alias = "request_attributes")]
public object? RequestAttributes { get; set; }
[YamlMember(Alias = "resource_attributes")]
public object? ResourceAttributes { get; set; }
[YamlMember(Alias = "principal_custom_security_attributes")]
public object? PrincipalCustomSecurityAttributes { get; set; }
[YamlMember(Alias = "environment")]
public EvalEnvironmentOverrides? Environment { get; set; }
}
private sealed class EvalEnvironmentOverrides
{
[YamlMember(Alias = "is_private_link")]
public bool? IsPrivateLink { get; set; }
[YamlMember(Alias = "private_endpoint")]
public string? PrivateEndpoint { get; set; }
[YamlMember(Alias = "subnet")]
public string? Subnet { get; set; }
[YamlMember(Alias = "utc_now")]
public string? UtcNow { get; set; }
}
}

View File

@@ -20,9 +20,14 @@
<ItemGroup>
<PackageReference Include="MSTest" />
<PackageReference Include="YamlDotNet" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Regorus" />
</ItemGroup>
<ItemGroup>
<None Include="../../../src/languages/azure_rbac/test_cases/*.yaml" Link="test_cases/%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -193,10 +193,19 @@ public class RegorusTests
var result = engine.GetPolicyPackageNames();
var packageNames = JsonNode.Parse(result!);
Assert.IsNotNull(result);
Assert.AreEqual("test", packageNames![0]["package_name"].ToString());
Assert.AreEqual("test.nested.name", packageNames![1]["package_name"].ToString());
var packageNames = JsonNode.Parse(result);
Assert.IsNotNull(packageNames);
var packageArray = packageNames.AsArray();
var firstPackage = packageArray[0]?.AsObject();
var secondPackage = packageArray[1]?.AsObject();
Assert.IsNotNull(firstPackage);
Assert.IsNotNull(secondPackage);
Assert.AreEqual("test", firstPackage!["package_name"]!.GetValue<string>());
Assert.AreEqual("test.nested.name", secondPackage!["package_name"]!.GetValue<string>());
}
[TestMethod]
@@ -209,71 +218,84 @@ public class RegorusTests
var result = engine.GetPolicyParameters();
var parameters = JsonNode.Parse(result!);
Assert.IsNotNull(result);
Assert.AreEqual(1, parameters![0]["parameters"].AsArray().Count);
Assert.AreEqual(1, parameters![0]["modifiers"].AsArray().Count);
var parameters = JsonNode.Parse(result);
Assert.IsNotNull(parameters);
Assert.AreEqual("a", parameters![0]["parameters"][0]["name"].ToString());
Assert.AreEqual("b", parameters![0]["modifiers"][0]["name"].ToString());
var parametersArray = parameters.AsArray();
var firstEntry = parametersArray[0]?.AsObject();
Assert.IsNotNull(firstEntry);
var parameterList = firstEntry!["parameters"]!.AsArray();
var modifierList = firstEntry["modifiers"]!.AsArray();
Assert.AreEqual(1, parameterList.Count);
Assert.AreEqual(1, modifierList.Count);
var parameterName = parameterList[0]?.AsObject()?["name"]?.GetValue<string>();
var modifierName = modifierList[0]?.AsObject()?["name"]?.GetValue<string>();
Assert.AreEqual("a", parameterName);
Assert.AreEqual("b", modifierName);
}
[TestMethod]
public void Global_memory_limit_can_be_set_and_cleared()
{
[TestMethod]
public void Global_memory_limit_can_be_set_and_cleared()
{
lock (LimitLock)
{
using var guard = new MemoryLimitScope();
using var guard = new MemoryLimitScope();
MemoryLimits.SetGlobalMemoryLimit(null);
Assert.IsNull(MemoryLimits.GetGlobalMemoryLimit());
const ulong limit = 32 * 1024;
MemoryLimits.SetGlobalMemoryLimit(limit);
Assert.AreEqual(limit, MemoryLimits.GetGlobalMemoryLimit());
MemoryLimits.SetGlobalMemoryLimit(null);
Assert.IsNull(MemoryLimits.GetGlobalMemoryLimit());
}
}
[TestMethod]
public void Memory_limit_violations_surface_from_engine_calls()
{
lock (LimitLock)
{
using var guard = new MemoryLimitScope();
using var engine = new Engine();
const ulong limit = 1;
var payload = new string('x', 128 * 1024);
MemoryLimits.FlushThreadMemoryCounters();
MemoryLimits.SetGlobalMemoryLimit(limit);
try
{
var ex = Assert.ThrowsException<InvalidOperationException>(
() => engine.SetInputJson($"{{\"payload\":\"{payload}\"}}"));
StringAssert.Contains(ex.Message, "execution exceeded memory limit");
}
finally
{
MemoryLimits.SetGlobalMemoryLimit(null);
MemoryLimits.FlushThreadMemoryCounters();
}
}
}
Assert.IsNull(MemoryLimits.GetGlobalMemoryLimit());
[TestMethod]
public void Evaluation_fails_when_input_pushes_policy_over_global_limit()
{
const ulong limit = 32 * 1024;
MemoryLimits.SetGlobalMemoryLimit(limit);
Assert.AreEqual(limit, MemoryLimits.GetGlobalMemoryLimit());
MemoryLimits.SetGlobalMemoryLimit(null);
Assert.IsNull(MemoryLimits.GetGlobalMemoryLimit());
}
}
[TestMethod]
public void Memory_limit_violations_surface_from_engine_calls()
{
lock (LimitLock)
{
using var guard = new MemoryLimitScope();
using var engine = new Engine();
using var guard = new MemoryLimitScope();
using var engine = new Engine();
const string policy = """
const ulong limit = 1;
var payload = new string('x', 128 * 1024);
MemoryLimits.FlushThreadMemoryCounters();
MemoryLimits.SetGlobalMemoryLimit(limit);
try
{
var ex = Assert.ThrowsException<InvalidOperationException>(
() => engine.SetInputJson($"{{\"payload\":\"{payload}\"}}"));
StringAssert.Contains(ex.Message, "execution exceeded memory limit");
}
finally
{
MemoryLimits.SetGlobalMemoryLimit(null);
MemoryLimits.FlushThreadMemoryCounters();
}
}
}
[TestMethod]
public void Evaluation_fails_when_input_pushes_policy_over_global_limit()
{
lock (LimitLock)
{
using var guard = new MemoryLimitScope();
using var engine = new Engine();
const string policy = """
package memorylimit
import rego.v1
@@ -281,96 +303,152 @@ import rego.v1
stretched := concat("", [input.block | numbers.range(0, input.repeat - 1)[_]])
""";
engine.AddPolicy("memorylimit.rego", policy);
engine.AddPolicy("memorylimit.rego", policy);
MemoryLimits.FlushThreadMemoryCounters();
const ulong limit = 4 * 1024 * 1024;
MemoryLimits.SetGlobalMemoryLimit(limit);
MemoryLimits.FlushThreadMemoryCounters();
const ulong limit = 4 * 1024 * 1024;
MemoryLimits.SetGlobalMemoryLimit(limit);
var block = new string('x', 16 * 1024);
var block = new string('x', 16 * 1024);
var smallInput = JsonSerializer.Serialize(new { block, repeat = 16 });
engine.SetInputJson(smallInput);
var smallResult = engine.EvalRule("data.memorylimit.stretched");
Assert.IsNotNull(smallResult);
var stretched = JsonSerializer.Deserialize<string>(smallResult);
Assert.IsNotNull(stretched, "Policy should return a string result.");
Assert.AreEqual(block.Length * 16, stretched!.Length, "Policy should expand the payload under the limit.");
var smallInput = JsonSerializer.Serialize(new { block, repeat = 16 });
engine.SetInputJson(smallInput);
var smallResult = engine.EvalRule("data.memorylimit.stretched");
Assert.IsNotNull(smallResult);
var stretched = JsonSerializer.Deserialize<string>(smallResult);
Assert.IsNotNull(stretched, "Policy should return a string result.");
Assert.AreEqual(block.Length * 16, stretched!.Length, "Policy should expand the payload under the limit.");
var largeInput = JsonSerializer.Serialize(new { block, repeat = 4096 });
engine.SetInputJson(largeInput);
var largeInput = JsonSerializer.Serialize(new { block, repeat = 4096 });
engine.SetInputJson(largeInput);
var ex = Assert.ThrowsException<InvalidOperationException>(
() => engine.EvalRule("data.memorylimit.stretched"));
StringAssert.Contains(ex.Message, "execution exceeded memory limit");
var ex = Assert.ThrowsException<InvalidOperationException>(
() => engine.EvalRule("data.memorylimit.stretched"));
StringAssert.Contains(ex.Message, "execution exceeded memory limit");
}
}
}
[TestMethod]
public void Thread_flush_threshold_roundtrips()
{
[TestMethod]
public void Thread_flush_threshold_roundtrips()
{
lock (LimitLock)
{
var original = MemoryLimits.GetThreadMemoryFlushThreshold();
try
{
const ulong threshold = 256 * 1024;
MemoryLimits.SetThreadFlushThresholdOverride(threshold);
Assert.AreEqual(threshold, MemoryLimits.GetThreadMemoryFlushThreshold());
MemoryLimits.SetThreadFlushThresholdOverride(null);
var restored = MemoryLimits.GetThreadMemoryFlushThreshold();
Assert.IsTrue(restored.HasValue, "Clearing override should restore allocator default.");
if (original.HasValue)
var original = MemoryLimits.GetThreadMemoryFlushThreshold();
try
{
Assert.AreEqual(original, restored);
const ulong threshold = 256 * 1024;
MemoryLimits.SetThreadFlushThresholdOverride(threshold);
Assert.AreEqual(threshold, MemoryLimits.GetThreadMemoryFlushThreshold());
MemoryLimits.SetThreadFlushThresholdOverride(null);
var restored = MemoryLimits.GetThreadMemoryFlushThreshold();
Assert.IsTrue(restored.HasValue, "Clearing override should restore allocator default.");
if (original.HasValue)
{
Assert.AreEqual(original, restored);
}
}
finally
{
MemoryLimits.SetThreadFlushThresholdOverride(original);
}
}
finally
{
MemoryLimits.SetThreadFlushThresholdOverride(original);
}
}
}
[TestMethod]
public void SetInputJson_has_negligible_allocations_after_warmup()
{
using var engine = new Engine();
const string payload = "{}";
// Warm up the engine and JIT to ensure subsequent measurements are representative.
for (int i = 0; i < 16; i++)
{
engine.SetInputJson(payload);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
const int iterations = 256;
var before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < iterations; i++)
[TestMethod]
public void SetInputJson_has_negligible_allocations_after_warmup()
{
engine.SetInputJson(payload);
using var engine = new Engine();
const string payload = "{}";
// Warm up the engine and JIT to ensure subsequent measurements are representative.
for (int i = 0; i < 16; i++)
{
engine.SetInputJson(payload);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
const int iterations = 256;
var before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < iterations; i++)
{
engine.SetInputJson(payload);
}
var after = GC.GetAllocatedBytesForCurrentThread();
var allocated = Math.Max(0, after - before);
var bytesPerOp = allocated / (double)iterations;
// Runtime bookkeeping (delegate caches, GC write barriers) differs across platforms, so
// we measure bytes per call rather than absolute totals and allow a small budget.
// CI will flag regressions where marshalling starts allocating per invocation.
// Allow a small budget for delegates and runtime bookkeeping while still flagging regressions.
Assert.IsTrue(
bytesPerOp <= 512,
$"Expected ≤512 B/op after warmup, but observed {bytesPerOp:F2} B/op (total {allocated} bytes)."
);
}
var after = GC.GetAllocatedBytesForCurrentThread();
var allocated = Math.Max(0, after - before);
var bytesPerOp = allocated / (double)iterations;
[TestMethod]
public void Disposed_objects_throw_object_disposed_exception()
{
var engine = new Engine();
engine.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => engine.EvalRule("data.test.message"));
// Runtime bookkeeping (delegate caches, GC write barriers) differs across platforms, so
// we measure bytes per call rather than absolute totals and allow a small budget.
// CI will flag regressions where marshalling starts allocating per invocation.
var program = Program.CreateEmpty();
program.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => program.SerializeBinary());
// Allow a small budget for delegates and runtime bookkeeping while still flagging regressions.
Assert.IsTrue(
bytesPerOp <= 512,
$"Expected ≤512 B/op after warmup, but observed {bytesPerOp:F2} B/op (total {allocated} bytes)."
);
}
var rvm = new Rvm();
rvm.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => rvm.Execute());
var modules = new[] { new PolicyModule("test.rego", "package test\nallow = true") };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.test.allow");
compiled.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => compiled.EvalWithInput("{}"));
}
[TestMethod]
public void Registry_helpers_return_empty_after_clear()
{
TargetRegistry.Clear();
Assert.IsTrue(TargetRegistry.IsEmpty);
Assert.AreEqual(0, TargetRegistry.GetNames().Count);
SchemaRegistry.ClearResources();
SchemaRegistry.ClearEffects();
Assert.IsTrue(SchemaRegistry.IsResourceRegistryEmpty);
Assert.IsTrue(SchemaRegistry.IsEffectRegistryEmpty);
Assert.AreEqual(0, SchemaRegistry.GetResourceNames().Count);
Assert.AreEqual(0, SchemaRegistry.GetEffectNames().Count);
}
[TestMethod]
public void Utf8_marshalling_handles_large_unicode_payloads()
{
var payload = string.Concat(new string('ß', 2048), "-✓-", new string('漢', 1024));
using var engine = new Engine();
engine.AddPolicy("test.rego", "package test\nmessage = input.msg");
engine.SetInputJson(JsonSerializer.Serialize(new { msg = payload }));
var result = engine.EvalRule("data.test.message");
Assert.IsNotNull(result);
// Compare by parsing the JSON string to avoid encoder differences across platforms.
var parsed = JsonSerializer.Deserialize<string>(result);
Assert.IsNotNull(parsed);
Assert.AreEqual(payload, parsed);
}
private sealed class MemoryLimitScope : IDisposable
{

View File

@@ -96,9 +96,9 @@ allow if {
Assert.AreEqual("true", result, "expected allow=true");
}
[TestMethod]
public void Program_host_await_suspend_and_resume_succeeds()
{
[TestMethod]
public void Program_host_await_suspend_and_resume_succeeds()
{
var modules = new[] { new PolicyModule("host_await.rego", HostAwaitPolicy) };
var entryPoints = new[] { "data.demo.allow" };
@@ -115,5 +115,5 @@ allow if {
var resumed = vm.Resume("{\"tier\":\"gold\"}");
Assert.AreEqual("true", resumed, "expected allow=true after resume");
}
}
}

View File

@@ -0,0 +1,153 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Manages Azure Policy alias definitions used for resource normalization
/// and policy compilation.
/// </summary>
public unsafe sealed class AliasRegistry : SafeHandleWrapper
{
/// <summary>
/// Create an empty alias registry.
/// </summary>
public AliasRegistry()
: base(RegorusAliasRegistryHandle.Create(), nameof(AliasRegistry))
{
}
/// <summary>
/// Load control-plane alias data (array of ProviderAliases) from a JSON string.
/// </summary>
/// <param name="json">JSON array of ProviderAliases (e.g. from Get-AzPolicyAlias or ResourceTypesAndAliases.json)</param>
public void LoadJson(string json)
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(regPtr =>
{
CheckAndDropResult(API.regorus_alias_registry_load_json(
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
return 0;
});
});
}
/// <summary>
/// Load a data-plane policy manifest from a JSON string.
/// </summary>
/// <param name="json">JSON object containing a DataPolicyManifest</param>
public void LoadManifest(string json)
{
Utf8Marshaller.WithUtf8(json, jsonPtr =>
{
UseHandle(regPtr =>
{
CheckAndDropResult(API.regorus_alias_registry_load_manifest(
(RegorusAliasRegistry*)regPtr, (byte*)jsonPtr));
return 0;
});
});
}
/// <summary>
/// Gets the number of resource types loaded in the registry.
/// </summary>
public long Length
{
get
{
return UseHandle(regPtr =>
{
return ResultHelpers.GetIntResult(
API.regorus_alias_registry_len((RegorusAliasRegistry*)regPtr));
});
}
}
/// <summary>
/// Normalize an ARM resource JSON and wrap it into the standard input envelope
/// expected by a compiled Azure Policy program.
/// </summary>
/// <param name="resourceJson">Raw ARM resource JSON</param>
/// <param name="apiVersion">API version string (e.g. "2023-01-01"), or null to use default alias paths</param>
/// <param name="contextJson">Additional context JSON object (pass "{}" if none)</param>
/// <param name="parametersJson">Policy parameter values JSON (pass "{}" if none)</param>
/// <returns>JSON string: { "resource": &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 =>
Utf8Marshaller.WithUtf8(contextJson, ctxPtr =>
Utf8Marshaller.WithUtf8(parametersJson, paramsPtr =>
{
if (apiVersion is null)
{
return UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_normalize_and_wrap(
(RegorusAliasRegistry*)regPtr,
(byte*)resPtr, null,
(byte*)ctxPtr, (byte*)paramsPtr));
});
}
else
{
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_normalize_and_wrap(
(RegorusAliasRegistry*)regPtr,
(byte*)resPtr, (byte*)apiPtr,
(byte*)ctxPtr, (byte*)paramsPtr));
}));
}
})));
}
/// <summary>
/// Denormalize a previously-normalized resource JSON back to ARM format.
/// </summary>
/// <param name="normalizedJson">The normalized resource JSON</param>
/// <param name="apiVersion">API version string, or null to use default alias paths</param>
/// <returns>Denormalized ARM JSON string</returns>
public string? Denormalize(string normalizedJson, string? apiVersion = null)
{
return Utf8Marshaller.WithUtf8(normalizedJson, normPtr =>
{
if (apiVersion is null)
{
return UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_denormalize(
(RegorusAliasRegistry*)regPtr,
(byte*)normPtr, null));
});
}
else
{
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_denormalize(
(RegorusAliasRegistry*)regPtr,
(byte*)normPtr, (byte*)apiPtr));
}));
}
});
}
private static string? CheckAndDropResult(RegorusResult result)
{
return ResultHelpers.GetStringResult(result);
}
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
namespace Regorus
{
/// <summary>
/// Global configuration for compiled pattern caches used by regex and glob builtins.
/// </summary>
public readonly struct CacheConfig
{
/// <summary>
/// Initializes a new instance of the <see cref="CacheConfig"/> struct.
/// </summary>
/// <param name="regex">Maximum cached compiled regex patterns (default 256, 0 = disabled).</param>
/// <param name="glob">Maximum cached compiled glob matchers (default 128, 0 = disabled).</param>
public CacheConfig(nuint regex, nuint glob)
{
Regex = regex;
Glob = glob;
}
/// <summary>Maximum cached compiled regex patterns (default 256).</summary>
public nuint Regex { get; }
/// <summary>Maximum cached compiled glob matchers (default 128).</summary>
public nuint Glob { get; }
internal Regorus.Internal.RegorusCacheConfig ToNative()
{
return new Regorus.Internal.RegorusCacheConfig
{
regex = Regex,
glob = Glob,
};
}
}
}

View File

@@ -3,7 +3,6 @@
using System;
using System.Text.Json;
using System.Threading;
using Regorus.Internal;
#nullable enable
@@ -18,20 +17,15 @@ namespace Regorus
/// Each instance represents a unique native policy object.
///
/// Thread Safety: This class is thread-safe for all operations. Multiple threads
/// can safely call EvalWithInput() concurrently, and Dispose() will safely wait
/// for all active evaluations to complete before freeing resources. No external
/// synchronization is required.
/// can safely call EvalWithInput() concurrently. Dispose() blocks new calls, waits
/// briefly, and defers the native release to the last in-flight caller if needed.
/// No external synchronization is required.
/// </summary>
public unsafe sealed class CompiledPolicy : IDisposable
public unsafe sealed class CompiledPolicy : SafeHandleWrapper
{
private RegorusCompiledPolicyHandle? _handle;
private readonly ManualResetEventSlim _idleEvent = new(initialState: true);
private int _isDisposed;
private int _activeEvaluations;
internal CompiledPolicy(RegorusCompiledPolicyHandle handle)
: base(handle, nameof(CompiledPolicy))
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
/// <summary>
@@ -45,36 +39,16 @@ namespace Regorus
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
public string? EvalWithInput(string inputJson)
{
// Increment active evaluations count
var active = System.Threading.Interlocked.Increment(ref _activeEvaluations);
if (active == 1)
return Internal.Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
{
_idleEvent.Reset();
}
try
{
ThrowIfDisposed();
return Internal.Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
return UseHandle(policyPtr =>
{
return UseHandle(policyPtr =>
unsafe
{
unsafe
{
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input((Internal.RegorusCompiledPolicy*)policyPtr, (byte*)inputPtr));
}
});
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input((Internal.RegorusCompiledPolicy*)policyPtr, (byte*)inputPtr));
}
});
}
finally
{
// Decrement active evaluations count
var remaining = System.Threading.Interlocked.Decrement(ref _activeEvaluations);
if (remaining == 0)
{
_idleEvent.Set();
}
}
});
}
/// <summary>
@@ -86,7 +60,6 @@ namespace Regorus
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
public PolicyInfo GetPolicyInfo()
{
ThrowIfDisposed();
var jsonResult = UseHandle(policyPtr =>
{
unsafe
@@ -94,7 +67,7 @@ namespace Regorus
return CheckAndDropResult(Internal.API.regorus_compiled_policy_get_policy_info((Internal.RegorusCompiledPolicy*)policyPtr));
}
});
if (string.IsNullOrEmpty(jsonResult))
{
throw new Exception("Failed to get policy info: empty response");
@@ -106,8 +79,8 @@ namespace Regorus
{
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<PolicyInfo>(jsonResult!, options)
return JsonSerializer.Deserialize<PolicyInfo>(jsonResult!, options)
?? throw new Exception("Failed to deserialize policy info");
}
catch (JsonException ex)
@@ -116,106 +89,9 @@ namespace Regorus
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
var handle = _handle;
if (handle != null)
{
_idleEvent.Wait();
handle.Dispose();
_handle = null;
}
_idleEvent.Dispose();
}
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
throw new ObjectDisposedException(nameof(CompiledPolicy));
}
private string? CheckAndDropResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = Internal.Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type switch
{
Internal.RegorusDataType.String => Internal.Utf8Marshaller.FromUtf8(result.output),
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Internal.RegorusDataType.Integer => result.int_value.ToString(),
Internal.RegorusDataType.None => null,
_ => Internal.Utf8Marshaller.FromUtf8(result.output)
};
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private RegorusCompiledPolicyHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(CompiledPolicy));
}
return handle;
}
internal T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
try
{
handle.DangerousAddRef(ref addedRef);
var pointer = handle.DangerousGetHandle();
if (pointer == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(CompiledPolicy));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
{
return UseHandle(func);
}
private void UseHandle(Action<IntPtr> action)
{
UseHandle<object?>(handlePtr =>
{
action(handlePtr);
return null;
});
return Internal.ResultHelpers.GetStringResult(result);
}
}
}

View File

@@ -12,17 +12,17 @@ namespace Regorus
/// <summary>
/// Represents a policy module with an ID and content.
/// </summary>
public struct PolicyModule
public readonly struct PolicyModule
{
/// <summary>
/// Gets or sets the unique identifier for this policy module.
/// Gets the unique identifier for this policy module.
/// </summary>
public string Id { get; set; }
public string Id { get; }
/// <summary>
/// Gets or sets the Rego policy content.
/// Gets the Rego policy content.
/// </summary>
public string Content { get; set; }
public string Content { get; }
/// <summary>
/// Initializes a new instance of the PolicyModule struct.
@@ -53,50 +53,40 @@ namespace Regorus
/// <exception cref="Exception">Thrown when compilation fails</exception>
public static CompiledPolicy CompilePolicyWithEntrypoint(string dataJson, IEnumerable<PolicyModule> modules, string entryPointRule)
{
var modulesArray = modules.ToArray();
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
try
if (modules is null)
{
for (int i = 0; i < modulesArray.Length; i++)
throw new ArgumentNullException(nameof(modules));
}
return CompilePolicyWithEntrypoint(dataJson, modules.ToArray(), entryPointRule);
}
/// <summary>
/// Compiles a policy from data and modules with a specific entry point rule.
/// </summary>
public static CompiledPolicy CompilePolicyWithEntrypoint(string dataJson, IReadOnlyList<PolicyModule> modules, string entryPointRule)
{
if (modules is null)
{
throw new ArgumentNullException(nameof(modules));
}
using var pinnedModules = Internal.ModuleMarshalling.PinPolicyModules(modules);
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
Utf8Marshaller.WithUtf8(entryPointRule, entryPointPtr =>
{
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
pinnedStrings.Add(idPinned);
pinnedStrings.Add(contentPinned);
nativeModules[i] = new Internal.RegorusPolicyModule
unsafe
{
id = idPinned.Pointer,
content = contentPinned.Pointer
};
}
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
Utf8Marshaller.WithUtf8(entryPointRule, entryPointPtr =>
{
unsafe
fixed (Internal.RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
{
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
{
var result = Internal.API.regorus_compile_policy_with_entrypoint(
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length, (byte*)entryPointPtr);
var result = Internal.API.regorus_compile_policy_with_entrypoint(
(byte*)dataPtr, modulesPtr, (UIntPtr)pinnedModules.Length, (byte*)entryPointPtr);
var policy = GetCompiledPolicyResult(result);
return policy;
}
return GetCompiledPolicyResult(result);
}
}));
}
finally
{
foreach (var pinned in pinnedStrings)
{
pinned.Dispose();
}
}
}
}));
}
/// <summary>
@@ -110,49 +100,39 @@ namespace Regorus
/// <exception cref="Exception">Thrown when compilation fails</exception>
public static CompiledPolicy CompilePolicyForTarget(string dataJson, IEnumerable<PolicyModule> modules)
{
var modulesArray = modules.ToArray();
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
try
if (modules is null)
{
for (int i = 0; i < modulesArray.Length; i++)
throw new ArgumentNullException(nameof(modules));
}
return CompilePolicyForTarget(dataJson, modules.ToArray());
}
/// <summary>
/// Compiles a target-aware policy from data and modules.
/// </summary>
public static CompiledPolicy CompilePolicyForTarget(string dataJson, IReadOnlyList<PolicyModule> modules)
{
if (modules is null)
{
throw new ArgumentNullException(nameof(modules));
}
using var pinnedModules = Internal.ModuleMarshalling.PinPolicyModules(modules);
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
unsafe
{
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
pinnedStrings.Add(idPinned);
pinnedStrings.Add(contentPinned);
nativeModules[i] = new Internal.RegorusPolicyModule
fixed (Internal.RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
{
id = idPinned.Pointer,
content = contentPinned.Pointer
};
}
var result = Internal.API.regorus_compile_policy_for_target(
(byte*)dataPtr, modulesPtr, (UIntPtr)pinnedModules.Length);
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
unsafe
{
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
{
var result = Internal.API.regorus_compile_policy_for_target(
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length);
var policy = GetCompiledPolicyResult(result);
return policy;
}
return GetCompiledPolicyResult(result);
}
});
}
finally
{
foreach (var pinned in pinnedStrings)
{
pinned.Dispose();
}
}
});
}
private static CompiledPolicy GetCompiledPolicyResult(Internal.RegorusResult result)

View File

@@ -16,14 +16,11 @@ namespace Regorus
/// Cloning is cheap and involves only incrementing reference counts for shared immutable objects like parsed policies,
/// data etc. Mutable state is deep copied as needed.
/// </summary>
public unsafe sealed class Engine : IDisposable
public unsafe sealed class Engine : SafeHandleWrapper
{
private RegorusEngineHandle? _handle;
private int _isDisposed;
public Engine()
: base(RegorusEngineHandle.Create(), nameof(Engine))
{
_handle = RegorusEngineHandle.Create();
}
public static void SetFallbackExecutionTimerConfig(ExecutionTimerConfig config)
@@ -37,42 +34,24 @@ namespace Regorus
CheckAndDropResult(Regorus.Internal.API.regorus_clear_fallback_execution_timer_config());
}
public void Dispose()
public static void SetCacheConfig(CacheConfig config)
{
Dispose(disposing: true);
// This object will be cleaned up by the Dispose method.
// Therefore, call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
var nativeConfig = config.ToNative();
CheckAndDropResult(Regorus.Internal.API.regorus_set_cache_config(nativeConfig));
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
void Dispose(bool disposing)
public static void ClearCache()
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
_handle?.Dispose();
_handle = null;
}
CheckAndDropResult(Regorus.Internal.API.regorus_clear_cache());
}
private Engine(RegorusEngineHandle handle)
: base(handle, nameof(Engine))
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
public Engine Clone()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
@@ -91,402 +70,216 @@ namespace Regorus
public void SetStrictBuiltinErrors(bool strict)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors((Regorus.Internal.RegorusEngine*)enginePtr, strict));
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors((Regorus.Internal.RegorusEngine*)enginePtr, strict));
});
}
public void SetExecutionTimerConfig(ExecutionTimerConfig config)
{
ThrowIfDisposed();
var nativeConfig = config.ToNative();
UseHandle(enginePtr =>
{
unsafe
{
var localConfig = nativeConfig;
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr, &localConfig));
}
var localConfig = nativeConfig;
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr, &localConfig));
});
}
public void ClearExecutionTimerConfig()
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr));
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
public void SetPolicyLengthConfig(PolicyLengthConfig config)
{
var nativeConfig = config.ToNative();
UseHandle(enginePtr =>
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_policy_length_config((Regorus.Internal.RegorusEngine*)enginePtr, nativeConfig));
});
}
public void ClearPolicyLengthConfig()
{
UseHandle(enginePtr =>
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_policy_length_config((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
public string? AddPolicy(string path, string rego)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(path, pathPtr =>
Utf8Marshaller.WithUtf8(rego, regoPtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr, (byte*)regoPtr));
}
});
}
}));
UseHandle(enginePtr =>
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr, (byte*)regoPtr))
)));
}
public void SetRegoV0(bool enable)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0((Regorus.Internal.RegorusEngine*)enginePtr, enable));
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0((Regorus.Internal.RegorusEngine*)enginePtr, enable));
});
}
public string? AddPolicyFromFile(string path)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(path, pathPtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
}
});
}
return UseHandle(enginePtr =>
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr))
);
});
}
public void AddDataJson(string data)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(data, dataPtr =>
{
unsafe
UseHandle(enginePtr =>
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)dataPtr));
}
});
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)dataPtr));
});
});
}
public void AddDataFromJsonFile(string path)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(path, pathPtr =>
{
unsafe
UseHandle(enginePtr =>
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
}
});
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
});
});
}
public void SetInputJson(string input)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(input, inputPtr =>
{
unsafe
UseHandle(enginePtr =>
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)inputPtr));
}
});
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)inputPtr));
});
});
}
public void SetInputFromJsonFile(string path)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(path, pathPtr =>
{
unsafe
UseHandle(enginePtr =>
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
}
});
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
});
});
}
public string? EvalQuery(string query)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(query, queryPtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)queryPtr));
}
});
}
return UseHandle(enginePtr =>
CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)queryPtr))
);
});
}
public string? EvalRule(string rule)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(rule, rulePtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)rulePtr));
}
});
}
return UseHandle(enginePtr =>
CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)rulePtr))
);
});
}
public void SetEnableCoverage(bool enable)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage((Regorus.Internal.RegorusEngine*)enginePtr, enable));
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage((Regorus.Internal.RegorusEngine*)enginePtr, enable));
});
}
public void ClearCoverageData()
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data((Regorus.Internal.RegorusEngine*)enginePtr));
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
public string? GetCoverageReport()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report((Regorus.Internal.RegorusEngine*)enginePtr));
}
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
public string? GetCoverageReportPretty()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty((Regorus.Internal.RegorusEngine*)enginePtr));
}
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
public void SetGatherPrints(bool enable)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints((Regorus.Internal.RegorusEngine*)enginePtr, enable));
}
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints((Regorus.Internal.RegorusEngine*)enginePtr, enable));
});
}
public string? TakePrints()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints((Regorus.Internal.RegorusEngine*)enginePtr));
}
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
public string? GetAstAsJson()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json((Regorus.Internal.RegorusEngine*)enginePtr));
}
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
public string? GetPolicyPackageNames()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names((Regorus.Internal.RegorusEngine*)enginePtr));
}
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
public string? GetPolicyParameters()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters((Regorus.Internal.RegorusEngine*)enginePtr));
}
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters((Regorus.Internal.RegorusEngine*)enginePtr));
});
}
private static string? StringFromUtf8(IntPtr ptr)
private static string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
{
#if NETSTANDARD2_1
return Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
private static string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
{
try
{
if (result.status != Regorus.Internal.RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type switch
{
Regorus.Internal.RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
Regorus.Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Regorus.Internal.RegorusDataType.Integer => result.int_value.ToString(),
Regorus.Internal.RegorusDataType.None => null,
_ => Utf8Marshaller.FromUtf8(result.output)
};
}
finally
{
Regorus.Internal.API.regorus_result_drop(result);
}
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
{
throw new ObjectDisposedException(nameof(Engine));
}
}
internal RegorusEngineHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(Engine));
}
return handle;
}
internal void UseHandle(Action<IntPtr> action)
{
UseHandle<object?>(handlePtr =>
{
action(handlePtr);
return null;
});
}
internal T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
try
{
handle.DangerousAddRef(ref addedRef);
var pointer = handle.DangerousGetHandle();
if (pointer == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(Engine));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
{
return UseHandle(func);
return ResultHelpers.GetStringResult(result);
}
}

View File

@@ -89,12 +89,14 @@ namespace Regorus
);
}
if (result.int_value < 0)
try
{
throw new OverflowException($"{errorContext}: native value was negative ({result.int_value})");
return checked((ulong)result.int_value);
}
catch (OverflowException ex)
{
throw new OverflowException($"{errorContext}: native value was out of range ({result.int_value})", ex);
}
return (ulong)result.int_value;
}
finally
{

View File

@@ -0,0 +1,156 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Buffers;
using System.Collections.Generic;
using Regorus;
#nullable enable
namespace Regorus.Internal
{
internal static unsafe class ModuleMarshalling
{
internal sealed class PinnedPolicyModules : IDisposable
{
private readonly List<Utf8Marshaller.PinnedUtf8> _pins;
private bool _disposed;
internal PinnedPolicyModules(RegorusPolicyModule[] buffer, int length, List<Utf8Marshaller.PinnedUtf8> pins)
{
Buffer = buffer;
Length = length;
_pins = pins;
}
internal RegorusPolicyModule[] Buffer { get; }
internal int Length { get; }
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var pin in _pins)
{
pin.Dispose();
}
ArrayPool<RegorusPolicyModule>.Shared.Return(Buffer, clearArray: true);
_disposed = true;
}
}
internal sealed class PinnedEntryPoints : IDisposable
{
private readonly List<Utf8Marshaller.PinnedUtf8> _pins;
private bool _disposed;
internal PinnedEntryPoints(IntPtr[] buffer, int length, List<Utf8Marshaller.PinnedUtf8> pins)
{
Buffer = buffer;
Length = length;
_pins = pins;
}
internal IntPtr[] Buffer { get; }
internal int Length { get; }
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var pin in _pins)
{
pin.Dispose();
}
ArrayPool<IntPtr>.Shared.Return(Buffer, clearArray: true);
_disposed = true;
}
}
internal static PinnedPolicyModules PinPolicyModules(IReadOnlyList<PolicyModule> modules)
{
if (modules is null)
{
throw new ArgumentNullException(nameof(modules));
}
var count = modules.Count;
var buffer = ArrayPool<RegorusPolicyModule>.Shared.Rent(count);
var pins = new List<Utf8Marshaller.PinnedUtf8>(count * 2);
try
{
for (int i = 0; i < count; i++)
{
var idPinned = Utf8Marshaller.Pin(modules[i].Id);
var contentPinned = Utf8Marshaller.Pin(modules[i].Content);
pins.Add(idPinned);
pins.Add(contentPinned);
buffer[i] = new RegorusPolicyModule
{
id = idPinned.Pointer,
content = contentPinned.Pointer
};
}
return new PinnedPolicyModules(buffer, count, pins);
}
catch
{
foreach (var pin in pins)
{
pin.Dispose();
}
ArrayPool<RegorusPolicyModule>.Shared.Return(buffer, clearArray: true);
throw;
}
}
internal static PinnedEntryPoints PinEntryPoints(IReadOnlyList<string> entryPoints)
{
if (entryPoints is null)
{
throw new ArgumentNullException(nameof(entryPoints));
}
var count = entryPoints.Count;
var buffer = ArrayPool<IntPtr>.Shared.Rent(count);
var pins = new List<Utf8Marshaller.PinnedUtf8>(count);
try
{
for (int i = 0; i < count; i++)
{
var entryPinned = Utf8Marshaller.Pin(entryPoints[i]);
pins.Add(entryPinned);
buffer[i] = (IntPtr)entryPinned.Pointer;
}
return new PinnedEntryPoints(buffer, count, pins);
}
catch
{
foreach (var pin in pins)
{
pin.Dispose();
}
ArrayPool<IntPtr>.Shared.Return(buffer, clearArray: true);
throw;
}
}
}
}

View File

@@ -428,6 +428,18 @@ namespace Regorus.Internal
[DllImport(LibraryName, EntryPoint = "regorus_engine_clear_execution_timer_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_clear_execution_timer_config(RegorusEngine* engine);
/// <summary>
/// Set the policy length limits for a specific engine instance.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_policy_length_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_policy_length_config(RegorusEngine* engine, RegorusPolicyLengthConfig config);
/// <summary>
/// Clear the policy length configuration for a specific engine instance.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_clear_policy_length_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_clear_policy_length_config(RegorusEngine* engine);
#endregion
#region Execution Timer Global Methods
@@ -446,6 +458,22 @@ namespace Regorus.Internal
#endregion
#region Cache Configuration Global Methods
/// <summary>
/// Configure the global pattern caches used by regex and glob builtins.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_set_cache_config", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_set_cache_config(RegorusCacheConfig config);
/// <summary>
/// Clear all entries from every pattern cache.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_clear_cache", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_clear_cache();
#endregion
#region Compilation Methods
/// <summary>
@@ -492,6 +520,16 @@ namespace Regorus.Internal
#endregion
#region RBAC Methods
/// <summary>
/// Evaluate an Azure RBAC condition expression against a JSON evaluation context.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_rbac_engine_eval_condition", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_rbac_engine_eval_condition(byte* condition, byte* context_json);
#endregion
#region Target Registry Methods
/// <summary>
@@ -631,6 +669,55 @@ namespace Regorus.Internal
internal static extern RegorusResult regorus_effect_schema_clear();
#endregion
#region Alias Registry Methods
/// <summary>
/// Create a new, empty AliasRegistry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusAliasRegistry* regorus_alias_registry_new();
/// <summary>
/// Drop an AliasRegistry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_alias_registry_drop(RegorusAliasRegistry* registry);
/// <summary>
/// Load control-plane alias data (array of ProviderAliases) into the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_load_json(RegorusAliasRegistry* registry, byte* json);
/// <summary>
/// Load a data-plane policy manifest into the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_load_manifest", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_load_manifest(RegorusAliasRegistry* registry, byte* json);
/// <summary>
/// Return the number of resource types loaded in the alias registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_len(RegorusAliasRegistry* registry);
/// <summary>
/// Normalize an ARM resource JSON and wrap it into the standard input envelope.
/// Returns a JSON string.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_normalize_and_wrap", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_normalize_and_wrap(
RegorusAliasRegistry* registry, byte* resource_json, byte* api_version, byte* context_json, byte* parameters_json);
/// <summary>
/// Denormalize a previously-normalized resource JSON back to ARM format.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_alias_registry_denormalize", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_alias_registry_denormalize(
RegorusAliasRegistry* registry, byte* normalized_json, byte* api_version);
#endregion
}
#region Native Structures
@@ -762,6 +849,27 @@ namespace Regorus.Internal
public uint check_interval;
}
/// <summary>
/// FFI representation of the policy length configuration.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct RegorusPolicyLengthConfig
{
public uint max_col;
public UIntPtr max_file_bytes;
public UIntPtr max_lines;
}
/// <summary>
/// FFI representation of the cache configuration.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct RegorusCacheConfig
{
public UIntPtr regex;
public UIntPtr glob;
}
/// <summary>
/// Byte buffer returned from FFI.
/// </summary>
@@ -815,5 +923,13 @@ namespace Regorus.Internal
public byte* content;
}
/// <summary>
/// Wrapper for AliasRegistry.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusAliasRegistry
{
}
#endregion
}

View File

@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
namespace Regorus
{
/// <summary>
/// Policy source length limits enforced when loading policy files.
/// </summary>
public readonly struct PolicyLengthConfig
{
/// <summary>
/// Initializes a new instance of the <see cref="PolicyLengthConfig"/> struct.
/// </summary>
/// <param name="maxCol">Maximum column width per line. Must be non-zero.</param>
/// <param name="maxFileBytes">Maximum policy file size in bytes. Must be non-zero.</param>
/// <param name="maxLines">Maximum number of lines per policy file. Must be non-zero.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when any parameter is zero.</exception>
public PolicyLengthConfig(uint maxCol, nuint maxFileBytes, nuint maxLines)
{
if (maxCol == 0)
throw new ArgumentOutOfRangeException(nameof(maxCol), "Must be non-zero.");
if (maxFileBytes == 0)
throw new ArgumentOutOfRangeException(nameof(maxFileBytes), "Must be non-zero.");
if (maxLines == 0)
throw new ArgumentOutOfRangeException(nameof(maxLines), "Must be non-zero.");
MaxCol = maxCol;
MaxFileBytes = maxFileBytes;
MaxLines = maxLines;
}
/// <summary>Maximum column width per line (default: 1024).</summary>
public uint MaxCol { get; }
/// <summary>Maximum policy file size in bytes (default: 1 MiB).</summary>
public nuint MaxFileBytes { get; }
/// <summary>Maximum number of lines per policy file (default: 20000).</summary>
public nuint MaxLines { get; }
internal Regorus.Internal.RegorusPolicyLengthConfig ToNative()
{
return new Regorus.Internal.RegorusPolicyLengthConfig
{
max_col = MaxCol,
max_file_bytes = MaxFileBytes,
max_lines = MaxLines,
};
}
}
}

View File

@@ -13,14 +13,11 @@ namespace Regorus
/// <summary>
/// Represents a compiled RVM program.
/// </summary>
public unsafe sealed class Program : IDisposable
public unsafe sealed class Program : SafeHandleWrapper
{
private RegorusProgramHandle? _handle;
private int _isDisposed;
private Program(RegorusProgramHandle handle)
: base(handle, nameof(Program))
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
/// <summary>
@@ -36,63 +33,57 @@ namespace Regorus
/// </summary>
public static Program CompileFromModules(string dataJson, IEnumerable<PolicyModule> modules, IEnumerable<string> entryPoints)
{
var modulesArray = modules.ToArray();
var entryPointsArray = entryPoints.ToArray();
if (entryPointsArray.Length == 0)
if (modules is null)
{
throw new ArgumentNullException(nameof(modules));
}
if (entryPoints is null)
{
throw new ArgumentNullException(nameof(entryPoints));
}
return CompileFromModules(dataJson, modules.ToArray(), entryPoints.ToArray());
}
/// <summary>
/// Compile an RVM program from modules and entry points.
/// </summary>
public static Program CompileFromModules(string dataJson, IReadOnlyList<PolicyModule> modules, IReadOnlyList<string> entryPoints)
{
if (modules is null)
{
throw new ArgumentNullException(nameof(modules));
}
if (entryPoints is null)
{
throw new ArgumentNullException(nameof(entryPoints));
}
if (entryPoints.Count == 0)
{
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
}
var nativeModules = new RegorusPolicyModule[modulesArray.Length];
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2 + entryPointsArray.Length);
var entryPointers = new IntPtr[entryPointsArray.Length];
using var pinnedModules = ModuleMarshalling.PinPolicyModules(modules);
using var pinnedEntryPoints = ModuleMarshalling.PinEntryPoints(entryPoints);
try
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
for (int i = 0; i < modulesArray.Length; i++)
fixed (RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
fixed (IntPtr* entryPtr = pinnedEntryPoints.Buffer)
{
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
pinnedStrings.Add(idPinned);
pinnedStrings.Add(contentPinned);
var result = API.regorus_program_compile_from_modules(
(byte*)dataPtr,
modulesPtr,
(UIntPtr)pinnedModules.Length,
(byte**)entryPtr,
(UIntPtr)pinnedEntryPoints.Length);
nativeModules[i] = new RegorusPolicyModule
{
id = idPinned.Pointer,
content = contentPinned.Pointer
};
return GetProgramResult(result);
}
for (int i = 0; i < entryPointsArray.Length; i++)
{
var entryPinned = Utf8Marshaller.Pin(entryPointsArray[i]);
pinnedStrings.Add(entryPinned);
entryPointers[i] = (IntPtr)entryPinned.Pointer;
}
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
fixed (RegorusPolicyModule* modulesPtr = nativeModules)
fixed (IntPtr* entryPtr = entryPointers)
{
var result = API.regorus_program_compile_from_modules(
(byte*)dataPtr,
modulesPtr,
(UIntPtr)modulesArray.Length,
(byte**)entryPtr,
(UIntPtr)entryPointsArray.Length);
return GetProgramResult(result);
}
});
}
finally
{
foreach (var pinned in pinnedStrings)
{
pinned.Dispose();
}
}
});
}
/// <summary>
@@ -104,44 +95,48 @@ namespace Regorus
{
throw new ArgumentNullException(nameof(engine));
}
if (entryPoints is null)
{
throw new ArgumentNullException(nameof(entryPoints));
}
var entryPointsArray = entryPoints.ToArray();
if (entryPointsArray.Length == 0)
return CompileFromEngine(engine, entryPoints.ToArray());
}
/// <summary>
/// Compile an RVM program from an engine instance and entry points.
/// </summary>
public static Program CompileFromEngine(Engine engine, IReadOnlyList<string> entryPoints)
{
if (engine is null)
{
throw new ArgumentNullException(nameof(engine));
}
if (entryPoints is null)
{
throw new ArgumentNullException(nameof(entryPoints));
}
if (entryPoints.Count == 0)
{
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
}
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(entryPointsArray.Length);
var entryPointers = new IntPtr[entryPointsArray.Length];
try
{
for (int i = 0; i < entryPointsArray.Length; i++)
{
var entryPinned = Utf8Marshaller.Pin(entryPointsArray[i]);
pinnedStrings.Add(entryPinned);
entryPointers[i] = (IntPtr)entryPinned.Pointer;
}
using var pinnedEntryPoints = ModuleMarshalling.PinEntryPoints(entryPoints);
return engine.UseHandleForInterop(enginePtr =>
{
fixed (IntPtr* entryPtr = entryPointers)
{
var result = API.regorus_engine_compile_program_with_entrypoints(
(RegorusEngine*)enginePtr,
(byte**)entryPtr,
(UIntPtr)entryPointsArray.Length);
return GetProgramResult(result);
}
});
}
finally
return engine.UseHandleForInterop(enginePtr =>
{
foreach (var pinned in pinnedStrings)
fixed (IntPtr* entryPtr = pinnedEntryPoints.Buffer)
{
pinned.Dispose();
var result = API.regorus_engine_compile_program_with_entrypoints(
(RegorusEngine*)enginePtr,
(byte**)entryPtr,
(UIntPtr)pinnedEntryPoints.Length);
return GetProgramResult(result);
}
}
});
}
/// <summary>
@@ -169,7 +164,6 @@ namespace Regorus
/// </summary>
public byte[] SerializeBinary()
{
ThrowIfDisposed();
return UseHandle(programPtr =>
{
var result = API.regorus_program_serialize_binary((RegorusProgram*)programPtr);
@@ -182,70 +176,12 @@ namespace Regorus
/// </summary>
public string? GenerateListing()
{
ThrowIfDisposed();
return UseHandle(programPtr =>
{
return CheckAndDropResult(API.regorus_program_generate_listing((RegorusProgram*)programPtr));
});
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
_handle?.Dispose();
_handle = null;
}
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
{
throw new ObjectDisposedException(nameof(Program));
}
}
internal RegorusProgramHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(Program));
}
return handle;
}
internal T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
try
{
handle.DangerousAddRef(ref addedRef);
var pointer = handle.DangerousGetHandle();
if (pointer == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(Program));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
private static Program GetProgramResult(RegorusResult result)
{
try
@@ -272,27 +208,7 @@ namespace Regorus
private static string? CheckAndDropResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type switch
{
RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
RegorusDataType.Integer => result.int_value.ToString(),
RegorusDataType.None => null,
_ => Utf8Marshaller.FromUtf8(result.output)
};
}
finally
{
API.regorus_result_drop(result);
}
return ResultHelpers.GetStringResult(result);
}
private static byte[] ExtractBuffer(RegorusResult result)

View File

@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Provides helpers for evaluating Azure RBAC condition expressions.
/// </summary>
public static unsafe class RbacEngine
{
/// <summary>
/// Evaluate an Azure RBAC condition expression against a JSON evaluation context.
/// </summary>
/// <param name="condition">Azure RBAC condition expression.</param>
/// <param name="contextJson">JSON encoded EvaluationContext.</param>
/// <returns>True if the condition evaluates to true; otherwise false.</returns>
/// <exception cref="Exception">Thrown when evaluation fails.</exception>
public static bool EvaluateCondition(string condition, string contextJson)
{
if (condition is null)
{
throw new ArgumentNullException(nameof(condition));
}
if (contextJson is null)
{
throw new ArgumentNullException(nameof(contextJson));
}
return Utf8Marshaller.WithUtf8(condition, conditionPtr =>
Utf8Marshaller.WithUtf8(contextJson, contextPtr =>
{
unsafe
{
var result = Internal.API.regorus_rbac_engine_eval_condition((byte*)conditionPtr, (byte*)contextPtr);
return ResultHelpers.GetBoolResult(result);
}
}));
}
}
}

View File

@@ -8,9 +8,18 @@
<LangVersion>10.0</LangVersion>
<!-- See https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-pack -->
<VersionPrefix>0.9.0</VersionPrefix>
<VersionPrefix>0.9.1</VersionPrefix>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT AND Apache-2.0 AND BSD-3-Clause</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/microsoft/regorus</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/regorus</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Authors>Microsoft</Authors>
<Company>Microsoft</Company>
<PackageTags>rego;policy;engine;authorization;opa;rust</PackageTags>
<Description>Fast, lightweight Rego interpreter and policy engine for .NET, powered by Rust.</Description>
<Copyright>Copyright (c) Microsoft Corporation.</Copyright>
</PropertyGroup>
<PropertyGroup>
@@ -46,10 +55,14 @@
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/$(RegorusFFIArtifactsProfile)/libregorus_ffi.so missing."
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/$(RegorusFFIArtifactsProfile)/libregorus_ffi.so')" />
<Error Text="$(RegorusFFIArtifactsDir)/aarch64-apple-darwin/$(RegorusFFIArtifactsProfile)/libregorus_ffi.dylib missing."
Condition="!Exists('$(RegorusFFIArtifactsDir)/aarch64-apple-darwin/$(RegorusFFIArtifactsProfile)/libregorus_ffi.dylib')" />
</Target>
<ItemGroup>
<None Include="docs/README.md" Pack="true" PackagePath="/" />
<None Include="../../../LICENSE" Pack="true" PackagePath="/" />
<!-- Copy each binary to expected location within the package -->
<None Include="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/$(RegorusFFIArtifactsProfile)/*.dll" Pack="true" PackagePath="runtimes/win-x64/native/" />

View File

@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
#nullable enable
namespace Regorus.Internal
{
internal static unsafe class ResultHelpers
{
internal static string? GetStringResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type switch
{
RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
RegorusDataType.Integer => result.int_value.ToString(),
RegorusDataType.None => null,
_ => Utf8Marshaller.FromUtf8(result.output)
};
}
finally
{
API.regorus_result_drop(result);
}
}
internal static bool GetBoolResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type == RegorusDataType.Boolean && result.bool_value;
}
finally
{
API.regorus_result_drop(result);
}
}
internal static long GetIntResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type == RegorusDataType.Integer ? result.int_value : 0;
}
finally
{
API.regorus_result_drop(result);
}
}
}
}

View File

@@ -7,22 +7,35 @@ using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Execution mode for the RVM runtime.
/// </summary>
public enum ExecutionMode : byte
{
/// <summary>
/// Run to completion without yielding.
/// </summary>
RunToCompletion = 0,
/// <summary>
/// Suspendable execution mode.
/// </summary>
Suspendable = 1,
}
/// <summary>
/// Wrapper for the Regorus RVM runtime.
/// </summary>
public unsafe sealed class Rvm : IDisposable
public unsafe sealed class Rvm : SafeHandleWrapper
{
private RegorusRvmHandle? _handle;
private int _isDisposed;
public Rvm()
: base(RegorusRvmHandle.Create(), nameof(Rvm))
{
_handle = RegorusRvmHandle.Create();
}
private Rvm(RegorusRvmHandle handle)
: base(handle, nameof(Rvm))
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
/// <summary>
@@ -47,13 +60,12 @@ namespace Regorus
/// </summary>
public void LoadProgram(Program program)
{
ThrowIfDisposed();
if (program is null)
{
throw new ArgumentNullException(nameof(program));
}
program.UseHandle(programPtr =>
program.UseHandleForInterop(programPtr =>
{
UseHandle(vmPtr =>
{
@@ -69,7 +81,6 @@ namespace Regorus
/// </summary>
public void SetDataJson(string dataJson)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
UseHandle(vmPtr =>
@@ -85,7 +96,6 @@ namespace Regorus
/// </summary>
public void SetInputJson(string inputJson)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
{
UseHandle(vmPtr =>
@@ -101,7 +111,6 @@ namespace Regorus
/// </summary>
public void SetExecutionMode(byte mode)
{
ThrowIfDisposed();
UseHandle(vmPtr =>
{
CheckAndDropResult(API.regorus_rvm_set_execution_mode((RegorusRvm*)vmPtr, mode));
@@ -109,12 +118,19 @@ namespace Regorus
});
}
/// <summary>
/// Set the execution mode.
/// </summary>
public void SetExecutionMode(ExecutionMode mode)
{
SetExecutionMode((byte)mode);
}
/// <summary>
/// Execute the program and return the JSON result.
/// </summary>
public string? Execute()
{
ThrowIfDisposed();
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_execute((RegorusRvm*)vmPtr));
@@ -126,7 +142,6 @@ namespace Regorus
/// </summary>
public string? ExecuteEntryPoint(string entryPoint)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(entryPoint, entryPtr =>
{
return UseHandle(vmPtr =>
@@ -141,7 +156,6 @@ namespace Regorus
/// </summary>
public string? ExecuteEntryPoint(ulong index)
{
ThrowIfDisposed();
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_execute_entry_point_by_index((RegorusRvm*)vmPtr, (UIntPtr)index));
@@ -153,7 +167,6 @@ namespace Regorus
/// </summary>
public string? Resume(string? resumeValueJson)
{
ThrowIfDisposed();
if (resumeValueJson is null)
{
return UseHandle(vmPtr =>
@@ -176,70 +189,12 @@ namespace Regorus
/// </summary>
public string? GetExecutionState()
{
ThrowIfDisposed();
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_get_execution_state((RegorusRvm*)vmPtr));
});
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
_handle?.Dispose();
_handle = null;
}
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
{
throw new ObjectDisposedException(nameof(Rvm));
}
}
internal RegorusRvmHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(Rvm));
}
return handle;
}
internal T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
try
{
handle.DangerousAddRef(ref addedRef);
var pointer = handle.DangerousGetHandle();
if (pointer == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(Rvm));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
private static Rvm GetRvmResult(RegorusResult result)
{
try
@@ -266,27 +221,7 @@ namespace Regorus
private static string? CheckAndDropResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type switch
{
RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
RegorusDataType.Integer => result.int_value.ToString(),
RegorusDataType.None => null,
_ => Utf8Marshaller.FromUtf8(result.output)
};
}
finally
{
API.regorus_result_drop(result);
}
return ResultHelpers.GetStringResult(result);
}
}
}

View File

@@ -0,0 +1,272 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
#nullable enable
namespace Regorus
{
/// <summary>
/// Base class for native handle wrappers that coordinates handle usage and disposal.
///
/// Behavior summary:
/// - UseHandle: blocks Dispose while running; throws ObjectDisposedException if disposal has started or the handle is invalid.
/// - Dispose: marks disposing and blocks new calls; waits briefly for in-flight calls to finish, then defers native release to the last exiting call if needed.
/// - Handles are never exposed directly; derived classes can only work through UseHandle helpers.
///
/// Concurrency model:
/// - _state tracks lifecycle transitions (Active -> DisposeRequested -> Released).
/// - HandleGate tracks in-flight operations and enforces the "no new calls after Dispose" rule.
/// - SafeHandle is pinned per call via DangerousAddRef to prevent use-after-free while native work runs.
/// - If Dispose times out, the last in-flight caller performs the release to avoid leaks.
/// </summary>
public abstract class SafeHandleWrapper : IDisposable
{
private static readonly TimeSpan DefaultDisposeTimeout = TimeSpan.FromMilliseconds(50);
private const int StateActive = 0;
private const int StateDisposeRequested = 1;
private const int StateReleased = 2;
private readonly HandleGate _gate;
private readonly string _ownerName;
private int _state;
private SafeHandle? _handle;
protected SafeHandleWrapper(SafeHandle handle, string ownerName)
{
// Cache ownership info and initialize the gate before any use to avoid racing disposal.
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
_ownerName = ownerName ?? throw new ArgumentNullException(nameof(ownerName));
_gate = new HandleGate(ownerName);
// Default to a very short wait when in-flight calls exist; release is deferred to the last caller if needed.
}
protected void UseHandle(Action<IntPtr> action)
{
// Reuse the generic path to keep add/ref/release in one place.
UseHandle<object?>(ptr =>
{
action(ptr);
return null;
});
}
protected T UseHandle<T>(Func<IntPtr, T> func)
{
// Fast reject if dispose was requested.
if (System.Threading.Volatile.Read(ref _state) != StateActive)
{
throw new ObjectDisposedException(_ownerName);
}
// Enter gate so Dispose waits for in-flight native calls.
_gate.Enter();
bool addedRef = false;
SafeHandle? handle = null;
try
{
// Race: Dispose could begin after Enter; GetHandleForUse validates the handle again.
handle = GetHandleForUse();
// DangerousAddRef pins the SafeHandle so Dispose cannot close it mid-call.
handle.DangerousAddRef(ref addedRef);
var pointer = handle.DangerousGetHandle();
// Validate pointer after AddRef in case handle became invalid between checks.
if (pointer == IntPtr.Zero)
{
throw new ObjectDisposedException(_ownerName);
}
return func(pointer);
}
finally
{
// Always release the DangerousAddRef to avoid leaking the native handle.
if (addedRef)
{
handle?.DangerousRelease();
}
// Leave gate so Dispose can proceed when the last caller exits.
var idle = _gate.Exit();
// Race: Dispose may have timed out while we were in-flight.
// The last exiting caller performs the native release to avoid leaks.
if (idle && System.Threading.Volatile.Read(ref _state) == StateDisposeRequested)
{
TryReleaseHandle();
}
}
}
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
{
// Explicit alias for interop-specific call sites.
return UseHandle(func);
}
internal void UseHandleForInterop(Action<IntPtr> action)
{
// Explicit alias for interop-specific call sites.
UseHandle(action);
}
private void ThrowIfDisposed()
{
// Fast check for dispose state so callers fail deterministically.
if (System.Threading.Volatile.Read(ref _state) != StateActive)
{
throw new ObjectDisposedException(_ownerName);
}
// Validate the underlying SafeHandle is still usable; avoids races with release.
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(_ownerName);
}
}
private SafeHandle GetHandleForUse()
{
// Centralized gate for derived classes to grab the handle safely.
// This is a second line of defense in case disposal began after the initial state check.
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(_ownerName);
}
return handle;
}
public void Dispose()
{
// Only the first caller runs disposal; others become no-ops.
if (System.Threading.Interlocked.CompareExchange(ref _state, StateDisposeRequested, StateActive) == StateActive)
{
// Block new calls and wait briefly if there are in-flight operations.
var completed = _gate.TryBeginDispose(DefaultDisposeTimeout, out var hadActive);
if (completed)
{
// Either no active calls or they drained within the short timeout.
TryReleaseHandle();
}
else
{
// Defer release to the last in-flight caller to avoid leaks without blocking indefinitely.
// Race: if the last in-flight caller already exited, there will be no Exit() to trigger release.
// Re-check active state and release immediately in that case.
if (!hadActive || _gate.IsIdle)
{
TryReleaseHandle();
}
}
}
GC.SuppressFinalize(this);
}
private void TryReleaseHandle()
{
if (System.Threading.Interlocked.CompareExchange(ref _state, StateReleased, StateDisposeRequested) != StateDisposeRequested)
{
return;
}
// Once released, no caller should be able to observe a valid handle.
// SafeHandle.Dispose closes the native resource; null to prevent reuse after dispose.
_handle?.Dispose();
_handle = null;
// Release the wait handle resources after disposal completes.
_gate.Dispose();
}
/// <summary>
/// Tracks in-flight operations and coordinates disposal.
/// </summary>
private sealed class HandleGate : IDisposable
{
private readonly string _ownerName;
private readonly System.Threading.ManualResetEventSlim _idle = new(initialState: true);
private int _active;
private int _disposing;
internal HandleGate(string ownerName)
{
_ownerName = ownerName;
}
internal void Enter()
{
// If disposal already started, reject new work immediately.
if (System.Threading.Volatile.Read(ref _disposing) != 0)
{
ThrowDisposed();
}
// Track active callers; first one resets idle event.
var active = System.Threading.Interlocked.Increment(ref _active);
if (active == 1)
{
_idle.Reset();
}
// Re-check disposing to handle races where Dispose began after increment.
if (System.Threading.Volatile.Read(ref _disposing) != 0)
{
Exit();
ThrowDisposed();
}
}
internal bool Exit()
{
// Last caller signals idle so Dispose can continue.
if (System.Threading.Interlocked.Decrement(ref _active) == 0)
{
_idle.Set();
return true;
}
return false;
}
internal bool IsIdle => System.Threading.Volatile.Read(ref _active) == 0;
internal bool TryBeginDispose(TimeSpan timeout, out bool hadActive)
{
// Set disposing flag once; subsequent calls treat as already disposing.
if (System.Threading.Interlocked.Exchange(ref _disposing, 1) != 0)
{
hadActive = System.Threading.Volatile.Read(ref _active) != 0;
return true;
}
hadActive = System.Threading.Volatile.Read(ref _active) != 0;
if (!hadActive)
{
// No in-flight callers; disposal can proceed without waiting.
return true;
}
// Wait for active callers to drain; optional timeout avoids blocking forever.
if (timeout == System.Threading.Timeout.InfiniteTimeSpan)
{
_idle.Wait();
return true;
}
// Race note: callers may finish between the timeout decision and Wait call; Wait handles that safely.
return _idle.Wait(timeout);
}
private void ThrowDisposed()
{
throw new ObjectDisposedException(_ownerName);
}
public void Dispose()
{
_idle.Dispose();
}
}
}
}

View File

@@ -44,7 +44,7 @@ namespace Regorus
protected override bool ReleaseHandle()
{
if (!IsInvalid && !IsClosed)
if (!IsInvalid)
{
unsafe
{
@@ -76,7 +76,7 @@ namespace Regorus
protected override bool ReleaseHandle()
{
if (!IsInvalid && !IsClosed)
if (!IsInvalid)
{
unsafe
{
@@ -124,7 +124,7 @@ namespace Regorus
protected override bool ReleaseHandle()
{
if (!IsInvalid && !IsClosed)
if (!IsInvalid)
{
unsafe
{
@@ -172,7 +172,7 @@ namespace Regorus
protected override bool ReleaseHandle()
{
if (!IsInvalid && !IsClosed)
if (!IsInvalid)
{
unsafe
{
@@ -183,4 +183,52 @@ namespace Regorus
return true;
}
}
internal sealed class RegorusAliasRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private RegorusAliasRegistryHandle() : base(ownsHandle: true)
{
}
internal static RegorusAliasRegistryHandle Create()
{
unsafe
{
var raw = Internal.API.regorus_alias_registry_new();
if (raw is null)
{
throw new InvalidOperationException("Failed to create Regorus alias registry.");
}
var handle = new RegorusAliasRegistryHandle();
handle.SetHandle((IntPtr)raw);
return handle;
}
}
internal static RegorusAliasRegistryHandle FromPointer(IntPtr pointer)
{
if (pointer == IntPtr.Zero)
{
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
}
var handle = new RegorusAliasRegistryHandle();
handle.SetHandle(pointer);
return handle;
}
protected override bool ReleaseHandle()
{
if (!IsInvalid)
{
unsafe
{
Internal.API.regorus_alias_registry_drop((Internal.RegorusAliasRegistry*)handle);
}
SetHandle(IntPtr.Zero);
}
return true;
}
}
}

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text.Json;
using Regorus.Internal;
#nullable enable
@@ -27,7 +29,7 @@ namespace Regorus
{
unsafe
{
CheckAndDropResult(Internal.API.regorus_resource_schema_register((byte*)namePtr, (byte*)schemaPtr));
ResultHelpers.GetStringResult(Internal.API.regorus_resource_schema_register((byte*)namePtr, (byte*)schemaPtr));
}
});
});
@@ -46,7 +48,7 @@ namespace Regorus
unsafe
{
var result = Internal.API.regorus_resource_schema_contains((byte*)namePtr);
return GetBoolResult(result);
return ResultHelpers.GetBoolResult(result);
}
});
}
@@ -61,7 +63,7 @@ namespace Regorus
get
{
var result = Internal.API.regorus_resource_schema_len();
return GetIntResult(result);
return ResultHelpers.GetIntResult(result);
}
}
@@ -75,7 +77,7 @@ namespace Regorus
get
{
var result = Internal.API.regorus_resource_schema_is_empty();
return GetBoolResult(result);
return ResultHelpers.GetBoolResult(result);
}
}
@@ -86,7 +88,16 @@ namespace Regorus
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static string ListResourceNames()
{
return CheckAndDropResult(Internal.API.regorus_resource_schema_list_names()) ?? "[]";
return ResultHelpers.GetStringResult(Internal.API.regorus_resource_schema_list_names()) ?? "[]";
}
/// <summary>
/// List all registered resource schema names as managed strings.
/// </summary>
public static IReadOnlyList<string> GetResourceNames()
{
var json = ListResourceNames();
return JsonSerializer.Deserialize<string[]>(json) ?? Array.Empty<string>();
}
/// <summary>
@@ -102,7 +113,7 @@ namespace Regorus
unsafe
{
var result = Internal.API.regorus_resource_schema_remove((byte*)namePtr);
return GetBoolResult(result);
return ResultHelpers.GetBoolResult(result);
}
});
}
@@ -113,7 +124,7 @@ namespace Regorus
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static void ClearResources()
{
CheckAndDropResult(Internal.API.regorus_resource_schema_clear());
ResultHelpers.GetStringResult(Internal.API.regorus_resource_schema_clear());
}
/// <summary>
@@ -130,7 +141,7 @@ namespace Regorus
{
unsafe
{
CheckAndDropResult(Internal.API.regorus_effect_schema_register((byte*)namePtr, (byte*)schemaPtr));
ResultHelpers.GetStringResult(Internal.API.regorus_effect_schema_register((byte*)namePtr, (byte*)schemaPtr));
}
});
});
@@ -149,7 +160,7 @@ namespace Regorus
unsafe
{
var result = Internal.API.regorus_effect_schema_contains((byte*)namePtr);
return GetBoolResult(result);
return ResultHelpers.GetBoolResult(result);
}
});
}
@@ -164,7 +175,7 @@ namespace Regorus
get
{
var result = Internal.API.regorus_effect_schema_len();
return GetIntResult(result);
return ResultHelpers.GetIntResult(result);
}
}
@@ -178,7 +189,7 @@ namespace Regorus
get
{
var result = Internal.API.regorus_effect_schema_is_empty();
return GetBoolResult(result);
return ResultHelpers.GetBoolResult(result);
}
}
@@ -189,7 +200,16 @@ namespace Regorus
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static string ListEffectNames()
{
return CheckAndDropResult(Internal.API.regorus_effect_schema_list_names()) ?? "[]";
return ResultHelpers.GetStringResult(Internal.API.regorus_effect_schema_list_names()) ?? "[]";
}
/// <summary>
/// List all registered effect schema names as managed strings.
/// </summary>
public static IReadOnlyList<string> GetEffectNames()
{
var json = ListEffectNames();
return JsonSerializer.Deserialize<string[]>(json) ?? Array.Empty<string>();
}
/// <summary>
@@ -205,7 +225,7 @@ namespace Regorus
unsafe
{
var result = Internal.API.regorus_effect_schema_remove((byte*)namePtr);
return GetBoolResult(result);
return ResultHelpers.GetBoolResult(result);
}
});
}
@@ -216,68 +236,7 @@ namespace Regorus
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static void ClearEffects()
{
CheckAndDropResult(Internal.API.regorus_effect_schema_clear());
}
private static string? CheckAndDropResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type switch
{
Internal.RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Internal.RegorusDataType.Integer => result.int_value.ToString(),
Internal.RegorusDataType.None => null,
_ => Utf8Marshaller.FromUtf8(result.output)
};
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static bool GetBoolResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static long GetIntResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
}
finally
{
Internal.API.regorus_result_drop(result);
}
ResultHelpers.GetStringResult(Internal.API.regorus_effect_schema_clear());
}
}
}

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text.Json;
using Regorus.Internal;
#nullable enable
@@ -26,7 +28,7 @@ namespace Regorus
{
unsafe
{
CheckAndDropResult(Internal.API.regorus_register_target_from_json((byte*)targetPtr));
ResultHelpers.GetStringResult(Internal.API.regorus_register_target_from_json((byte*)targetPtr));
}
});
}
@@ -44,7 +46,7 @@ namespace Regorus
unsafe
{
var result = Internal.API.regorus_target_registry_contains((byte*)namePtr);
return GetBoolResult(result);
return ResultHelpers.GetBoolResult(result);
}
});
}
@@ -56,7 +58,16 @@ namespace Regorus
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static string ListNames()
{
return CheckAndDropResult(Internal.API.regorus_target_registry_list_names()) ?? "[]";
return ResultHelpers.GetStringResult(Internal.API.regorus_target_registry_list_names()) ?? "[]";
}
/// <summary>
/// Get a list of all registered target names as managed strings.
/// </summary>
public static IReadOnlyList<string> GetNames()
{
var json = ListNames();
return JsonSerializer.Deserialize<string[]>(json) ?? Array.Empty<string>();
}
/// <summary>
@@ -72,7 +83,7 @@ namespace Regorus
unsafe
{
var result = Internal.API.regorus_target_registry_remove((byte*)namePtr);
return GetBoolResult(result);
return ResultHelpers.GetBoolResult(result);
}
});
}
@@ -83,7 +94,7 @@ namespace Regorus
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static void Clear()
{
CheckAndDropResult(Internal.API.regorus_target_registry_clear());
ResultHelpers.GetStringResult(Internal.API.regorus_target_registry_clear());
}
/// <summary>
@@ -96,10 +107,9 @@ namespace Regorus
get
{
var result = Internal.API.regorus_target_registry_len();
return GetIntResult(result);
return ResultHelpers.GetIntResult(result);
}
}
/// <summary>
/// Check if the target registry is empty.
/// </summary>
@@ -110,68 +120,7 @@ namespace Regorus
get
{
var result = Internal.API.regorus_target_registry_is_empty();
return GetBoolResult(result);
}
}
private static string? CheckAndDropResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type switch
{
Internal.RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Internal.RegorusDataType.Integer => result.int_value.ToString(),
Internal.RegorusDataType.None => null,
_ => Utf8Marshaller.FromUtf8(result.output)
};
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static bool GetBoolResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static long GetIntResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
}
finally
{
Internal.API.regorus_result_drop(result);
return ResultHelpers.GetBoolResult(result);
}
}
}

View File

@@ -17,10 +17,10 @@ namespace Regorus.Internal
/// </summary>
internal static class Utf8Marshaller
{
// Mirrors BCL patterns (e.g., System.Text.Json encoding helpers) by stackalloc'ing
// up to 512 bytes to cover common short strings while keeping the stack usage well
// below typical per-frame limits; larger payloads fall back to pooled buffers.
private const int StackAllocThreshold = 512;
// Mirrors BCL patterns (e.g., System.Text.Json encoding helpers) by stackalloc'ing
// up to 512 bytes to cover common short strings while keeping the stack usage well
// below typical per-frame limits; larger payloads fall back to pooled buffers.
private const int StackAllocThreshold = 512;
/// <summary>
/// Represents a pooled and pinned UTF-8 buffer suitable for scenarios where

View File

@@ -65,7 +65,7 @@ triplet_count := count([1 |
private const string EXECUTION_TIMER_QUERY = "data.limits.timer.triplet_count";
private const int EXECUTION_TIMER_VALUE_COUNT = 40;
private const string RVM_POLICY = """
private const string RVM_POLICY = """
package demo
import rego.v1
@@ -78,7 +78,7 @@ allow if {
}
""";
private const string RVM_DATA = """
private const string RVM_DATA = """
{
"roles": {
"alice": ["admin", "reader"]
@@ -86,13 +86,13 @@ allow if {
}
""";
private const string RVM_INPUT = """
private const string RVM_INPUT = """
{
"user": "alice"
}
""";
private const string HOST_AWAIT_POLICY = """
private const string HOST_AWAIT_POLICY = """
package demo
import rego.v1
@@ -105,7 +105,7 @@ allow if {
}
""";
private const string HOST_AWAIT_INPUT = """
private const string HOST_AWAIT_INPUT = """
{
"account": {
"id": "acct-1",
@@ -216,7 +216,7 @@ allow if {
var nonCompliantResult = compiledPolicy.EvalWithInput(NON_COMPLIANT_STORAGE_ACCOUNT);
Console.WriteLine($"Result: {nonCompliantResult}");
// 4. Demonstrate thread-safe concurrent evaluation
Console.WriteLine("\n4. Testing concurrent evaluation from multiple threads:");
DemonstrateConcurrentEvaluation(compiledPolicy);
@@ -246,42 +246,44 @@ allow if {
};
Console.WriteLine($"Starting {testInputs.Length} concurrent evaluations...");
var tasks = testInputs.Select(input =>
Task.Run(() => {
var tasks = testInputs.Select(input =>
Task.Run(() =>
{
var (threadName, json) = input;
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// Multiple evaluations per thread to stress test
var results = new List<string>();
for (int i = 0; i < 1000; i++)
{
var result = compiledPolicy.EvalWithInput(json);
var result = compiledPolicy.EvalWithInput(json)
?? throw new System.InvalidOperationException("Expected EvalWithInput to return a JSON value.");
results.Add(result);
}
stopwatch.Stop();
var microseconds = stopwatch.ElapsedTicks * 1000000 / System.Diagnostics.Stopwatch.Frequency;
// Verify all results are identical (thread safety)
var firstResult = results[0];
var allIdentical = results.All(r => r == firstResult);
Console.WriteLine($"✓ {threadName}: {results.Count} evaluations in {microseconds}μs, " +
$"Results consistent: {allIdentical}");
return (threadName, results.Count, microseconds, allIdentical);
})
).ToArray();
// Wait for all threads to complete
var results = Task.WhenAll(tasks).Result;
Console.WriteLine("\nConcurrency test results:");
var totalEvaluations = results.Sum(r => r.Item2);
var maxTime = results.Max(r => r.Item3);
var allConsistent = results.All(r => r.allIdentical);
Console.WriteLine($"✓ Total evaluations: {totalEvaluations}");
Console.WriteLine($"✓ Max thread time: {maxTime}μs");
Console.WriteLine($"✓ All threads consistent: {allConsistent}");
@@ -292,28 +294,28 @@ allow if {
static void DemonstratePolicyInfo(Regorus.CompiledPolicy compiledPolicy)
{
Console.WriteLine("Getting policy metadata using GetPolicyInfo()...");
try
{
var policyInfo = compiledPolicy.GetPolicyInfo();
Console.WriteLine($"✓ Policy Information Retrieved:");
Console.WriteLine($" Target Name: {policyInfo.TargetName ?? "None"}");
Console.WriteLine($" Effect Rule: {policyInfo.EffectRule ?? "None"}");
Console.WriteLine($" Entrypoint Rule: {policyInfo.EntrypointRule}");
Console.WriteLine($" Module IDs ({policyInfo.ModuleIds.Count}):");
foreach (var moduleId in policyInfo.ModuleIds)
{
Console.WriteLine($" - {moduleId}");
}
Console.WriteLine($" Applicable Resource Types ({policyInfo.ApplicableResourceTypes.Count}):");
foreach (var resourceType in policyInfo.ApplicableResourceTypes)
{
Console.WriteLine($" - {resourceType}");
}
if (policyInfo.Parameters != null && policyInfo.Parameters.Count > 0)
{
Console.WriteLine($" Policy Parameters:");
@@ -333,7 +335,7 @@ allow if {
Console.WriteLine($" Description: {param.Description}");
}
}
if (parameterSet.Modifiers.Count > 0)
{
Console.WriteLine($" Modifiers ({parameterSet.Modifiers.Count}):");
@@ -348,11 +350,11 @@ allow if {
{
Console.WriteLine(" No parameter information available");
}
// Demonstrate JSON serialization of policy info
Console.WriteLine("\n✓ Policy Info as JSON:");
var jsonOptions = new JsonSerializerOptions
{
var jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

View File

@@ -18,8 +18,13 @@ var w = new Stopwatch();
w.Restart();
// Configure the global pattern caches.
Regorus.Engine.SetCacheConfig(new Regorus.CacheConfig(regex: 256, glob: 128));
var engine = new Regorus.Engine();
engine.SetRegoV0(true);
// Raise the default col limit to 2000
engine.SetPolicyLengthConfig(new Regorus.PolicyLengthConfig(maxCol: 2000, maxFileBytes: 1048576, maxLines: 20000));
w.Stop();
var newEngineTicks = w.ElapsedTicks;
@@ -42,7 +47,8 @@ w.Restart();
// Set input and eval rule.
engine.SetInputFromJsonFile("../../../tests/aci/input.json");
var value = engine.EvalRule("data.framework.mount_overlay");
var value = engine.EvalRule("data.framework.mount_overlay")
?? throw new System.InvalidOperationException("Expected EvalRule to return a JSON value.");
#if NET8_0_OR_GREATER
var valueDoc = System.Text.Json.JsonDocument.Parse(value);

View File

@@ -1,10 +1,10 @@
{
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "3.7.56"
},
"sdk": {
"allowPrerelease": false,
"version": "8.0.412",
"rollForward": "latestFeature"
}
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "3.7.134"
},
"sdk": {
"allowPrerelease": false,
"version": "8.0.412",
"rollForward": "latestFeature"
}
}

574
bindings/ffi/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,9 @@
[package]
name = "regorus-ffi"
version = "0.9.0"
version = "0.9.1"
edition = "2021"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
@@ -31,8 +32,10 @@ default = [
"coverage",
"allocator-memory-limits",
"rvm",
"rbac",
"regorus/arc",
"regorus/full-opa",
"cache",
"contention_checks",
]
ast = ["regorus/ast"]
@@ -42,6 +45,8 @@ coverage = ["regorus/coverage"]
allocator-memory-limits = ["regorus/allocator-memory-limits"]
contention_checks = ["parking_lot"]
rvm = ["regorus/rvm"]
rbac = ["regorus/azure-rbac"]
cache = ["regorus/cache"]
custom_allocator = []
[build-dependencies]

View File

@@ -0,0 +1,479 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! FFI bindings for `AliasRegistry` Azure Policy alias catalog management.
#![cfg(feature = "azure_policy")]
use crate::common::{from_c_str, to_ref, RegorusResult, RegorusStatus};
use crate::panic_guard::with_unwind_guard;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use anyhow::Result;
use core::ffi::c_char;
use core::ptr;
use regorus::languages::azure_policy::aliases::AliasRegistry;
/// Opaque wrapper for `AliasRegistry`.
pub struct RegorusAliasRegistry {
registry: AliasRegistry,
}
// ---------------------------------------------------------------------------
// Lifecycle
// ---------------------------------------------------------------------------
/// Create a new, empty `AliasRegistry`.
///
/// The caller must eventually call `regorus_alias_registry_drop` to free the handle.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_new() -> *mut RegorusAliasRegistry {
let wrapper = RegorusAliasRegistry {
registry: AliasRegistry::new(),
};
Box::into_raw(Box::new(wrapper))
}
/// Drop a `RegorusAliasRegistry`.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_drop(registry: *mut RegorusAliasRegistry) {
if let Ok(r) = to_ref(registry) {
unsafe {
let _ = Box::from_raw(ptr::from_mut(r));
}
}
}
// ---------------------------------------------------------------------------
// Loading
// ---------------------------------------------------------------------------
/// Load control-plane alias data (array of `ProviderAliases`) into the registry.
///
/// `json` must be a valid null-terminated UTF-8 string containing the JSON
/// array returned by `Get-AzPolicyAlias` or the static
/// `ResourceTypesAndAliases.json` file.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_load_json(
registry: *mut RegorusAliasRegistry,
json: *const c_char,
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<()> {
let json_str = from_c_str(json)?;
to_ref(registry)?.registry.load_from_json(&json_str)?;
Ok(())
}();
match output {
Ok(()) => RegorusResult::ok_void(),
Err(e) => RegorusResult::err_with_message(
RegorusStatus::InvalidDataFormat,
format!("Failed to load alias catalog: {e}"),
),
}
})
}
/// Load a data-plane policy manifest into the registry.
///
/// `json` must be a valid null-terminated UTF-8 string containing a single
/// `DataPolicyManifest` JSON object.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_load_manifest(
registry: *mut RegorusAliasRegistry,
json: *const c_char,
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<()> {
let json_str = from_c_str(json)?;
to_ref(registry)?
.registry
.load_data_policy_manifest_json(&json_str)?;
Ok(())
}();
match output {
Ok(()) => RegorusResult::ok_void(),
Err(e) => RegorusResult::err_with_message(
RegorusStatus::InvalidDataFormat,
format!("Failed to load data-plane manifest: {e}"),
),
}
})
}
// ---------------------------------------------------------------------------
// Queries
// ---------------------------------------------------------------------------
/// Return the number of resource types loaded in the alias registry.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_len(registry: *mut RegorusAliasRegistry) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<i64> {
let len = to_ref(registry)?.registry.len();
Ok(len as i64)
}();
match output {
Ok(n) => RegorusResult::ok_int(n),
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
}
})
}
// ---------------------------------------------------------------------------
// Normalize / Denormalize
// ---------------------------------------------------------------------------
/// Normalize an ARM resource JSON and wrap it into the standard input envelope.
///
/// Returns a JSON string:
/// `{ "resource": <normalized>, "context": <context>, "parameters": <params> }`.
///
/// * `resource_json` raw ARM resource JSON
/// * `api_version` API version string (e.g. `"2023-01-01"`), or null to use
/// the default alias paths
/// * `context_json` JSON object for additional context (pass `"{}"` if none)
/// * `parameters_json` JSON object of policy parameter values (pass `"{}"` if none)
#[no_mangle]
pub extern "C" fn regorus_alias_registry_normalize_and_wrap(
registry: *mut RegorusAliasRegistry,
resource_json: *const c_char,
api_version: *const c_char,
context_json: *const c_char,
parameters_json: *const c_char,
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let resource_str = from_c_str(resource_json)?;
let api_ver = if api_version.is_null() {
None
} else {
let s = from_c_str(api_version)?;
if s.is_empty() {
None
} else {
Some(s)
}
};
let context_str = from_c_str(context_json)?;
let params_str = from_c_str(parameters_json)?;
let resource = regorus::Value::from_json_str(&resource_str)?;
let context = regorus::Value::from_json_str(&context_str)?;
let params = regorus::Value::from_json_str(&params_str)?;
let wrapped = to_ref(registry)?.registry.normalize_and_wrap(
&resource,
api_ver.as_deref(),
Some(context),
Some(params),
);
wrapped.to_json_str()
}();
match output {
Ok(s) => RegorusResult::ok_string(s),
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
}
})
}
/// Denormalize a previously-normalized resource JSON back to ARM format.
///
/// * `normalized_json` the normalized resource JSON
/// * `api_version` API version string, or null to use the default alias paths
///
/// Returns the denormalized ARM JSON string.
#[no_mangle]
pub extern "C" fn regorus_alias_registry_denormalize(
registry: *mut RegorusAliasRegistry,
normalized_json: *const c_char,
api_version: *const c_char,
) -> RegorusResult {
with_unwind_guard(|| {
let output = || -> Result<String> {
let normalized_str = from_c_str(normalized_json)?;
let api_ver = if api_version.is_null() {
None
} else {
let s = from_c_str(api_version)?;
if s.is_empty() {
None
} else {
Some(s)
}
};
let normalized = regorus::Value::from_json_str(&normalized_str)?;
let result = to_ref(registry)?
.registry
.denormalize(&normalized, api_ver.as_deref());
result.to_json_str()
}();
match output {
Ok(s) => RegorusResult::ok_string(s),
Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::regorus_result_drop;
use core::ffi::CStr;
use std::ffi::CString;
/// Helper: create a C string from a Rust &str.
fn c(s: &str) -> CString {
CString::new(s).expect("CString::new failed")
}
/// Helper: assert a RegorusResult has Ok status and extract string output.
fn assert_ok_string(r: &RegorusResult) -> String {
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
assert!(!r.output.is_null(), "expected non-null output");
let s = unsafe { CStr::from_ptr(r.output) }
.to_str()
.expect("invalid UTF-8 in output")
.to_string();
s
}
/// Helper: assert a RegorusResult has Ok status with integer output.
fn assert_ok_int(r: &RegorusResult) -> i64 {
assert_eq!(r.status, RegorusStatus::Ok, "expected Ok status");
r.int_value
}
const ALIASES: &str = r#"[{
"namespace": "Microsoft.Storage",
"resourceTypes": [{
"resourceType": "storageAccounts",
"aliases": [{
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"defaultPath": "properties.supportsHttpsTrafficOnly",
"paths": []
}]
}]
}]"#;
const MANIFEST: &str = r#"{
"dataNamespace": "Microsoft.KeyVault.Data",
"aliases": [],
"resourceTypeAliases": [{
"resourceType": "vaults/certificates",
"aliases": [{
"name": "Microsoft.KeyVault.Data/vaults/certificates/keySize",
"paths": [{ "path": "keySize", "apiVersions": ["7.0"] }]
}]
}]
}"#;
#[test]
fn lifecycle_new_and_drop() {
let reg = regorus_alias_registry_new();
assert!(!reg.is_null());
regorus_alias_registry_drop(reg);
}
#[test]
fn load_json_and_check_len() {
let reg = regorus_alias_registry_new();
let json = c(ALIASES);
let r = regorus_alias_registry_load_json(reg, json.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let r = regorus_alias_registry_len(reg);
assert_eq!(assert_ok_int(&r), 1);
regorus_result_drop(r);
regorus_alias_registry_drop(reg);
}
#[test]
fn load_manifest_and_check_len() {
let reg = regorus_alias_registry_new();
let json = c(MANIFEST);
let r = regorus_alias_registry_load_manifest(reg, json.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let r = regorus_alias_registry_len(reg);
assert_eq!(assert_ok_int(&r), 1);
regorus_result_drop(r);
regorus_alias_registry_drop(reg);
}
#[test]
fn load_invalid_json_returns_error() {
let reg = regorus_alias_registry_new();
let bad = c("not valid json");
let r = regorus_alias_registry_load_json(reg, bad.as_ptr());
assert_ne!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
regorus_alias_registry_drop(reg);
}
#[test]
fn normalize_and_wrap_round_trip() {
let reg = regorus_alias_registry_new();
let aliases = c(ALIASES);
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let resource = c(r#"{
"name": "acct1",
"type": "Microsoft.Storage/storageAccounts",
"properties": { "supportsHttpsTrafficOnly": true }
}"#);
let api = c("2023-01-01");
let ctx = c(r#"{"resourceGroup": {"name": "rg1"}}"#);
let params = c(r#"{"env": "prod"}"#);
// Normalize
let r = regorus_alias_registry_normalize_and_wrap(
reg,
resource.as_ptr(),
api.as_ptr(),
ctx.as_ptr(),
params.as_ptr(),
);
let envelope_json = assert_ok_string(&r);
regorus_result_drop(r);
// Parse and verify structure
let envelope: serde_json::Value =
serde_json::from_str(&envelope_json).expect("invalid JSON output");
assert!(
envelope.get("resource").is_some(),
"envelope missing 'resource'"
);
assert!(
envelope.get("parameters").is_some(),
"envelope missing 'parameters'"
);
assert!(
envelope.get("context").is_some(),
"envelope missing 'context'"
);
// The normalized resource should have lowercased alias fields
let res = &envelope["resource"];
assert_eq!(res["supportshttpstrafficonly"], true);
assert_eq!(res["name"], "acct1");
// Context and parameters should be passed through
assert_eq!(envelope["context"]["resourceGroup"]["name"], "rg1");
assert_eq!(envelope["parameters"]["env"], "prod");
// Denormalize the resource portion
let resource_json = serde_json::to_string(&res).expect("serialize resource");
let norm_cstr = c(&resource_json);
let r = regorus_alias_registry_denormalize(reg, norm_cstr.as_ptr(), api.as_ptr());
let denorm_json = assert_ok_string(&r);
regorus_result_drop(r);
let denorm: serde_json::Value =
serde_json::from_str(&denorm_json).expect("invalid denorm JSON");
// Should be back under properties with restored casing
assert_eq!(
denorm["properties"]["supportsHttpsTrafficOnly"], true,
"expected restored casing under properties"
);
regorus_alias_registry_drop(reg);
}
#[test]
fn denormalize_invalid_json_returns_error() {
let reg = regorus_alias_registry_new();
let aliases = c(ALIASES);
let r = regorus_alias_registry_load_json(reg, aliases.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let bad = c("not json");
let api = c("2023-01-01");
let r = regorus_alias_registry_denormalize(reg, bad.as_ptr(), api.as_ptr());
assert_ne!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
regorus_alias_registry_drop(reg);
}
#[test]
fn normalize_data_plane_manifest() {
let reg = regorus_alias_registry_new();
let manifest = c(MANIFEST);
let r = regorus_alias_registry_load_manifest(reg, manifest.as_ptr());
assert_eq!(r.status, RegorusStatus::Ok);
regorus_result_drop(r);
let resource = c(r#"{
"type": "Microsoft.KeyVault.Data/vaults/certificates",
"keySize": 2048
}"#);
let api = c("7.0");
let ctx = c("{}");
let params = c("{}");
let r = regorus_alias_registry_normalize_and_wrap(
reg,
resource.as_ptr(),
api.as_ptr(),
ctx.as_ptr(),
params.as_ptr(),
);
let envelope_json = assert_ok_string(&r);
regorus_result_drop(r);
let envelope: serde_json::Value =
serde_json::from_str(&envelope_json).expect("invalid JSON output");
assert_eq!(envelope["resource"]["keysize"], 2048);
regorus_alias_registry_drop(reg);
}
#[test]
fn empty_registry_normalize() {
let reg = regorus_alias_registry_new();
let resource = c(r#"{"name": "test", "type": "Unknown/type", "properties": {"foo": 1}}"#);
let api = c("");
let ctx = c("{}");
let params = c("{}");
let r = regorus_alias_registry_normalize_and_wrap(
reg,
resource.as_ptr(),
api.as_ptr(),
ctx.as_ptr(),
params.as_ptr(),
);
let json = assert_ok_string(&r);
regorus_result_drop(r);
let envelope: serde_json::Value = serde_json::from_str(&json).expect("invalid JSON");
// Without aliases, properties should still be flattened
assert_eq!(envelope["resource"]["foo"], 1);
assert_eq!(envelope["resource"]["name"], "test");
regorus_alias_registry_drop(reg);
}
}

View File

@@ -11,6 +11,7 @@ use core::ffi::{c_char, c_longlong, c_void, CStr};
use core::{mem, ptr};
/// Status of a call on `RegorusEngine`.
#[derive(Debug, PartialEq)]
#[repr(C)]
pub enum RegorusStatus {
/// The operation was successful.

View File

@@ -6,6 +6,7 @@ use crate::common::{
};
use crate::compiled_policy::RegorusCompiledPolicy;
use crate::limits::RegorusExecutionTimerConfig;
use crate::limits::RegorusPolicyLengthConfig;
use crate::lock::{new_handle, read, try_read, try_write, Handle, ReadGuard, WriteGuard};
use crate::panic_guard::with_unwind_guard;
use alloc::boxed::Box;
@@ -491,6 +492,37 @@ pub extern "C" fn regorus_engine_clear_execution_timer_config(
}())
}
/// Set the policy length limits used when loading policies.
#[no_mangle]
pub extern "C" fn regorus_engine_set_policy_length_config(
engine: *mut RegorusEngine,
config: RegorusPolicyLengthConfig,
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let mut guard = engine.try_write()?;
guard.set_policy_length_config(config.to_policy_length_config()?);
Ok(())
}())
})
}
/// Clear the policy length configuration, reverting to defaults.
#[no_mangle]
pub extern "C" fn regorus_engine_clear_policy_length_config(
engine: *mut RegorusEngine,
) -> RegorusResult {
with_unwind_guard(|| {
to_regorus_result(|| -> Result<()> {
let engine = to_ref(engine)?;
let mut guard = engine.try_write()?;
guard.clear_policy_length_config();
Ok(())
}())
})
}
/// Get pretty printed coverage report.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty

View File

@@ -5,6 +5,7 @@
extern crate alloc;
mod alias_registry;
mod allocator;
mod common;
mod compile;
@@ -14,6 +15,8 @@ mod engine;
mod limits;
mod lock;
mod panic_guard;
#[cfg(feature = "rbac")]
mod rbac;
#[cfg(feature = "rvm")]
pub(crate) mod rvm;
mod schema_registry;

View File

@@ -4,7 +4,7 @@
use crate::common::{to_regorus_result, RegorusResult, RegorusStatus};
use alloc::format;
use anyhow::{anyhow, Result};
use core::num::NonZeroU32;
use core::num::{NonZeroU32, NonZeroUsize};
use core::time::Duration;
use regorus::utils::limits::{self, ExecutionTimerConfig};
@@ -158,6 +158,31 @@ impl RegorusExecutionTimerConfig {
}
}
/// FFI representation of [`regorus::PolicyLengthConfig`].
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RegorusPolicyLengthConfig {
/// Maximum column width per line (must be non-zero).
pub max_col: u32,
/// Maximum policy file size in bytes (must be non-zero).
pub max_file_bytes: usize,
/// Maximum number of lines per policy file (must be non-zero).
pub max_lines: usize,
}
impl RegorusPolicyLengthConfig {
pub fn to_policy_length_config(self) -> Result<regorus::PolicyLengthConfig> {
Ok(regorus::PolicyLengthConfig {
max_col: NonZeroU32::new(self.max_col)
.ok_or_else(|| anyhow!("max_col must be non-zero"))?,
max_file_bytes: NonZeroUsize::new(self.max_file_bytes)
.ok_or_else(|| anyhow!("max_file_bytes must be non-zero"))?,
max_lines: NonZeroUsize::new(self.max_lines)
.ok_or_else(|| anyhow!("max_lines must be non-zero"))?,
})
}
}
#[no_mangle]
pub extern "C" fn regorus_set_fallback_execution_timer_config(
config: RegorusExecutionTimerConfig,
@@ -174,6 +199,40 @@ pub extern "C" fn regorus_clear_fallback_execution_timer_config() -> RegorusResu
RegorusResult::ok_void()
}
// ---------------------------------------------------------------------------
// Cache configuration (global)
// ---------------------------------------------------------------------------
/// FFI representation of [`regorus::cache::Config`].
#[cfg(feature = "cache")]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RegorusCacheConfig {
/// Maximum compiled regex patterns (default 256, 0 = disabled).
pub regex: usize,
/// Maximum compiled glob matchers (default 128, 0 = disabled).
pub glob: usize,
}
/// Configure the global pattern caches used by `regex.*` and `glob.*` builtins.
#[cfg(feature = "cache")]
#[no_mangle]
pub extern "C" fn regorus_set_cache_config(config: RegorusCacheConfig) -> RegorusResult {
regorus::cache::configure(regorus::cache::Config {
regex: config.regex,
glob: config.glob,
});
RegorusResult::ok_void()
}
/// Clear all entries from every pattern cache.
#[cfg(feature = "cache")]
#[no_mangle]
pub extern "C" fn regorus_clear_cache() -> RegorusResult {
regorus::cache::clear();
RegorusResult::ok_void()
}
#[cfg(test)]
mod tests {
use super::{

59
bindings/ffi/src/rbac.rs Normal file
View File

@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::common::{from_c_str, RegorusResult, RegorusStatus};
use crate::panic_guard::with_unwind_guard;
use alloc::format;
use core::ffi::c_char;
use regorus::languages::azure_rbac::ast::EvaluationContext;
use regorus::languages::azure_rbac::interpreter::ConditionInterpreter;
#[no_mangle]
/// Evaluate an Azure RBAC condition expression against a JSON evaluation context.
///
/// * `condition`: RBAC condition string.
/// * `context_json`: JSON representation of EvaluationContext.
pub extern "C" fn regorus_rbac_engine_eval_condition(
condition: *const c_char,
context_json: *const c_char,
) -> RegorusResult {
with_unwind_guard(|| {
let condition = match from_c_str(condition) {
Ok(value) => value,
Err(err) => {
return RegorusResult::err_with_message(
RegorusStatus::InvalidArgument,
format!("{err}"),
)
}
};
let context_json = match from_c_str(context_json) {
Ok(value) => value,
Err(err) => {
return RegorusResult::err_with_message(
RegorusStatus::InvalidArgument,
format!("{err}"),
)
}
};
let context: EvaluationContext = match serde_json::from_str(&context_json) {
Ok(context) => context,
Err(err) => {
return RegorusResult::err_with_message(
RegorusStatus::InvalidDataFormat,
format!("invalid context json: {err}"),
)
}
};
let interpreter = ConditionInterpreter::new(&context);
match interpreter.evaluate_str(&condition) {
Ok(result) => RegorusResult::ok_bool(result),
Err(err) => RegorusResult::err_with_message(
RegorusStatus::Error,
format!("condition evaluation failed: {err}"),
),
}
})
}

View File

@@ -17,7 +17,15 @@ func main() {
engine := regorus.NewEngine()
defer engine.Close()
// Configure the global pattern caches.
if err = regorus.SetCacheConfig(regorus.CacheConfig{Regex: 256, Glob: 128}); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
engine.SetRegoV0(true)
// Raise the default col limit to 2000
engine.SetPolicyLengthConfig(regorus.PolicyLengthConfig{MaxCol: 2000, MaxFileBytes: 1048576, MaxLines: 20000})
elapsed1 := time.Since(t)

View File

@@ -214,3 +214,59 @@ func (e *Engine) TakePrints() (string, error) {
return C.GoString(result.output), nil
}
type PolicyLengthConfig struct {
MaxCol uint32
MaxFileBytes uint
MaxLines uint
}
func (e *Engine) SetPolicyLengthConfig(config PolicyLengthConfig) error {
c := C.RegorusPolicyLengthConfig{
max_col: C.uint32_t(config.MaxCol),
max_file_bytes: C.size_t(config.MaxFileBytes),
max_lines: C.size_t(config.MaxLines),
}
result := C.regorus_engine_set_policy_length_config(e.e, c)
defer C.regorus_result_drop(result)
if result.status != C.Ok {
return fmt.Errorf("%s", C.GoString(result.error_message))
}
return nil
}
func (e *Engine) ClearPolicyLengthConfig() error {
result := C.regorus_engine_clear_policy_length_config(e.e)
defer C.regorus_result_drop(result)
if result.status != C.Ok {
return fmt.Errorf("%s", C.GoString(result.error_message))
}
return nil
}
type CacheConfig struct {
Regex uint
Glob uint
}
func SetCacheConfig(config CacheConfig) error {
c := C.RegorusCacheConfig{
regex: C.size_t(config.Regex),
glob: C.size_t(config.Glob),
}
result := C.regorus_set_cache_config(c)
defer C.regorus_result_drop(result)
if result.status != C.Ok {
return fmt.Errorf("%s", C.GoString(result.error_message))
}
return nil
}
func ClearCache() error {
result := C.regorus_clear_cache()
defer C.regorus_result_drop(result)
if result.status != C.Ok {
return fmt.Errorf("%s", C.GoString(result.error_message))
}
return nil
}

648
bindings/java/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,11 @@
[package]
name = "regorus-java"
version = "0.9.0"
version = "0.9.1"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/java"
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -13,12 +14,13 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
crate-type = ["cdylib"]
[features]
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
default = ["ast", "cache", "coverage", "regorus/std", "regorus/full-opa", "regorus/allocator-memory-limits"]
coverage = ["regorus/coverage"]
ast = ["regorus/ast"]
cache = ["regorus/cache"]
[dependencies]
anyhow = "1.0"
serde_json = "1.0.112"
jni = "0.21.1"
jni = "0.22.4"
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }

View File

@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import com.microsoft.regorus.CacheConfig;
import com.microsoft.regorus.Engine;
import com.microsoft.regorus.PolicyLengthConfig;
import com.microsoft.regorus.PolicyModule;
import com.microsoft.regorus.Program;
import com.microsoft.regorus.Rvm;
@@ -9,6 +11,9 @@ import com.microsoft.regorus.Rvm;
public class Test {
public static void main(String[] args) {
// Configure the global pattern caches.
CacheConfig.configure(new CacheConfig(256, 128));
try (Engine engine = new Engine()) {
String pkg = engine.addPolicy(
"hello.rego",
@@ -26,6 +31,9 @@ public class Test {
// Enable coverage.
engine.setEnableCoverage(true);
// Raise the default col limit to 2000
engine.setPolicyLengthConfig(new PolicyLengthConfig(2000, 1048576, 20000));
// Evaluate rule.
String valueJson = engine.evalRule("data.test.message");
System.out.println(valueJson);

View File

@@ -9,7 +9,7 @@
<groupId>com.microsoft.regorus</groupId>
<artifactId>regorus-java</artifactId>
<version>0.9.0</version>
<version>0.9.1</version>
<name>Regorus Java</name>
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>
@@ -18,7 +18,15 @@
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/blog/license/mit</url>
<url>https://opensource.org/licenses/MIT</url>
</license>
<license>
<name>Apache License 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
</license>
<license>
<name>BSD 3-Clause License</name>
<url>https://opensource.org/licenses/BSD-3-Clause</url>
</license>
</licenses>
@@ -40,13 +48,13 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<version>2.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -68,7 +76,7 @@
<plugin>
<artifactId>exec-maven-plugin</artifactId>
<groupId>org.codehaus.mojo</groupId>
<version>3.1.0</version>
<version>3.6.3</version>
<executions>
<execution>
<!-- Build a debug release for tests -->
@@ -89,7 +97,7 @@
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<version>3.5.5</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>
@@ -100,7 +108,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<version>3.12.0</version>
<executions>
<execution>
<id>attach-javadoc</id>
@@ -115,7 +123,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-sources</id>

View File

@@ -2,9 +2,11 @@
// Licensed under the MIT License.
use anyhow::Result;
use core::num::{NonZeroU32, NonZeroUsize};
use jni::objects::{JBooleanArray, JByteArray, JClass, JObject, JObjectArray, JString};
use jni::strings::JNIString;
use jni::sys::{jboolean, jbooleanArray, jbyteArray, jlong, jobjectArray, jstring};
use jni::JNIEnv;
use jni::{jni_str, Env, EnvUnowned, Outcome};
use regorus::languages::rego::compiler::Compiler;
use regorus::rvm::program::{
@@ -16,7 +18,7 @@ use std::sync::Arc;
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
_env: JNIEnv,
_env: EnvUnowned,
_class: JClass,
) -> jlong {
let engine = Engine::new();
@@ -25,7 +27,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
_env: JNIEnv,
_env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jlong {
@@ -36,7 +38,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetRegoV0(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
enable: bool,
@@ -50,7 +52,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetRegoV0(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
path: JString,
@@ -58,9 +60,9 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = env.get_string(&path)?.into();
let rego: String = env.get_string(&rego)?.into();
let pkg = env.new_string(engine.add_policy(path, rego)?)?;
let path: String = path.try_to_string(env)?;
let rego: String = rego.try_to_string(env)?;
let pkg = JString::new(env, engine.add_policy(path, rego)?)?;
Ok(pkg.into_raw())
});
@@ -72,15 +74,15 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
path: JString,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = env.get_string(&path)?.into();
let pkg = env.new_string(engine.add_policy_from_file(path)?)?;
let path: String = path.try_to_string(env)?;
let pkg = JString::new(env, engine.add_policy_from_file(path)?)?;
Ok(pkg.into_raw())
});
@@ -92,14 +94,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPackages(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let packages = engine.get_packages()?;
let packages_json = env.new_string(serde_json::to_string_pretty(&packages)?)?;
let packages_json = JString::new(env, serde_json::to_string_pretty(&packages)?)?;
Ok(packages_json.into_raw())
});
@@ -111,14 +113,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPackages(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPolicies(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let policies = engine.get_policies_as_json()?;
let policies_json = env.new_string(&policies)?;
let policies_json = JString::new(env, &policies)?;
Ok(policies_json.into_raw())
});
@@ -130,7 +132,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPolicies(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearData(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) {
@@ -143,14 +145,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearData(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJson(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
data: JString,
) {
let _ = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let data: String = env.get_string(&data)?.into();
let data: String = data.try_to_string(env)?;
engine.add_data_json(&data)?;
Ok(())
});
@@ -158,14 +160,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJson(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJsonFromFile(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
path: JString,
) {
let _ = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = env.get_string(&path)?.into();
let path: String = path.try_to_string(env)?;
engine.add_data(Value::from_json_file(path)?)?;
Ok(())
});
@@ -173,14 +175,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJsonFromFi
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJson(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
input: JString,
) {
let _ = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let input: String = env.get_string(&input)?.into();
let input: String = input.try_to_string(env)?;
engine.set_input_json(&input)?;
Ok(())
});
@@ -188,14 +190,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJson(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromFile(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
path: JString,
) {
let _ = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = env.get_string(&path)?.into();
let path: String = path.try_to_string(env)?;
engine.set_input(Value::from_json_file(path)?);
Ok(())
});
@@ -203,16 +205,16 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromF
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalQuery(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
query: JString,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let query: String = env.get_string(&query)?.into();
let query: String = query.try_to_string(env)?;
let results = engine.eval_query(query, false)?;
let output = env.new_string(serde_json::to_string(&results)?)?;
let output = JString::new(env, serde_json::to_string(&results)?)?;
Ok(output.into_raw())
});
@@ -224,16 +226,16 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalQuery(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalRule(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
rule: JString,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let rule: String = env.get_string(&rule)?.into();
let rule: String = rule.try_to_string(env)?;
let value = engine.eval_rule(rule)?;
let output = env.new_string(value.to_json_str()?)?;
let output = JString::new(env, value.to_json_str()?)?;
Ok(output.into_raw())
});
@@ -246,7 +248,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalRule(
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
enable: bool,
@@ -261,14 +263,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let report = engine.get_coverage_report()?;
let output = env.new_string(serde_json::to_string_pretty(&report)?)?;
let output = JString::new(env, serde_json::to_string_pretty(&report)?)?;
Ok(output.into_raw())
});
@@ -281,14 +283,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReportPretty(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let report = engine.get_coverage_report()?.to_string_pretty()?;
let output = env.new_string(&report)?;
let output = JString::new(env, &report)?;
Ok(output.into_raw())
});
@@ -301,7 +303,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearCoverageData(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) {
@@ -314,7 +316,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearCoverageData
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetGatherPrints(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
b: bool,
@@ -328,14 +330,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetGatherPrints(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeTakePrints(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let prints = engine.take_prints()?;
let output = env.new_string(serde_json::to_string_pretty(&prints)?)?;
let output = JString::new(env, serde_json::to_string_pretty(&prints)?)?;
Ok(output.into_raw())
});
@@ -348,14 +350,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeTakePrints(
#[no_mangle]
#[cfg(feature = "ast")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_getAstAsJson(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let ast = engine.get_ast_as_json()?;
let output = env.new_string(&ast)?;
let output = JString::new(env, &ast)?;
Ok(output.into_raw())
});
@@ -365,9 +367,73 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_getAstAsJson(
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetPolicyLengthConfig(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
max_col: u32,
max_file_bytes: jlong,
max_lines: jlong,
) {
let _ = throw_err(env, |_env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.set_policy_length_config(regorus::PolicyLengthConfig {
max_col: NonZeroU32::new(max_col)
.ok_or_else(|| anyhow::anyhow!("maxCol must be non-zero"))?,
max_file_bytes: NonZeroUsize::new(max_file_bytes as usize)
.ok_or_else(|| anyhow::anyhow!("maxFileBytes must be non-zero"))?,
max_lines: NonZeroUsize::new(max_lines as usize)
.ok_or_else(|| anyhow::anyhow!("maxLines must be non-zero"))?,
});
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearPolicyLengthConfig(
_env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.clear_policy_length_config();
}
#[cfg(feature = "cache")]
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_CacheConfig_nativeSetCacheConfig(
_env: EnvUnowned,
_class: JClass,
regex: jlong,
glob: jlong,
) {
regorus::cache::configure(regorus::cache::Config {
regex: if regex < 0 {
0
} else {
usize::try_from(regex).unwrap_or(usize::MAX)
},
glob: if glob < 0 {
0
} else {
usize::try_from(glob).unwrap_or(usize::MAX)
},
});
}
#[cfg(feature = "cache")]
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_CacheConfig_nativeClearCache(
_env: EnvUnowned,
_class: JClass,
) {
regorus::cache::clear();
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
_env: JNIEnv,
_env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) {
@@ -378,7 +444,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromModules(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
data_json: JString,
module_ids: jobjectArray,
@@ -386,7 +452,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromModul
entry_points: jobjectArray,
) -> jlong {
let res = throw_err(env, |env| {
let data_json: String = env.get_string(&data_json)?.into();
let data_json: String = data_json.try_to_string(env)?;
let data = Value::from_json_str(&data_json)?;
let ids = get_string_array(env, module_ids)?;
@@ -422,7 +488,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromModul
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromEngine(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
entry_points: jobjectArray,
@@ -447,7 +513,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromEngin
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeGenerateListing(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
program_ptr: jlong,
) -> jstring {
@@ -455,7 +521,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeGenerateListing(
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
let listing =
generate_assembly_listing(program.as_ref(), &AssemblyListingConfig::default());
let output = env.new_string(&listing)?;
let output = JString::new(env, &listing)?;
Ok(output.into_raw())
});
@@ -467,7 +533,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeGenerateListing(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeSerializeBinary(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
program_ptr: jlong,
) -> jbyteArray {
@@ -491,7 +557,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeSerializeBinary(
/// for the duration of the call. They must come from the JVM for the current
/// thread and not be used after this function returns.
pub unsafe extern "system" fn Java_com_microsoft_regorus_Program_nativeDeserializeBinary(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
data: jbyteArray,
is_partial: jbooleanArray,
@@ -501,7 +567,7 @@ pub unsafe extern "system" fn Java_com_microsoft_regorus_Program_nativeDeseriali
return Err(anyhow::anyhow!("data must not be null"));
}
let data = unsafe { JByteArray::from_raw(data) };
let data = unsafe { JByteArray::from_raw(env, data) };
let bytes = env.convert_byte_array(&data)?;
let (program, partial) =
match RvmProgram::deserialize_binary(&bytes).map_err(|e| anyhow::anyhow!(e))? {
@@ -510,11 +576,15 @@ pub unsafe extern "system" fn Java_com_microsoft_regorus_Program_nativeDeseriali
};
if !is_partial.is_null() {
let is_partial = unsafe { JBooleanArray::from_raw(is_partial) };
let len = env.get_array_length(&is_partial)?;
let is_partial = unsafe { JBooleanArray::from_raw(env, is_partial) };
let len = is_partial.len(env)?;
if len > 0 {
let value: [jboolean; 1] = [if partial { 1 } else { 0 }];
env.set_boolean_array_region(&is_partial, 0, &value)?;
let value: [jboolean; 1] = [if partial {
jni::sys::JNI_TRUE
} else {
jni::sys::JNI_FALSE
}];
is_partial.set_region(env, 0, &value)?;
}
}
@@ -526,7 +596,7 @@ pub unsafe extern "system" fn Java_com_microsoft_regorus_Program_nativeDeseriali
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeDrop(
_env: JNIEnv,
_env: EnvUnowned,
_class: JClass,
program_ptr: jlong,
) {
@@ -537,7 +607,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Program_nativeDrop(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeNew(
_env: JNIEnv,
_env: EnvUnowned,
_class: JClass,
) -> jlong {
let vm = RegoVM::new();
@@ -546,7 +616,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeNew(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeLoadProgram(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
program_ptr: jlong,
@@ -561,14 +631,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeLoadProgram(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetDataJson(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
data_json: JString,
) {
let _ = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let data_json: String = env.get_string(&data_json)?.into();
let data_json: String = data_json.try_to_string(env)?;
let data = Value::from_json_str(&data_json)?;
vm.set_data(data)?;
Ok(())
@@ -577,14 +647,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetDataJson(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetInputJson(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
input_json: JString,
) {
let _ = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let input_json: String = env.get_string(&input_json)?.into();
let input_json: String = input_json.try_to_string(env)?;
let input = Value::from_json_str(&input_json)?;
vm.set_input(input);
Ok(())
@@ -593,7 +663,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetInputJson(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetExecutionMode(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
mode: u8,
@@ -612,14 +682,14 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetExecutionMode(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecute(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let result = vm.execute()?;
let output = env.new_string(result.to_json_str()?)?;
let output = JString::new(env, result.to_json_str()?)?;
Ok(output.into_raw())
});
@@ -631,16 +701,16 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecute(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecuteEntryPoint(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
entry_point: JString,
) -> jstring {
let res = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let entry_point: String = env.get_string(&entry_point)?.into();
let entry_point: String = entry_point.try_to_string(env)?;
let result = vm.execute_entry_point_by_name(&entry_point)?;
let output = env.new_string(result.to_json_str()?)?;
let output = JString::new(env, result.to_json_str()?)?;
Ok(output.into_raw())
});
@@ -652,7 +722,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecuteEntryPoint(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeResume(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
resume_json: JString,
@@ -661,13 +731,13 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeResume(
let res = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let value = if has_value {
let resume_json: String = env.get_string(&resume_json)?.into();
let resume_json: String = resume_json.try_to_string(env)?;
Some(Value::from_json_str(&resume_json)?)
} else {
None
};
let result = vm.resume(value)?;
let output = env.new_string(result.to_json_str()?)?;
let output = JString::new(env, result.to_json_str()?)?;
Ok(output.into_raw())
});
@@ -679,13 +749,13 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeResume(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeGetExecutionState(
env: JNIEnv,
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let output = env.new_string(format!("{:?}", vm.execution_state()))?;
let output = JString::new(env, format!("{:?}", vm.execution_state()))?;
Ok(output.into_raw())
});
@@ -697,7 +767,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeGetExecutionState(
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeDrop(
_env: JNIEnv,
_env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
) {
@@ -706,27 +776,57 @@ pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeDrop(
}
}
fn throw_err<T>(mut env: JNIEnv, mut f: impl FnMut(&mut JNIEnv) -> Result<T>) -> Result<T> {
match f(&mut env) {
Ok(val) => Ok(val),
Err(err) => {
env.throw(err.to_string())?;
fn throw_err<T>(mut env: EnvUnowned, f: impl FnOnce(&mut Env) -> Result<T>) -> Result<T> {
let outcome = env.with_env(|env| -> Result<T> {
match f(env) {
Ok(val) => Ok(val),
Err(err) => {
if let Err(throw_err) = env.throw_new(
jni_str!("java/lang/RuntimeException"),
JNIString::new(err.to_string()),
) {
return Err(anyhow::anyhow!(
"Failed to throw Java RuntimeException for error '{err}': {throw_err}"
));
}
Err(err)
}
}
});
match outcome.into_outcome() {
Outcome::Ok(val) => Ok(val),
Outcome::Err(err) => Err(err),
Outcome::Panic(payload) => {
let msg = payload
.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("unknown panic");
let err = anyhow::anyhow!("panic: {msg}");
// Try to surface the panic as a Java exception.
let _ = env.with_env(|env| -> Result<()> {
env.throw_new(
jni_str!("java/lang/RuntimeException"),
JNIString::new(format!("Rust panic: {msg}")),
)?;
Ok(())
});
Err(err)
}
}
}
fn get_string_array(env: &mut JNIEnv, array: jobjectArray) -> Result<Vec<String>> {
fn get_string_array(env: &mut Env, array: jobjectArray) -> Result<Vec<String>> {
if array.is_null() {
return Ok(Vec::new());
}
let array = unsafe { JObjectArray::from_raw(array) };
let len = env.get_array_length(&array)?;
let mut values = Vec::with_capacity(len as usize);
let array = unsafe { JObjectArray::<JObject>::from_raw(env, array) };
let len = array.len(env)?;
let mut values = Vec::with_capacity(len);
for i in 0..len {
let obj = env.get_object_array_element(&array, i)?;
let jstr = JString::from(obj);
let value: String = env.get_string(&jstr)?.into();
let obj = array.get_element(env, i)?;
let jstr = unsafe { JString::from_raw(env, obj.into_raw()) };
let value: String = jstr.try_to_string(env)?;
values.push(value);
}
Ok(values)

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
**/
package com.microsoft.regorus;
/**
* Global configuration for compiled pattern caches used by regex and glob builtins.
*
* <p>Capacity of 0 disables the corresponding cache.
*/
public final class CacheConfig {
static {
System.loadLibrary("regorus_java");
}
private static native void nativeSetCacheConfig(long regex, long glob);
private static native void nativeClearCache();
/**
* Maximum cached compiled regex patterns (default 256).
*/
public final long regex;
/**
* Maximum cached compiled glob matchers (default 128).
*/
public final long glob;
/**
* Create a new cache configuration.
*
* @param regex Maximum cached compiled regex patterns (0 = disabled).
* @param glob Maximum cached compiled glob matchers (0 = disabled).
*/
public CacheConfig(long regex, long glob) {
if (regex < 0) {
throw new IllegalArgumentException("regex must be non-negative");
}
if (glob < 0) {
throw new IllegalArgumentException("glob must be non-negative");
}
this.regex = regex;
this.glob = glob;
}
/**
* Apply this cache configuration globally.
*/
public static void configure(CacheConfig config) {
nativeSetCacheConfig(config.regex, config.glob);
}
/**
* Clear all entries from every pattern cache.
*/
public static void clear() {
nativeClearCache();
}
}

View File

@@ -39,6 +39,8 @@ public class Engine implements AutoCloseable, Cloneable {
private static native void nativeClearCoverageData(long enginePtr);
private static native void nativeSetGatherPrints(long enginePtr, boolean b);
private static native String nativeTakePrints(long enginePtr);
private static native void nativeSetPolicyLengthConfig(long enginePtr, int maxCol, long maxFileBytes, long maxLines);
private static native void nativeClearPolicyLengthConfig(long enginePtr);
private static native void nativeDestroyEngine(long enginePtr);
// Pointer to Engine allocated on Rust's heap, all native methods works on
@@ -259,6 +261,22 @@ public class Engine implements AutoCloseable, Cloneable {
return nativeTakePrints(enginePtr);
}
/**
* Set the policy length limits used when loading policies.
*
* @param config Policy length configuration.
*/
public void setPolicyLengthConfig(PolicyLengthConfig config) {
nativeSetPolicyLengthConfig(enginePtr, config.maxCol, config.maxFileBytes, config.maxLines);
}
/**
* Clear the policy length configuration, reverting to defaults.
*/
public void clearPolicyLengthConfig() {
nativeClearPolicyLengthConfig(enginePtr);
}
long getPtr() {
return enginePtr;
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
**/
package com.microsoft.regorus;
/**
* Policy source length limits enforced when loading policy files.
*
* All values must be positive (non-zero).
*/
public final class PolicyLengthConfig {
/**
* Maximum column width per line (default: 1024).
*/
public final int maxCol;
/**
* Maximum policy file size in bytes (default: 1 MiB).
*/
public final long maxFileBytes;
/**
* Maximum number of lines per policy file (default: 20000).
*/
public final long maxLines;
/**
* Create a new policy length configuration.
*
* @param maxCol Maximum column width per line.
* @param maxFileBytes Maximum policy file size in bytes.
* @param maxLines Maximum number of lines per policy file.
*/
public PolicyLengthConfig(int maxCol, long maxFileBytes, long maxLines) {
if (maxCol <= 0) {
throw new IllegalArgumentException("maxCol must be positive");
}
if (maxFileBytes <= 0) {
throw new IllegalArgumentException("maxFileBytes must be positive");
}
if (maxLines <= 0) {
throw new IllegalArgumentException("maxLines must be positive");
}
this.maxCol = maxCol;
this.maxFileBytes = maxFileBytes;
this.maxLines = maxLines;
}
}

View File

@@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"getrandom",
"getrandom 0.3.4",
"once_cell",
"serde",
"version_check",
@@ -25,6 +25,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -36,9 +42,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.100"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "autocfg"
@@ -46,22 +52,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"serde",
"unty",
]
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -79,9 +69,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "borrow-or-share"
@@ -101,9 +91,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.19.1"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytecount"
@@ -113,9 +103,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.55"
version = "1.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -128,10 +118,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.43"
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core",
]
[[package]]
name = "chrono"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -150,12 +151,30 @@ dependencies = [
"phf",
]
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "data-encoding"
version = "2.10.0"
@@ -182,6 +201,18 @@ dependencies = [
"serde",
]
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -190,9 +221,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fancy-regex"
version = "0.14.0"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
dependencies = [
"bit-set",
"regex-automata",
@@ -207,15 +238,27 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fluent-uri"
version = "0.3.2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5"
checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e"
dependencies = [
"borrow-or-share",
"ref-cast",
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -242,9 +285,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"r-efi 5.3.0",
"wasip2",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
"rand_core",
"wasip2",
"wasip3",
]
[[package]]
@@ -259,11 +318,25 @@ dependencies = [
"regex-syntax",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
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"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
]
[[package]]
name = "heck"
@@ -376,6 +449,12 @@ 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"
@@ -404,37 +483,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "indoc"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
dependencies = [
"rustversion",
]
[[package]]
name = "ipnet"
version = "2.11.0"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "itoa"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.85"
version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -442,27 +512,28 @@ dependencies = [
[[package]]
name = "jsonschema"
version = "0.30.0"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b46a0365a611fbf1d2143104dcf910aada96fafd295bab16c60b802bf6fa1d"
checksum = "6f29616f6e19415398eb186964fb7cbbeef572c79bede3622a8277667924bbe3"
dependencies = [
"ahash",
"base64",
"bytecount",
"data-encoding",
"email_address",
"fancy-regex",
"fraction",
"getrandom 0.3.4",
"idna",
"itoa",
"num-cmp",
"num-traits",
"once_cell",
"percent-encoding",
"referencing",
"regex",
"regex-syntax",
"serde",
"serde_json",
"unicode-general-category",
"uuid-simd",
]
@@ -473,10 +544,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.180"
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "litemap"
@@ -500,19 +577,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.7.6"
name = "lru"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
[[package]]
name = "memoffset"
version = "0.9.1"
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "msvc_spectre_libs"
@@ -604,15 +678,15 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.21.3"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "ordered-float"
version = "5.1.0"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e"
dependencies = [
"num-traits",
]
@@ -672,9 +746,21 @@ dependencies = [
[[package]]
name = "portable-atomic"
version = "1.13.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "postcard"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"serde",
]
[[package]]
name = "potential_utf"
@@ -686,12 +772,13 @@ dependencies = [
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"zerocopy",
"proc-macro2",
"syn",
]
[[package]]
@@ -705,38 +792,33 @@ dependencies = [
[[package]]
name = "pyo3"
version = "0.24.2"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219"
checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1"
dependencies = [
"anyhow",
"cfg-if",
"indoc",
"libc",
"memoffset",
"once_cell",
"portable-atomic",
"pyo3-build-config",
"pyo3-ffi",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.24.2"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999"
checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7"
dependencies = [
"once_cell",
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.24.2"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33"
checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc"
dependencies = [
"libc",
"pyo3-build-config",
@@ -744,9 +826,9 @@ dependencies = [
[[package]]
name = "pyo3-macros"
version = "0.24.2"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9"
checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
@@ -756,9 +838,9 @@ dependencies = [
[[package]]
name = "pyo3-macros-backend"
version = "0.24.2"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a"
checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a"
dependencies = [
"heck",
"proc-macro2",
@@ -769,9 +851,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.44"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
@@ -783,33 +865,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand_chacha"
version = "0.9.0"
name = "rand"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
dependencies = [
"ppv-lite86",
"chacha20",
"getrandom 0.4.2",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.5"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom",
]
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
name = "redox_syscall"
@@ -842,13 +918,14 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.30.0"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8eff4fa778b5c2a57e85c5f2fe3a709c52f0e60d23146e2151cbef5893f420e"
checksum = "b8a618c14f8ba29d8193bb55e2bf13e4fb2b1115313ecb7ae94b43100c7ac7d5"
dependencies = [
"ahash",
"fluent-uri",
"once_cell",
"getrandom 0.3.4",
"hashbrown 0.16.1",
"parking_lot",
"percent-encoding",
"serde_json",
@@ -856,9 +933,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.2"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
@@ -868,9 +945,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
@@ -879,16 +956,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.8"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "regorus"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"anyhow",
"bincode",
"chrono",
"chrono-tz",
"data-encoding",
@@ -897,9 +973,12 @@ dependencies = [
"ipnet",
"jsonschema",
"lazy_static",
"lru",
"msvc_spectre_libs",
"num-bigint",
"num-traits",
"parking_lot",
"postcard",
"rand",
"regex",
"regorus-mimalloc",
@@ -929,7 +1008,7 @@ dependencies = [
[[package]]
name = "regoruspy"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"anyhow",
"ordered-float",
@@ -946,9 +1025,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "scopeguard"
@@ -1038,9 +1117,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "spin"
version = "0.9.8"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
[[package]]
name = "stable_deref_trait"
@@ -1050,9 +1129,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.114"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@@ -1072,9 +1151,9 @@ dependencies = [
[[package]]
name = "target-lexicon"
version = "0.13.4"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba"
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
[[package]]
name = "thiserror"
@@ -1107,16 +1186,22 @@ dependencies = [
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
name = "unicode-general-category"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
[[package]]
name = "unindent"
version = "0.2.4"
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
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"
@@ -1124,12 +1209,6 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "url"
version = "2.5.8"
@@ -1150,14 +1229,12 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.20.0"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
dependencies = [
"getrandom",
"js-sys",
"getrandom 0.4.2",
"rand",
"wasm-bindgen",
]
[[package]]
@@ -1167,7 +1244,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8"
dependencies = [
"outref",
"uuid",
"vsimd",
]
@@ -1193,10 +1269,19 @@ dependencies = [
]
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
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 = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
dependencies = [
"cfg-if",
"once_cell",
@@ -1207,9 +1292,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.108"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1217,9 +1302,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.108"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1230,13 +1315,47 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.108"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
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"
@@ -1301,6 +1420,88 @@ 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-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"
@@ -1333,18 +1534,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.36"
version = "0.8.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.36"
version = "0.8.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
dependencies = [
"proc-macro2",
"quote",
@@ -1407,6 +1608,6 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.17"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

View File

@@ -2,10 +2,11 @@
[package]
name = "regoruspy"
version = "0.9.0"
version = "0.9.1"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/python"
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
@@ -14,14 +15,15 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
crate-type = ["cdylib"]
[features]
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
default = ["ast", "cache", "coverage", "regorus/std", "regorus/full-opa", "regorus/allocator-memory-limits"]
ast = ["regorus/ast"]
cache = ["regorus/cache"]
coverage = ["regorus/coverage"]
[dependencies]
anyhow = "1.0"
ordered-float = "5.0.0"
pyo3 = { version = "0.24.1", features = ["abi3-py310", "anyhow", "extension-module"] }
ordered-float = "5.3.0"
pyo3 = { version = "0.28.2", features = ["abi3-py310", "anyhow", "extension-module"] }
regorus = { path = "../..", default-features = false, features = ["arc", "rvm"] }
serde_json = "1.0.140"

View File

@@ -5,7 +5,11 @@ build-backend = "maturin"
[project]
name = "regorus"
requires-python = ">=3.8"
license = { text = "MIT AND Apache-2.0 AND BSD-3-Clause" }
classifiers = [
"License :: OSI Approved :: Apache Software License",
"License :: OSI Approved :: BSD License",
"License :: OSI Approved :: MIT License",
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",

View File

@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use anyhow::{anyhow, Result};
use core::num::{NonZeroU32, NonZeroUsize};
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pyo3::types::*;
@@ -43,7 +44,7 @@ impl Default for Engine {
fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
// dicts
Ok(if let Ok(dict) = ob.downcast::<PyDict>() {
Ok(if let Ok(dict) = ob.cast::<PyDict>() {
let mut map = BTreeMap::new();
for (k, v) in dict {
map.insert(from(&k)?, from(&v)?);
@@ -51,7 +52,7 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
map.into()
}
// set
else if let Ok(pset) = ob.downcast::<PySet>() {
else if let Ok(pset) = ob.cast::<PySet>() {
let mut set = BTreeSet::new();
for v in pset {
set.insert(from(&v)?);
@@ -59,7 +60,7 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
set.into()
}
// frozen set
else if let Ok(pfset) = ob.downcast::<PyFrozenSet>() {
else if let Ok(pfset) = ob.cast::<PyFrozenSet>() {
//
let mut set = BTreeSet::new();
for v in pfset {
@@ -68,13 +69,13 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
set.into()
}
// lists and tuples
else if let Ok(plist) = ob.downcast::<PyList>() {
else if let Ok(plist) = ob.cast::<PyList>() {
let mut array = Vec::new();
for v in plist {
array.push(from(&v)?);
}
array.into()
} else if let Ok(ptuple) = ob.downcast::<PyTuple>() {
} else if let Ok(ptuple) = ob.cast::<PyTuple>() {
let mut array = Vec::new();
for v in ptuple {
array.push(from(&v)?);
@@ -85,6 +86,10 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
else if let Ok(s) = ob.extract::<String>() {
s.into()
}
// Boolean
else if let Ok(b) = ob.extract::<bool>() {
b.into()
}
// Numeric
else if let Ok(v) = ob.extract::<i64>() {
v.into()
@@ -93,16 +98,12 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
} else if let Ok(v) = ob.extract::<f64>() {
v.into()
}
// Boolean
else if let Ok(b) = ob.extract::<bool>() {
b.into()
}
// None
else if ob.downcast::<PyNone>().is_ok() {
else if ob.cast::<PyNone>().is_ok() {
Value::Null
}
// Anything that is a sequence
else if let Ok(pseq) = ob.downcast::<PySequence>() {
else if let Ok(pseq) = ob.cast::<PySequence>() {
let mut array = Vec::new();
for i in 0..pseq.len()? {
array.push(from(&pseq.get_item(i)?)?);
@@ -110,7 +111,7 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
array.into()
}
// Anything that is a map
else if let Ok(pmap) = ob.downcast::<PyMapping>() {
else if let Ok(pmap) = ob.cast::<PyMapping>() {
let mut map = BTreeMap::new();
let keys = pmap.keys()?;
let values = pmap.values()?;
@@ -127,7 +128,7 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
})
}
fn to(mut v: Value, py: Python<'_>) -> Result<PyObject> {
fn to(mut v: Value, py: Python<'_>) -> Result<Py<PyAny>> {
let obj = match v {
Value::Null => None::<u64>.into_bound_py_any(py),
@@ -138,12 +139,17 @@ fn to(mut v: Value, py: Python<'_>) -> Result<PyObject> {
Value::String(s) => s.into_bound_py_any(py),
Value::Number(_) => {
if let Ok(f) = v.as_f64() {
if v.as_number()?.is_integer() {
if let Ok(u) = v.as_u64() {
u.into_bound_py_any(py)
} else {
v.as_i64()?.into_bound_py_any(py)
}
} else if let Ok(f) = v.as_f64() {
f.into_bound_py_any(py)
} else if let Ok(u) = v.as_u64() {
u.into_bound_py_any(py)
} else {
v.as_i64()?.into_bound_py_any(py)
// fallback
v.as_f64()?.into_bound_py_any(py)
}
}
@@ -291,7 +297,7 @@ impl Engine {
/// Evaluate query.
///
/// * `query`: Rego expression to be evaluate.
pub fn eval_query(&mut self, query: String, py: Python<'_>) -> Result<PyObject> {
pub fn eval_query(&mut self, query: String, py: Python<'_>) -> Result<Py<PyAny>> {
let results = self.engine.eval_query(query, false)?;
let rlist = PyList::empty(py);
@@ -332,7 +338,7 @@ impl Engine {
/// Evaluate rule.
///
/// * `rule`: Full path to the rule.
pub fn eval_rule(&mut self, rule: String, py: Python<'_>) -> Result<PyObject> {
pub fn eval_rule(&mut self, rule: String, py: Python<'_>) -> Result<Py<PyAny>> {
to(self.engine.eval_rule(rule)?, py)
}
@@ -344,6 +350,78 @@ impl Engine {
v.to_json_str()
}
/// Registers a custom Python function as a Rego extension.
///
/// This allows you to define functions in Python that can be called directly
/// from your Rego policies. The Python function will be called synchronously
/// during policy evaluation.
///
/// Arguments passed from Rego are automatically converted to their corresponding
/// Python types. The return value is converted back to a Rego value.
///
/// * `path`: Full path to the function as it will be used in Rego.
/// * `nargs`: The number of arguments the function expects.
/// * `extension`: The Python function to execute. Must accept exactly `nargs` arguments.
///
/// Note: When the engine is cloned, extensions share the same Python callable reference
/// rather than being deep-copied. Stateful callables will share state across clones.
pub fn add_extension(&mut self, path: String, nargs: u8, extension: Py<PyAny>) -> Result<()> {
Python::attach(|py| {
if !extension.bind(py).is_callable() {
return Err(anyhow!("extension '{}' must be callable", path));
}
Ok(())
})?;
let func_ref = Arc::new(extension);
let path_clone = path.clone();
let extension_impl = move |args: Vec<Value>| -> Result<Value, anyhow::Error> {
Python::attach(|py| {
let py_args_vec: Result<Vec<Py<PyAny>>> =
args.into_iter().map(|arg| to(arg, py)).collect();
let py_args = PyTuple::new(py, py_args_vec?)?;
let py_result = func_ref.call1(py, py_args).map_err(|e| {
anyhow!("extension '{}' raises Python error: {}", path_clone, e)
})?;
let rego_result = from(&py_result.into_bound(py))?;
Ok(rego_result)
})
};
self.engine
.add_extension(path, nargs, Box::new(extension_impl))
}
/// Set the policy length limits used when loading policies.
///
/// * `max_col`: Maximum column width per line.
/// * `max_file_bytes`: Maximum policy file size in bytes.
/// * `max_lines`: Maximum number of lines per policy file.
#[pyo3(signature = (*, max_col, max_file_bytes, max_lines))]
pub fn set_policy_length_config(
&mut self,
max_col: u32,
max_file_bytes: usize,
max_lines: usize,
) -> Result<()> {
self.engine
.set_policy_length_config(::regorus::PolicyLengthConfig {
max_col: NonZeroU32::new(max_col)
.ok_or_else(|| anyhow!("max_col must be non-zero"))?,
max_file_bytes: NonZeroUsize::new(max_file_bytes)
.ok_or_else(|| anyhow!("max_file_bytes must be non-zero"))?,
max_lines: NonZeroUsize::new(max_lines)
.ok_or_else(|| anyhow!("max_lines must be non-zero"))?,
});
Ok(())
}
/// Clear the policy length configuration, reverting to defaults.
pub fn clear_policy_length_config(&mut self) {
self.engine.clear_policy_length_config();
}
/// Enable code coverage
///
/// * `enable`: Whether to enable coverage or not.
@@ -544,10 +622,33 @@ impl Rvm {
}
}
/// Configure the global pattern caches used by `regex.*` and `glob.*` builtins.
///
/// * `regex`: Maximum cached compiled regex patterns (default 256, 0 = disabled).
/// * `glob`: Maximum cached compiled glob matchers (default 128, 0 = disabled).
#[cfg(feature = "cache")]
#[pyfunction]
#[pyo3(signature = (*, regex = 256, glob = 128))]
fn set_cache_config(regex: usize, glob: usize) {
::regorus::cache::configure(::regorus::cache::Config { regex, glob });
}
/// Clear all entries from every pattern cache.
#[cfg(feature = "cache")]
#[pyfunction]
fn clear_cache() {
::regorus::cache::clear();
}
#[pymodule]
pub fn regorus(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<crate::Engine>()?;
m.add_class::<crate::Program>()?;
m.add_class::<crate::Rvm>()?;
#[cfg(feature = "cache")]
{
m.add_function(wrap_pyfunction!(set_cache_config, m)?)?;
m.add_function(wrap_pyfunction!(clear_cache, m)?)?;
}
Ok(())
}

View File

@@ -1,16 +1,20 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import regorus
import sys
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
# Configure the global pattern caches.
regorus.set_cache_config(regex=256, glob=128)
# Create engine
engine = regorus.Engine()
engine.set_rego_v0(True)
# Raise the default col limit to 2000
engine.set_policy_length_config(max_col=2000, max_file_bytes=1048576, max_lines=20000)
# Load policies
pkg = engine.add_policy_from_file('../../tests/aci/framework.rego')
@@ -163,3 +167,254 @@ def run_host_await_example():
print(vm.resume('{"tier":"gold"}'))
run_host_await_example()
def test_extension_execution():
rego = regorus.Engine()
rego.add_policy("demo",
"""
package demo
result := greeting(a, b) if {
a := data.a
b := data.b
}
""")
def custom_function(arg1, arg2):
return f"{arg1}, {arg2}!"
rego.add_extension("greeting", 2, custom_function)
rego.add_data({"a": "Hello", "b": "World"})
result = rego.eval_rule("data.demo.result")
assert result == "Hello, World!", f"Unexpected result: {result}"
test_extension_execution()
def test_extension_wrong_arity():
rego = regorus.Engine()
rego.add_policy("demo",
"""
package demo
result := greeting(a, b) if {
a := data.a
b := data.b
}
""")
def custom_function(arg1, arg2):
return f"{arg1}, {arg2}!"
rego.add_extension("greeting", 3, custom_function)
rego.add_data({"a": "Hello", "b": "World"})
try:
rego.eval_rule("data.demo.result")
except RuntimeError as ex:
assert "error: incorrect number of parameters supplied to extension" in str(ex)
else:
assert False, "exception not thrown"
test_extension_wrong_arity()
def test_extension_raises_exception():
rego = regorus.Engine()
rego.add_policy("demo",
"""
package demo
result := greeting(a, b) if {
a := data.a
b := data.b
}
""")
def custom_function(arg1, arg2):
raise RuntimeError("unknown error")
rego.add_extension("greeting", 2, custom_function)
rego.add_data({"a": "Hello", "b": "World"})
try:
rego.eval_rule("data.demo.result")
except RuntimeError as ex:
assert "error: extension 'greeting' raises Python error: RuntimeError: unknown error" in str(ex)
else:
assert False, "exception not thrown"
test_extension_raises_exception()
def test_extension_zero_arg():
rego = regorus.Engine()
rego.add_policy("demo",
"""
package demo
result := greeting()
""")
def custom_function():
return "Hello, World!"
rego.add_extension("greeting", 0, custom_function)
rego.add_data({"a": "Hello", "b": "World"})
result = rego.eval_rule("data.demo.result")
assert result == "Hello, World!", f"Unexpected result: {result}"
test_extension_zero_arg()
def test_extension_non_callable():
rego = regorus.Engine()
rego.add_policy("demo",
"""
package demo
result := greeting()
""")
try:
rego.add_extension("greeting", 0, 123)
except RuntimeError as ex:
assert "extension 'greeting' must be callable" in str(ex)
else:
assert False, "exception not thrown"
test_extension_non_callable()
def test_extension_duplicate():
rego = regorus.Engine()
rego.add_policy("demo",
"""
package demo
result := greeting()
""")
def custom_function1(arg1, arg2):
return f"{arg1}, {arg2}!"
def custom_function2(arg1, arg2):
return f"{arg1}, {arg2}!"
rego.add_extension("greeting", 0, custom_function1)
try:
rego.add_extension("greeting", 0, custom_function2)
except RuntimeError as ex:
assert "extension already added" in str(ex)
else:
assert False, "exception not thrown"
test_extension_duplicate()
def test_extension_types():
rego = regorus.Engine()
rego.add_policy("demo",
"""
package demo
i := custom.triple(10)
f := custom.triple(2.5)
b1 := custom.negate(true)
b2 := custom.negate(false)
a := custom.first([true, null, 1])
b := custom.first([null, null, 1])
c := custom.first([null, null, null])
object := custom.modify_object({"a": 1, "b": 2})
list := custom.modify_list([3, 4])
set := custom.modify_set({5, 6})
""")
def triple(n):
return n*3
def negate(b):
return not b
def first(lst):
for i in lst:
if i is not None:
return i
return None
def modify_object(object):
assert isinstance(object, dict)
return {k: v*2 for k, v in object.items()}
def modify_list(lst):
assert isinstance(lst, list)
return [x*2 for x in lst]
def modify_set(st):
assert isinstance(st, set)
return {x*2 for x in st}
rego.add_extension("custom.triple", 1, triple)
rego.add_extension("custom.negate", 1, negate)
rego.add_extension("custom.first", 1, first)
rego.add_extension("custom.modify_object", 1, modify_object)
rego.add_extension("custom.modify_list", 1, modify_list)
rego.add_extension("custom.modify_set", 1, modify_set)
i = rego.eval_rule("data.demo.i")
assert i == 30, f"Unexpected result for 'i': {i}"
f = rego.eval_rule("data.demo.f")
assert f == 7.5, f"Unexpected result for 'f': {f}"
b1 = rego.eval_rule("data.demo.b1")
assert b1 == False, f"Unexpected result for 'b1': {b1}"
b2 = rego.eval_rule("data.demo.b2")
assert b2 == True, f"Unexpected result for 'b2': {b2}"
a = rego.eval_rule("data.demo.a")
assert a == True, f"Unexpected result for 'a': {a}"
b = rego.eval_rule("data.demo.b")
assert b == 1, f"Unexpected result for 'b': {b}"
c = rego.eval_rule("data.demo.c")
assert c is None, f"Unexpected result for 'c': {c}"
obj = rego.eval_rule("data.demo.object")
assert obj == {"a": 2, "b": 4}, f"Unexpected object: {obj}"
lst = rego.eval_rule("data.demo.list")
assert lst == [6, 8], f"Unexpected list: {lst}"
st = rego.eval_rule("data.demo.set")
assert st == {10, 12}, f"Unexpected set: {st}"
test_extension_types()
def test_boolean_mapping():
rego = regorus.Engine()
rego.add_policy("demo",
"""
package demo
result_b := data.a if {
data.a == true
}
result_i := data.b if {
data.b == 1
}
""")
rego.add_data({"a": True, "b": 1})
result_b = rego.eval_rule("data.demo.result_b")
assert isinstance(result_b, bool), f"Expected bool, got {type(result_b)}"
assert result_b, f"Unexpected result for 'result_b': {result_b}"
result_i = rego.eval_rule("data.demo.result_i")
assert isinstance(result_i, int), f"Expected int, got {type(result_i)}"
assert result_i == 1, f"Unexpected result for 'result_i': {result_i}"
test_boolean_mapping()

392
bindings/ruby/Cargo.lock generated
View File

@@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"getrandom",
"getrandom 0.3.4",
"once_cell",
"serde",
"version_check",
@@ -25,6 +25,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -36,9 +42,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.100"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "autocfg"
@@ -46,12 +52,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bindgen"
version = "0.69.5"
@@ -147,10 +147,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.43"
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core",
]
[[package]]
name = "chrono"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -186,6 +197,15 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "data-encoding"
version = "2.10.0"
@@ -226,9 +246,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fancy-regex"
version = "0.14.0"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
dependencies = [
"bit-set",
"regex-automata",
@@ -243,15 +263,27 @@ checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
[[package]]
name = "fluent-uri"
version = "0.3.2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5"
checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e"
dependencies = [
"borrow-or-share",
"ref-cast",
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -278,9 +310,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"r-efi 5.3.0",
"wasip2",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
"rand_core",
"wasip2",
"wasip3",
]
[[package]]
@@ -301,11 +349,31 @@ dependencies = [
"regex-syntax",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
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"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "iana-time-zone"
@@ -412,6 +480,12 @@ 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"
@@ -440,14 +514,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "ipnet"
version = "2.11.0"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "itertools"
@@ -476,27 +552,28 @@ dependencies = [
[[package]]
name = "jsonschema"
version = "0.30.0"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b46a0365a611fbf1d2143104dcf910aada96fafd295bab16c60b802bf6fa1d"
checksum = "6f29616f6e19415398eb186964fb7cbbeef572c79bede3622a8277667924bbe3"
dependencies = [
"ahash",
"base64",
"bytecount",
"data-encoding",
"email_address",
"fancy-regex",
"fraction",
"getrandom 0.3.4",
"idna",
"itoa",
"num-cmp",
"num-traits",
"once_cell",
"percent-encoding",
"referencing",
"regex",
"regex-syntax",
"serde",
"serde_json",
"unicode-general-category",
"uuid-simd",
]
@@ -512,6 +589,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[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.180"
@@ -550,10 +633,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "magnus"
version = "0.7.1"
name = "lru"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d87ae53030f3a22e83879e666cb94e58a7bdf31706878a0ba48752994146dab"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
[[package]]
name = "magnus"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012"
dependencies = [
"magnus-macros",
"rb-sys",
@@ -563,9 +652,9 @@ dependencies = [
[[package]]
name = "magnus-macros"
version = "0.6.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5968c820e2960565f647819f5928a42d6e874551cab9d88d75e3e0660d7f71e3"
checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
dependencies = [
"proc-macro2",
"quote",
@@ -578,20 +667,6 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "mimalloc"
version = "2.2.6"
dependencies = [
"mimalloc-sys",
]
[[package]]
name = "mimalloc-sys"
version = "0.0.0"
dependencies = [
"cc",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -765,12 +840,13 @@ dependencies = [
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"zerocopy",
"proc-macro2",
"syn",
]
[[package]]
@@ -798,33 +874,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand_chacha"
version = "0.9.0"
name = "rand"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
dependencies = [
"ppv-lite86",
"chacha20",
"getrandom 0.4.2",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.5"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom",
]
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
name = "rb-sys"
@@ -852,9 +922,9 @@ dependencies = [
[[package]]
name = "rb-sys-env"
version = "0.1.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a35802679f07360454b418a5d1735c89716bde01d35b1560fc953c1415a0b3bb"
checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
[[package]]
name = "redox_syscall"
@@ -887,13 +957,14 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.30.0"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8eff4fa778b5c2a57e85c5f2fe3a709c52f0e60d23146e2151cbef5893f420e"
checksum = "b8a618c14f8ba29d8193bb55e2bf13e4fb2b1115313ecb7ae94b43100c7ac7d5"
dependencies = [
"ahash",
"fluent-uri",
"once_cell",
"getrandom 0.3.4",
"hashbrown 0.16.1",
"parking_lot",
"percent-encoding",
"serde_json",
@@ -901,9 +972,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.2"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
@@ -930,37 +1001,56 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "regorus"
version = "0.5.0"
version = "0.9.1"
dependencies = [
"anyhow",
"chrono",
"chrono-tz",
"data-encoding",
"globset",
"indexmap",
"ipnet",
"jsonschema",
"lazy_static",
"mimalloc",
"lru",
"msvc_spectre_libs",
"num-bigint",
"num-traits",
"parking_lot",
"rand",
"regex",
"regorus-mimalloc",
"semver",
"serde",
"serde_json",
"serde_yaml",
"spin",
"thiserror",
"url",
"uuid",
]
[[package]]
name = "regorus-mimalloc"
version = "2.2.6"
dependencies = [
"regorus-mimalloc-sys",
]
[[package]]
name = "regorus-mimalloc-sys"
version = "2.2.6"
dependencies = [
"cc",
]
[[package]]
name = "regorusrb"
version = "0.6.0"
version = "0.9.1"
dependencies = [
"magnus",
"regorus",
"serde",
"serde_json",
"serde_magnus",
]
@@ -1046,9 +1136,9 @@ dependencies = [
[[package]]
name = "serde_magnus"
version = "0.9.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b8b945a2dadb221f1c5490cfb411cab6c3821446b8eca50ee07e5a3893ec51"
checksum = "8ff64c88ddd26acdcad5a501f18bcc339927b77b69f4a03bfaf2a6fc5ba2ac4b"
dependencies = [
"magnus",
"serde",
@@ -1092,6 +1182,12 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "spin"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@@ -1156,12 +1252,24 @@ dependencies = [
"zerovec",
]
[[package]]
name = "unicode-general-category"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[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"
@@ -1188,14 +1296,12 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.19.0"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
dependencies = [
"getrandom",
"js-sys",
"getrandom 0.4.2",
"rand",
"wasm-bindgen",
]
[[package]]
@@ -1205,7 +1311,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8"
dependencies = [
"outref",
"uuid",
"vsimd",
]
@@ -1230,6 +1335,15 @@ dependencies = [
"wit-bindgen",
]
[[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",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
@@ -1275,6 +1389,40 @@ 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"
@@ -1339,6 +1487,88 @@ 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-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"

View File

@@ -7,10 +7,10 @@ gemspec
# These gems are required for local development and testing,
# but won't be included in the published gem
gem "minitest", "~> 5.25"
gem "rake", "~> 13.2"
gem "rake-compiler", "~> 1.2"
gem "rake-compiler-dock", "~> 1.9"
gem "rubocop", "~> 1.73", require: false
gem "rubocop-minitest", "~> 0.37.1", require: false
gem "minitest", "~> 6.0"
gem "rake", "~> 13.3"
gem "rake-compiler", "~> 1.3"
gem "rake-compiler-dock", "~> 1.11"
gem "rubocop", "~> 1.86", require: false
gem "rubocop-minitest", "~> 0.39.1", require: false
gem "rubocop-rake", "~> 0.7.1", require: false

View File

@@ -7,25 +7,30 @@ PATH
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
json (2.10.2)
language_server-protocol (3.17.0.4)
ast (2.4.3)
drb (2.2.3)
json (2.19.2)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
minitest (5.25.4)
parallel (1.26.3)
parser (3.3.7.1)
minitest (6.0.3)
drb (~> 2.0)
prism (~> 1.5)
parallel (1.27.0)
parser (3.3.10.2)
ast (~> 2.4.1)
racc
prism (1.9.0)
racc (1.8.1)
rainbow (3.1.1)
rake (13.2.1)
rake-compiler (1.2.9)
rake (13.3.1)
rake-compiler (1.3.1)
rake
rake-compiler-dock (1.9.1)
rb_sys (0.9.111)
rake-compiler-dock (= 1.9.1)
regexp_parser (2.10.0)
rubocop (1.73.2)
rake-compiler-dock (1.11.0)
rb_sys (0.9.125)
json (>= 2)
rake-compiler-dock (= 1.11.0)
regexp_parser (2.11.3)
rubocop (1.86.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -33,35 +38,36 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-ast (>= 1.49.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.38.1)
parser (>= 3.3.1.0)
rubocop-minitest (0.37.1)
rubocop-ast (1.49.1)
parser (>= 3.3.7.2)
prism (~> 1.7)
rubocop-minitest (0.39.1)
lint_roller (~> 1.1)
rubocop (>= 1.72.1, < 2.0)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-rake (0.7.1)
lint_roller (~> 1.1)
rubocop (>= 1.72.1)
ruby-progressbar (1.13.0)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
minitest (~> 5.25)
rake (~> 13.2)
rake-compiler (~> 1.2)
rake-compiler-dock (~> 1.9)
minitest (~> 6.0)
rake (~> 13.3)
rake-compiler (~> 1.3)
rake-compiler-dock (~> 1.11)
regorusrb!
rubocop (~> 1.73)
rubocop-minitest (~> 0.37.1)
rubocop (~> 1.86)
rubocop-minitest (~> 0.39.1)
rubocop-rake (~> 0.7.1)
BUNDLED WITH

View File

@@ -1,8 +1,9 @@
[package]
name = "regorusrb"
version = "0.9.0"
version = "0.9.1"
edition = "2024"
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
publish = false
[lib]
@@ -10,12 +11,14 @@ crate-type = ["cdylib"]
path = "src/lib.rs"
[features]
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
default = ["ast", "cache", "coverage", "regorus/std", "regorus/full-opa", "regorus/allocator-memory-limits"]
ast = ["regorus/ast"]
cache = ["regorus/cache"]
coverage = ["regorus/coverage"]
[dependencies]
magnus = { version = "0.7.1" }
magnus = { version = "0.8.2" }
regorus = { path = "../../../..", default-features = false, features = ["arc"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.140"
serde_magnus = "0.9.0"
serde_magnus = "0.11.0"

View File

@@ -1,10 +1,26 @@
use core::num::{NonZeroU32, NonZeroUsize};
use magnus::{Error, Ruby, exception::runtime_error, method, module, prelude::*};
use regorus::Engine as RegorusEngine;
use serde::Deserialize;
use std::cell::RefCell;
use std::cmp::Ordering;
// `Value` exists under magnus, regorus, and serde_json, so be explicit
#[derive(Deserialize)]
struct PolicyLengthSpec {
max_col: u32,
max_file_bytes: usize,
max_lines: usize,
}
#[cfg(feature = "cache")]
#[derive(Deserialize)]
struct CacheConfigSpec {
regex: usize,
glob: usize,
}
#[derive(Default)]
#[magnus::wrap(class = "Regorus::Engine")]
pub struct Engine {
@@ -55,15 +71,17 @@ impl Engine {
.map_err(|e| Error::new(runtime_error(), format!("Failed to add policy: {e}")))
}
fn add_data(&self, ruby_hash: magnus::RHash) -> Result<(), Error> {
let data_value: regorus::Value = serde_magnus::deserialize(ruby_hash).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to deserialize Ruby value: {e}"),
)
})?;
fn add_data(ruby: &Ruby, rb_self: &Self, ruby_hash: magnus::RHash) -> Result<(), Error> {
let data_value: regorus::Value =
serde_magnus::deserialize(ruby, ruby_hash).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to deserialize Ruby value: {e}"),
)
})?;
self.engine
rb_self
.engine
.borrow_mut()
.add_data(data_value)
.map_err(|e| Error::new(runtime_error(), format!("Failed to add data: {e}")))
@@ -111,15 +129,16 @@ impl Engine {
.map_err(|e| Error::new(runtime_error(), format!("Failed to get policies: {e}")))
}
fn set_input(&self, ruby_hash: magnus::RHash) -> Result<(), Error> {
let input_value: regorus::Value = serde_magnus::deserialize(ruby_hash).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to deserialize Ruby value: {e}"),
)
})?;
fn set_input(ruby: &Ruby, rb_self: &Self, ruby_hash: magnus::RHash) -> Result<(), Error> {
let input_value: regorus::Value =
serde_magnus::deserialize(ruby, ruby_hash).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to deserialize Ruby value: {e}"),
)
})?;
self.engine.borrow_mut().set_input(input_value);
rb_self.engine.borrow_mut().set_input(input_value);
Ok(())
}
@@ -142,14 +161,14 @@ impl Engine {
Ok(())
}
fn eval_query(&self, query: String) -> Result<magnus::Value, Error> {
let results = self
fn eval_query(ruby: &Ruby, rb_self: &Self, query: String) -> Result<magnus::Value, Error> {
let results = rb_self
.engine
.borrow_mut()
.eval_query(query, false)
.map_err(|e| Error::new(runtime_error(), format!("Failed to evaluate query: {e}")))?;
serde_magnus::serialize(&results).map_err(|e| {
serde_magnus::serialize(ruby, &results).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to serailzie query results: {e}"),
@@ -177,15 +196,19 @@ impl Engine {
})
}
fn eval_rule(&self, query: String) -> Result<Option<magnus::Value>, Error> {
fn eval_rule(
ruby: &Ruby,
rb_self: &Self,
query: String,
) -> Result<Option<magnus::Value>, Error> {
let result =
self.engine.borrow_mut().eval_rule(query).map_err(|e| {
rb_self.engine.borrow_mut().eval_rule(query).map_err(|e| {
Error::new(runtime_error(), format!("Failed to evaluate rule: {e}"))
})?;
match result {
regorus::Value::Undefined => Ok(None), // Convert undefined to Ruby's nil
_ => serde_magnus::serialize(&result) // Serialize other results normally
regorus::Value::Undefined => Ok(None),
_ => serde_magnus::serialize(ruby, &result)
.map(Some)
.map_err(|e| {
magnus::Error::new(
@@ -280,6 +303,34 @@ impl Engine {
})
}
fn set_policy_length_config(
ruby: &Ruby,
rb_self: &Self,
hash: magnus::RHash,
) -> Result<(), Error> {
let spec: PolicyLengthSpec = serde_magnus::deserialize(ruby, hash).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to deserialize policy length config: {e}"),
)
})?;
let config = regorus::PolicyLengthConfig {
max_col: NonZeroU32::new(spec.max_col)
.ok_or_else(|| Error::new(runtime_error(), "max_col must be non-zero"))?,
max_file_bytes: NonZeroUsize::new(spec.max_file_bytes)
.ok_or_else(|| Error::new(runtime_error(), "max_file_bytes must be non-zero"))?,
max_lines: NonZeroUsize::new(spec.max_lines)
.ok_or_else(|| Error::new(runtime_error(), "max_lines must be non-zero"))?,
};
rb_self.engine.borrow_mut().set_policy_length_config(config);
Ok(())
}
fn clear_policy_length_config(&self) -> Result<(), Error> {
self.engine.borrow_mut().clear_policy_length_config();
Ok(())
}
#[cfg(feature = "ast")]
fn get_ast_as_json(&self) -> Result<String, Error> {
self.engine
@@ -361,7 +412,47 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
engine_class.define_method("set_gather_prints", method!(Engine::set_gather_prints, 1))?;
engine_class.define_method("take_prints", method!(Engine::take_prints, 0))?;
// policy length limits
engine_class.define_method(
"set_policy_length_config",
method!(Engine::set_policy_length_config, 1),
)?;
engine_class.define_method(
"clear_policy_length_config",
method!(Engine::clear_policy_length_config, 0),
)?;
// ast
engine_class.define_method("get_ast_as_json", method!(Engine::get_ast_as_json, 0))?;
// cache configuration (module-level)
#[cfg(feature = "cache")]
{
regorus_module
.define_module_function("set_cache_config", magnus::function!(set_cache_config, 1))?;
regorus_module.define_module_function("clear_cache", magnus::function!(clear_cache, 0))?;
}
Ok(())
}
#[cfg(feature = "cache")]
fn set_cache_config(ruby: &Ruby, hash: magnus::RHash) -> Result<(), Error> {
let spec: CacheConfigSpec = serde_magnus::deserialize(ruby, hash).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to deserialize cache config: {e}"),
)
})?;
regorus::cache::configure(regorus::cache::Config {
regex: spec.regex,
glob: spec.glob,
});
Ok(())
}
#[cfg(feature = "cache")]
fn clear_cache() -> Result<(), Error> {
regorus::cache::clear();
Ok(())
}

View File

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

View File

@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
spec.summary = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
spec.homepage = "https://github.com/microsoft/regorus/blob/main/bindings/ruby"
spec.license = "MIT"
spec.licenses = ["MIT", "Apache-2.0", "BSD-3-Clause"]
spec.required_ruby_version = ">= 3.4.2"
spec.required_rubygems_version = ">= 3.6.5"

View File

@@ -183,6 +183,16 @@ class TestRegorus < Minitest::Test
assert_equal ["<query.rego>:1: Hello"], @engine.take_prints
end
def test_set_policy_length_config
@engine.set_policy_length_config({ max_col: 2000, max_file_bytes: 1048576, max_lines: 20000 })
@engine.clear_policy_length_config
end
def test_set_cache_config
::Regorus.set_cache_config({ regex: 256, glob: 128 })
::Regorus.clear_cache
end
def alice_results
{
result: [

532
bindings/wasm/Cargo.lock generated
View File

@@ -25,6 +25,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -36,9 +42,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.100"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "async-trait"
@@ -57,22 +63,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"serde",
"unty",
]
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -90,9 +80,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "borrow-or-share"
@@ -112,9 +102,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.19.1"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytecount"
@@ -130,9 +120,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.55"
version = "1.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -145,10 +135,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.43"
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core",
]
[[package]]
name = "chrono"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -167,12 +168,30 @@ dependencies = [
"phf",
]
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "data-encoding"
version = "2.10.0"
@@ -199,6 +218,18 @@ dependencies = [
"serde",
]
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -207,9 +238,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fancy-regex"
version = "0.14.0"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
dependencies = [
"bit-set",
"regex-automata",
@@ -224,15 +255,27 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fluent-uri"
version = "0.3.2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5"
checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e"
dependencies = [
"borrow-or-share",
"ref-cast",
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -254,26 +297,25 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.31"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-task"
version = "0.3.31"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.31"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
@@ -299,11 +341,27 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"r-efi 5.3.0",
"wasip2",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi 6.0.0",
"rand_core",
"wasip2",
"wasip3",
"wasm-bindgen",
]
[[package]]
name = "globset"
version = "0.4.18"
@@ -316,11 +374,31 @@ dependencies = [
"regex-syntax",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
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"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "iana-time-zone"
@@ -427,6 +505,12 @@ 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"
@@ -455,56 +539,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "ipnet"
version = "2.11.0"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "itoa"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.85"
version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "jsonschema"
version = "0.30.0"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b46a0365a611fbf1d2143104dcf910aada96fafd295bab16c60b802bf6fa1d"
checksum = "6f29616f6e19415398eb186964fb7cbbeef572c79bede3622a8277667924bbe3"
dependencies = [
"ahash",
"base64",
"bytecount",
"data-encoding",
"email_address",
"fancy-regex",
"fraction",
"getrandom 0.3.4",
"idna",
"itoa",
"num-cmp",
"num-traits",
"once_cell",
"percent-encoding",
"referencing",
"regex",
"regex-syntax",
"serde",
"serde_json",
"unicode-general-category",
"uuid-simd",
]
@@ -515,10 +602,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.180"
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
[[package]]
name = "libm"
@@ -548,10 +641,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.7.6"
name = "lru"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "minicov"
@@ -663,9 +762,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.21.3"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "oorandom"
@@ -728,15 +827,21 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pin-utils"
version = "0.1.0"
name = "postcard"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"serde",
]
[[package]]
name = "potential_utf"
@@ -748,12 +853,13 @@ dependencies = [
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"zerocopy",
"proc-macro2",
"syn",
]
[[package]]
@@ -767,9 +873,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.44"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
@@ -781,33 +887,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand_chacha"
version = "0.9.0"
name = "rand"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
dependencies = [
"ppv-lite86",
"chacha20",
"getrandom 0.4.2",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.5"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
name = "redox_syscall"
@@ -840,13 +940,14 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.30.0"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8eff4fa778b5c2a57e85c5f2fe3a709c52f0e60d23146e2151cbef5893f420e"
checksum = "b8a618c14f8ba29d8193bb55e2bf13e4fb2b1115313ecb7ae94b43100c7ac7d5"
dependencies = [
"ahash",
"fluent-uri",
"once_cell",
"getrandom 0.3.4",
"hashbrown 0.16.1",
"parking_lot",
"percent-encoding",
"serde_json",
@@ -854,9 +955,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.2"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
@@ -866,9 +967,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
@@ -877,16 +978,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.8"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "regorus"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"anyhow",
"bincode",
"chrono",
"chrono-tz",
"data-encoding",
@@ -895,9 +995,12 @@ dependencies = [
"ipnet",
"jsonschema",
"lazy_static",
"lru",
"msvc_spectre_libs",
"num-bigint",
"num-traits",
"parking_lot",
"postcard",
"rand",
"regex",
"semver",
@@ -912,12 +1015,14 @@ dependencies = [
[[package]]
name = "regorusjs"
version = "0.9.0"
version = "0.9.1"
dependencies = [
"getrandom 0.2.17",
"getrandom 0.3.4",
"getrandom 0.4.2",
"regorus",
"serde",
"serde-wasm-bindgen",
"serde_json",
"uuid",
"wasm-bindgen",
@@ -932,9 +1037,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "same-file"
@@ -967,6 +1072,17 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_core"
version = "1.0.228"
@@ -1027,9 +1143,9 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "slab"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
@@ -1039,9 +1155,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "spin"
version = "0.9.8"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
[[package]]
name = "stable_deref_trait"
@@ -1051,9 +1167,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.114"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@@ -1102,10 +1218,22 @@ dependencies = [
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
name = "unicode-general-category"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
[[package]]
name = "unicode-ident"
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"
@@ -1113,12 +1241,6 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "url"
version = "2.5.8"
@@ -1139,11 +1261,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.20.0"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
dependencies = [
"getrandom 0.3.4",
"getrandom 0.4.2",
"js-sys",
"rand",
"wasm-bindgen",
@@ -1156,7 +1278,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8"
dependencies = [
"outref",
"uuid",
"vsimd",
]
@@ -1198,10 +1319,19 @@ dependencies = [
]
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
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 = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
dependencies = [
"cfg-if",
"once_cell",
@@ -1212,23 +1342,19 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.58"
version = "0.4.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f"
checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e"
dependencies = [
"cfg-if",
"futures-util",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.108"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1236,9 +1362,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.108"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1249,18 +1375,18 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.108"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-bindgen-test"
version = "0.3.58"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45649196a53b0b7a15101d845d44d2dda7374fc1b5b5e2bbf58b7577ff4b346d"
checksum = "941c102b3f0c15b6d72a53205e09e6646aafcf2991e18412cc331dbac1806bc0"
dependencies = [
"async-trait",
"cast",
@@ -1280,9 +1406,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.58"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f579cdd0123ac74b94e1a4a72bd963cf30ebac343f2df347da0b8df24cdebed2"
checksum = "a26bd6570f39bb1440fd8f01b63461faaf2a3f6078a508e4e54efa99363108d2"
dependencies = [
"proc-macro2",
"quote",
@@ -1291,18 +1417,42 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-shared"
version = "0.2.108"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8145dd1593bf0fb137dbfa85b8be79ec560a447298955877804640e40c2d6ea"
checksum = "1c29582b14d5bf030b02fa232b9b57faf2afc322d2c61964dd80bad02bf76207"
[[package]]
name = "web-sys"
version = "0.3.85"
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"js-sys",
"wasm-bindgen",
"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]]
@@ -1387,6 +1537,88 @@ 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-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"
@@ -1419,18 +1651,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.36"
version = "0.8.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dafd85c832c1b68bbb4ec0c72c7f6f4fc5179627d2bc7c26b30e4c0cc11e76cc"
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.36"
version = "0.8.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cb7e4e8436d9db52fbd6625dbf2f45243ab84994a72882ec8227b99e72b439a"
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
dependencies = [
"proc-macro2",
"quote",
@@ -1493,6 +1725,6 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.17"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

View File

@@ -2,10 +2,11 @@
[package]
name = "regorusjs"
version = "0.9.0"
version = "0.9.1"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/wasm"
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -31,9 +32,11 @@ default = [
"regorus/time",
"regorus/uuid",
"regorus/urlquery",
"regorus/yaml"
"regorus/yaml",
"cache"
]
ast = ["regorus/ast"]
cache = ["regorus/cache"]
coverage = ["regorus/coverage"]
[dependencies]
@@ -41,15 +44,18 @@ regorus = { path = "../..", default-features = false, features = ["arc", "rvm"]
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
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
# when targeting wasm32-unknown-unknown.
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng", "js"]}
# Enable wasm_js. See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
getrandom_for_jsonschema = { package = "getrandom", version = "0.2.15", features = ["std", "js"] }
getrandom = { version = "0.3.1", features = ["std", "wasm_js"] }
uuid = { version = "1.22.0", default-features = false, features = ["v4", "fast-rng", "js"]}
# Configure getrandom for WebAssembly: 0.2 uses the `js` feature, while 0.3+ use `wasm_js`.
# See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
getrandom02 = { package = "getrandom", version = "0.2.15", features = ["std", "js"] }
getrandom03 = { package = "getrandom", version = "0.3.1", features = ["std", "wasm_js"] }
getrandom = { version = "0.4.2", features = ["wasm_js"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.40"
wasm-bindgen-test = "0.3.67"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }

View File

@@ -3,6 +3,7 @@
#![allow(non_snake_case)]
use core::num::{NonZeroU32, NonZeroUsize};
use regorus::languages::rego::compiler::Compiler;
use regorus::rvm::program::{
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
@@ -26,6 +27,42 @@ struct ModuleSpec {
content: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct PolicyLengthSpec {
max_col: u32,
max_file_bytes: usize,
max_lines: usize,
}
#[cfg(feature = "cache")]
#[derive(Deserialize)]
struct CacheConfigSpec {
regex: usize,
glob: usize,
}
/// Configure the global pattern caches used by regex and glob builtins.
///
/// Accepts a JS object: `{ regex, glob }`.
#[cfg(feature = "cache")]
#[wasm_bindgen(js_name = "setCacheConfig")]
pub fn set_cache_config(config: JsValue) -> Result<(), JsValue> {
let spec: CacheConfigSpec = serde_wasm_bindgen::from_value(config).map_err(error_to_jsvalue)?;
regorus::cache::configure(regorus::cache::Config {
regex: spec.regex,
glob: spec.glob,
});
Ok(())
}
/// Clear all entries from every pattern cache.
#[cfg(feature = "cache")]
#[wasm_bindgen(js_name = "clearCache")]
pub fn clear_cache() {
regorus::cache::clear();
}
#[wasm_bindgen]
pub struct Program {
program: Arc<RvmProgram>,
@@ -183,6 +220,29 @@ impl Engine {
self.engine.set_gather_prints(b)
}
/// Set the policy length limits used when loading policies.
///
/// Accepts a JS object: `{ maxCol, maxFileBytes, maxLines }`.
pub fn setPolicyLengthConfig(&mut self, config: JsValue) -> Result<(), JsValue> {
let spec: PolicyLengthSpec =
serde_wasm_bindgen::from_value(config).map_err(error_to_jsvalue)?;
self.engine
.set_policy_length_config(regorus::PolicyLengthConfig {
max_col: NonZeroU32::new(spec.max_col)
.ok_or_else(|| JsValue::from_str("maxCol must be non-zero"))?,
max_file_bytes: NonZeroUsize::new(spec.max_file_bytes)
.ok_or_else(|| JsValue::from_str("maxFileBytes must be non-zero"))?,
max_lines: NonZeroUsize::new(spec.max_lines)
.ok_or_else(|| JsValue::from_str("maxLines must be non-zero"))?,
});
Ok(())
}
/// Clear the policy length configuration, reverting to defaults.
pub fn clearPolicyLengthConfig(&mut self) {
self.engine.clear_policy_length_config();
}
/// Take the gathered output of print statements.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints

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