Compare commits

...

339 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
Anand Krishnamoorthi
0e9e34a519 chore: Gate exports using allocator-limits feature (#564)
This is needed to publish regorus-mimalloc

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-30 23:56:29 +05:30
Anand Krishnamoorthi
3f7a5496dc feat(bindings)!: add RVM/Program support across FFI and language bindings (#565)
- FFI: add RVM/Program APIs, execution state accessors, HostAwait handling, and buffer/result helpers in rvm.rs, common.rs, engine.rs.
- Compiler: emit HostAwait for __builtin_host_await in function_calls.rs.
- RVM tests: add HostAwait regression cases and extend harness for suspend/resume responses in host_await.yaml and mod.rs.
- C/C++: add RVM tests/examples and wrapper updates in rvm_tests.c, rvm_tests.cpp, regorus.hpp, plus CMake wiring.
- C#: add Program/Rvm bindings, SafeHandle/PInvoke, tests, and example usage in Regorus, RvmProgramTests.cs, Program.cs, and README updates.
- Go: add Program/Rvm bindings, tests, and examples in rvm.go, rvm_test.go, main.go.
- Java: add Program/Rvm bindings, JNI glue, and examples in lib.rs, regorus, Test.java.
- Python: add Program/Rvm bindings and examples in lib.rs, test.py.
- WASM: add Program/Rvm bindings and examples in lib.rs, test.js.
- Tooling: wire binding tests in xtask and ignore generated Java artifacts in .gitignore.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-30 23:55:31 +05:30
Anand Krishnamoorthi
0316ccd90c chore(release): publish vendored mimalloc crates (#563)
- Prefix regorus- to mimalloc crates and add MIT licenses
- alias dependencies to avoid code changes
- add versions and release-plz publish entries
- update Cargo.lock files for new crate names

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-30 09:03:02 +05:30
Anand Krishnamoorthi
10eebfe54c test(rvm): Move vm execution limit tests to a separate test to avoid flakiness (#558)
Having a separate integration test allows the execution tests to freely
change the global fallback limits without affecting other tests.

also ask release-plz to ignore xtask package

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-29 04:15:33 +05:30
Anand Krishnamoorthi
e688806ca0 chore: Keep regorus and binding versions in sync (#552)
Bump up the versions to 0.9.0 to match the C# binding version.

Also use central version management for C# projects

Also fix clippy lint errors

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-28 05:58:34 +05:30
Anand Krishnamoorthi
394625d4bc feat!: add cooperative execution-time limits across engine, VM, and binding (#539)
- Introduce ExecutionTimer/ExecutionTimerConfig to allow limiting evaluating time.
- To amortize time checking costs, checking interval can be configured via the notion of work units
- A global fallback time limit can be set to universally limit all evaluation in addition to engine level limit setting.
- Implement limnits in interpreter and RVM. In RVM, also handle suspend/resume so that time during pause is not counted.
- Add engine-level APIs to set/clear per-engine timer configuration and apply global fallback defaults.
- Surface execution-time limits through FFI and C# bindings
- Add C# tests and example usage to validate engine overrides, global fallback behavior, and compiled policy enforcement.
- Expand docs for execution-time limit
- Add interpreter YAML cases and VM unit tests for time-limit behavior and deterministic time sources.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-28 05:58:03 +05:30
Anand Krishnamoorthi
e68e852ee3 feat(xtask): consolidate CI workflows onto xtask helpers (#542)
- split the xtask crate into structured modules for
  - bindings
  - ci
  - dev
  - util
  - no-std
- Adding commands for
  - ci-release/ci-debug
  - MUSL/no-std
  - per- binding language smoke tests
  - developer tasks (fmt, clippy, pre-commit, pre-push)
- refresh Cargo manifests/locks, binding readmes, and shared FFI helpers so every binding reuses the same preparation steps
- refactor GitHub Actions (release/debug, extensions, CodeQL, clippy, bindings) to call the new xtask commands
- Use rust-cache in ci workflows (microsoft qdk also does this)
- extend README with a contributor workflow section describing how xtask mirrors CI expectations
- update pre-commit and pre-push hooks to use the xtask dev commands

WORKAROUND:
When dotnet is run from an xtask, codeql tracer intercepts it an routes to a nonexistent binary.
Therefore in codeql workflow, xtask is not used for c# and instead dotnet is directly invoked.
Tracked by #545

closes #475

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-27 07:42:21 +05:30
Anand Krishnamoorthi
2b1434b3ac fix(release): Fix release-plz dirty-tree errors from Ruby binstubs (#547)
Ruby binstubs were checked in, but ignore via .gitignore **bin pattern.
This causes release-plz to think that the source tree is dirty.

The binstubs are deleted from source repo since they are always regenerated by bundler.
Also simplizy release-plz to focus only on the core crate.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-24 07:09:25 +05:30
Anand Krishnamoorthi
fd59bb5a91 feat(memory): Allocator-backed global memory limits (#544)
Policy evaluation at scale needs to be able to set memory limits
so that a bad policy does not hog memory or to ensure that
policy evaluation itself does not use too much memory which could
cause other components to suffer.

This PR introduces capability to set and enforce global memory limits.
It also lays the groundwork for enabling per evaluation limits in future.

Once a global memory limit is set, Regorus maintains per thread counters
to track memory activity (allocation, deallocation) of a thread.
These counters are periodically flushed to global memory counters.
Per thread counters avoid the contention that updating global counters
on each alloc/free would cause.

Policy evaluation periodically checks these counters and raises errors
if allocated memory has exceeded the configured limit.

Currently memory limit capability is exposed only to FFI and C#.

Also update mimalloc to v2.2.6

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-24 07:08:54 +05:30
Anand Krishnamoorthi
80686d6ed1 feat(ffi): unwind safety: shield FFI entrypoints with panic guard (#546)
This PR implements widely accepted Rust programming practices for
dealing with panics across ABI (programming language) boundaries.

- Add panic_guard.rs to wrap FFI calls and prevent panic across FFI/ABI boundary (undefined behavior).
- Capture per-thread backtraces via a temporary panic hook
- After a panic, subsequent invocations are poisoned.
- Integrate with_unwind_guard across the engine, schema registry, and target registry exportis

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-21 09:20:10 +05:30
Anand Krishnamoorthi
9426b2ec02 fix: Imports without a name binding (#543)
Handle imports that don't use the `as` clause to create a binding.
These imports are bound to the last identifier in the imported path.

Fix both interpreter and compiler.
Add tests.

fixes #541

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-15 03:57:52 +05:30
Anand Krishnamoorthi
740db8a0f5 chore: Harden RVM implementation (#537)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-14 06:07:31 +05:30
Ben Beasley
d626f75421 Include all licenses in SPDX expression and LICENSE file (#540)
Change the SPDX license expression from `MIT` to
`MIT AND Apache-2.0 AND BSD-3-Clause` to reflect all of the licenses
that apply to the crate’s sources.

Add license text for `Apache-2.0` and the `BSD-3-Clause` license from
Go’s `time` module to `LICENSE`. Like `MIT`, both of these licenses
require the license text to be distributed with source and/or binaries.
2026-01-10 18:31:49 +05:30
Anand Krishnamoorthi
5afbd96159 chore: Interpreter hardening (#536)
Fix majority of the interpreter lint errors/warnings

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-30 18:21:09 -06:00
Anand Krishnamoorthi
28891ef883 chore: Harden instructions and program (#535)
Also enforce sane limits in program

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-30 18:20:07 -06:00
Anand Krishnamoorthi
49958c2ece chore: Make clippy clean and harden helpers (#532)
- Promote common accessors (Expr/Rule span/eidx, ScopeContext constructors, Engine::set_rego_v0) to const
- Prefer Option combinators (map_or, then_some) and map_or_else
- Tighten engine logic: add missing semicolons, use checked u32::try_from, make boolean query evaluation avoid unchecked indexing,

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-29 14:47:09 -06:00
Anand Krishnamoorthi
08a5e00960 chore: Harden lexer bounds and span handling (#531)
- Document arithmetic safety assumptions and add explicit lexer limits for columns, file size (1 MiB), and line count.
  Realistic policies will be well within these bounds.
- Use checked arithmetic to prevent overflow underflow.
- Avoid var name shadowing.
- Misc clippy lints

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-29 14:46:05 -06:00
Anand Krishnamoorthi
1d71df30b6 chore: Fix lint errors in lookup.rs (#530)
- Made the lookup module crate-visible to address clippy’s redundant visibility lint.
- Replaced unchecked as casts with a fallible usize_from_u32 helper and propagate conversion errors in lookup accessors.
- Switched LookupIndexError to implement core::error::Error for no_std correctness.
- Fixed the pattern type mismatch by matching on the value in the Display impl.
- Promoted trivial helpers to const fn (new, module_len) per clippy suggestions.
- Centralized bounds-checked slot access via slot_ref/slot_mut to keep getters/clearers lint-clean and avoid unchecked indexing.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-29 13:53:46 -06:00
Anand Krishnamoorthi
249dcd0b43 chore: Add clippy lints (#529)
Lints are added (deny) at crate level.

In each offending file, the failing lints are explicitly allowed.
Each file will be fixed in subsequent PRs.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-23 15:59:34 -06:00
Anand Krishnamoorthi
604591a0f7 Merge pull request #527 from anakrish/checked-indexing
Checked indexing
2025-12-19 13:24:55 -06:00
Anand Krishnamoorthi
dbfb8e38a8 fix: Skip reordering in truncated queries.
In case all the statements of a query don't execute,
skip reordering the result expressions to match the
source order. Doing so requires maintaining additional
data structures not worth the complexity for now.

Additionally we want to discourage queries and encourage
evaluating rules. Queries are inherently less performant
than rules which can be precompiled.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-19 12:08:09 -06:00
Anand Krishnamoorthi
273a80571e fix: apply expression ordering to schedule in a safe way
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-19 11:33:25 -06:00
Anand Krishnamoorthi
3f29eb2fa6 fix: Create ordered statements in a safe way
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-19 11:33:25 -06:00
Anand Krishnamoorthi
889a02ddd6 fix: Avoid unwrap when accesssing current module
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-19 11:33:24 -06:00
Anand Krishnamoorthi
70f63a0982 fix: Avoid unrap/expect in context management
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-19 11:33:24 -06:00
Anand Krishnamoorthi
6bc1249dc8 feat: Safeguard lookup use
Detect invalid indexes and raise internal errors.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-19 11:33:24 -06:00
Anand Krishnamoorthi
5d0cf95332 feat: add recursion limit to parser
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-19 11:33:24 -06:00
Anand Krishnamoorthi
fd4bb3081f feat: Safeguard against panics in parser
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-19 11:33:24 -06:00
Anand Krishnamoorthi
93a633750c feat: Guard against runtime panics in lexer
Add guardrails for operations to ensure that they
won't panic at runtime.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-19 11:33:24 -06:00
Anand Krishnamoorthi
52b56f4214 Merge pull request #526 from anakrish/quick-fixes
Quick fixes
2025-12-17 16:56:06 -06:00
Anand Krishnamoorthi
8b84d4ce12 Merge pull request #525 from tjons/tjons/feat-implement-net-cidr-expand
feat: implement `net.cidr_expand` builtin
2025-12-17 13:16:52 -06:00
Anand Krishnamoorthi
ecf95833f9 Merge pull request #519 from anakrish/opa-rvm-3
Opa rvm 3
2025-12-16 14:11:08 -06:00
Anand Krishnamoorthi
9fa8036ce4 feat: Implement Rego else block compilation
- teach the Rego compiler to compile else chains correctly
- test suite

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-16 12:03:03 -06:00
Anand Krishnamoorthi
a232b13e50 feat: Else blocks in definitions
- ensure both run-to-completion and suspendable rule execution stop evaluating
  bodies once one succeeds so later else branches are skipped
- test cases

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-16 12:03:03 -06:00
Anand Krishnamoorthi
e9a50bcfd5 test: skip test case which queries rule suffixes
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-16 12:03:02 -06:00
Anand Krishnamoorthi
d0fa639bb8 feat: Reject with keyword usage
RVM does not plan to support the `with` keyword which is mainly used
for testing.

- introduce CompilerError::WithKeywordUnsupported and fail query compilation
  when any literal carries with_mods
- skip OPA test cases that hit the error

The "withkeyword" folder is retained in the TODO list to indicate its
lack of support.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-16 12:03:02 -06:00
Anand Krishnamoorthi
a514e8da83 fix: Implement RVM set ops correctly
- treat set subtraction in RVM the same as the interpreter by supporting
  Value::Set operands in sub_values
- emit internal-only builtin names for set union/intersection and register
  handlers so compiled bytecode resolves without exposing new Rego builtins
- add regression coverage for literal set difference/intersection
  (x/y from failure.rego) in tests/rvm/rego/cases/sets.yaml

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-16 12:03:02 -06:00
Anand Krishnamoorthi
252ae0e312 Merge pull request #516 from anakrish/rvm-opa-2
Handle more OPA semantics in RVM and compiler
2025-12-16 11:28:36 -06:00
Anand Krishnamoorthi
ce85e0102d fix: use debug build in pre-commit hook
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-16 09:55:20 -06:00
Anand Krishnamoorthi
a8f5ac6117 fix: Run pre-commit tests in debug mode
Release mode uses LTO optimization for binaries.
This can take up a lot of time especially for doc tests which
create a separate binary for each test.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-16 09:43:57 -06:00
Anand Krishnamoorthi
632f64b2ce fix: remove non-existent feature use in git push hook
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-16 09:36:24 -06:00
tjons
c41f289b19 feat: implement net.cidr_expand builtin
Signed-off-by: tjons <tylerschade99@gmail.com>
2025-12-16 06:05:32 -05:00
Anand Krishnamoorthi
2a75b3b0b6 Merge pull request #524 from hkrutzer/fix-underflow
fix: Integer underflow in lexer error message formatting
2025-12-15 14:37:11 -06:00
Hans Krutzer
3962b3c38d fix: Integer underflow in lexer error message formatting 2025-12-15 20:00:45 +01:00
Anand Krishnamoorthi
d4b7d1ff6c Merge pull request #522 from microsoft/dependabot/cargo/criterion-0.8.1
build(deps): bump criterion from 0.7.0 to 0.8.1
2025-12-10 06:38:27 -06:00
Anand Krishnamoorthi
d6cd738822 Merge pull request #523 from dpokluda/jetbrains-gitignore-support
Add support for JetBrains IDEs gitignore
2025-12-08 17:13:56 -06:00
David Pokluda
36e75d3e49 Add support for JetBrains IDEs gitignore 2025-12-08 14:17:20 -08:00
dependabot[bot]
befe131048 build(deps): bump criterion from 0.7.0 to 0.8.1
Bumps [criterion](https://github.com/criterion-rs/criterion.rs) from 0.7.0 to 0.8.1.
- [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-plot-v0.7.0...criterion-v0.8.1)

---
updated-dependencies:
- dependency-name: criterion
  dependency-version: 0.8.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 02:07:45 +00:00
Anand Krishnamoorthi
bedf667adc feat: Handle literal comparisons that use = and comprehensions without loops
- emit AssertCondition for equality-only assignment plans (outside soft-assert mode) so rules like `0 = 1` fail under the VM just like the interpreter
- let comprehension bodies consume assertion failures by advancing or exiting their iteration context, both in run-to-completion and suspendable execution

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-03 13:34:14 -06:00
Anand Krishnamoorthi
8269968c4a feat: Handle computed reference roots in RVM compiler
Allow compile_chained_ref to fall back to “evaluate root expression → chain access”
so literal arrays, comprehensions, and other computed roots no longer raise NotSimpleReferenceChain.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-03 13:34:14 -06:00
Anand Krishnamoorthi
e3d23766ae feat: Ensure RVM caches deterministic builtins
Mirror interpreter implementation:
- use builtins::must_cache to determine whether builtin must be cached.
- reuse cached value when applicable
- clear the VM’s builtin cache whenever execution state resets to avoid leaking values across runs
- add a YAML regression for rand.intn set comprehensions and re-enable the rand cases in the OPA test suite

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-03 13:34:14 -06:00
Anand Krishnamoorthi
1d627f3798 Merge pull request #515 from anakrish/rvm-opa-1
feat: Handle more OPA semantics in RVM compiler
2025-12-03 13:33:04 -06:00
Anand Krishnamoorthi
b7b3d3ec87 feat: Soft-assert mode for builtin out-params under not
- Add a scoped soft_assert_mode to the compiler so `not` statements compile their subexpressions without emitting hard AssertCondition/AssertNotUndefined instructions.
- Teach binding-plan application to return an optional result register; equality plans now yield a boolean in soft mode, allowing not abs(-5 , 3) to succeed instead of aborting.
- Update function-call, loop, and rule plumbing to consume the new binding-plan outcome, including copying the produced register when an out-parameter equality is used.
- Trim the OPA TODO list to the remaining troublesome folders.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-02 15:27:43 -06:00
Anand Krishnamoorthi
30bd134a0b fix: Handle builtin out-parameter calls in RVM compiler
- Teach the hoister/destructuring planner to respect parent scope when building binding plans for extra arguments, so already-bound vars yield equality checks.
- Update the compiler’s function-call path to drop the trailing out-argument, run its binding plan after the call, and share call-target resolution logic.
- Add regression suites for builtin and user-defined out-parameter scenarios plus align the CLI example output when RVM returns undefined.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-02 13:29:12 -06:00
Anand Krishnamoorthi
5aefd51cb6 feat: Add span information to compiler errors
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-02 13:27:38 -06:00
Anand Krishnamoorthi
e060e43a6c test: OPA RVM validation (#514)
- fixes:
  - ensure loop hoist lookups reserve query capacity and keep loop-var tables sized when compiling default rules
  - rebuild hoisting tables with the analyzer’s schedule when available so statement order matches evaluation

- OPA test
  - Also test using RVM workflow in OPA suite
  - Maintain a list of test folders that don't yet pass and skip them

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-02 13:26:38 -06:00
Anand Krishnamoorthi
12c083e29e test: Add RVM compiler testing to ACI tests (#509)
code fixes:
  - compiler: add `is_var_bound_in_current_scope` and use it in destructuring so
    only the innermost scope blocks rebinding while still catching duplicates
    within that block.
  - rvm: treat `not` over undefined operands as a successful negation to match
    interpreter semantics.

tests/aci:
   migrate YAML cases to `data.policy.rule` queries with `{x: …}`
  bindings, expand the harness to run interpreter plus RVM (with optional
  skipping), align results to the binding format, add readable timing output,
  and support a `--filter` flag for targeting cases.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-01 16:58:44 -06:00
Anand Krishnamoorthi
a8a3a9809b feat!: Use num-bigint for large numbers (#500)
- Supply chain: Use the popular num-bigint crate for handling large integers
- Optimization: Handle f64, i64, u64 directly. These will be the most common instances of a number.

OPA number semantics isn't clear.
https://github.com/open-policy-agent/opa/issues/6281

As part of this change, we update the following failing tests:
- A local test that relies on what 15.3/3 evaluates to.
 With our current change, we round in a different direction than what OPA does, but consistent
 with Rust. We produce 5.1000000000000005 where as the OPA test expects 5.1.
 There is no clear definition in Rego of what the right answer is. Moreover, policies should not
 rely on exact floating point value comparison. Therefore this deviations is justified.
 The test is patched to pass.
- Another local vm test that exercised 1.1 + 2.2
- Another local vm test that exercises 5.5 - 2.2
- An OPA test that expects that a large integer number say 10e308 is printed in exponent notation.
 num-bigint does not print using scientific notation and instead prints all the digits.
 The benefit of preserving this compatibility is not clear. We skip this test.
- Doc tests that exercised handling floating point numbers with more than 15 (what f64 supports)
  digits of precision. There is no usecase for this scenario. The tests are updated to reflect
  the behavior.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-12-01 13:25:02 -06:00
Copilot
14deaaa5b6 Re-enable wasm-pack test after upstream issue fix (#508)
* Initial plan

* Re-enable wasm-pack test and update Node.js to v22

Co-authored-by: anakrish <35780660+anakrish@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: anakrish <35780660+anakrish@users.noreply.github.com>
2025-11-25 12:03:16 -06:00
dependabot[bot]
ed360879a6 build(deps): bump prettydiff from 0.8.1 to 0.9.0 (#502)
Bumps [prettydiff](https://github.com/romankoblov/prettydiff) from 0.8.1 to 0.9.0.
- [Release notes](https://github.com/romankoblov/prettydiff/releases)
- [Changelog](https://github.com/oli-obk/prettydiff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/romankoblov/prettydiff/commits/0.9.0)

---
updated-dependencies:
- dependency-name: prettydiff
  dependency-version: 0.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 15:02:35 -06:00
dependabot[bot]
4988bda647 build(deps): bump indexmap from 2.12.0 to 2.12.1 (#503)
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.12.0 to 2.12.1.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/main/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.12.0...2.12.1)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-version: 2.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 12:16:12 -06:00
dependabot[bot]
92b9ec8fa8 build(deps): bump clap from 4.5.52 to 4.5.53 (#505)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.52 to 4.5.53.
- [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.52...clap_complete-v4.5.53)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.53
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 12:15:17 -06:00
Anand Krishnamoorthi
a3a20a1235 feat!: Rego -> RVM Compiler and extensive testsuite (#506)
# RVM compiler test cases

Coverage:
- arithmetic
- arrays
- chained lookups
- comparisons
- comprehensions
- default rules
- destructuring
- function rules
- loops/quantifiers
- multiple entrypoints
- objects/sets
- variables
- negative/edge scenarios such as data/rule conflicts
- virtual data lookups
- etc

 # Modify interpreter and compiled policy for RVM Compilation

- Interpreter::eval_default_rule_for_compiler:
   evaluates a named default rule in isolation - allows compiler to emit a constant value instead of instructions
   for the default value

#  feat: Rego Compiler Scaffolding

- Introduce the rego::compiler module surface and entry point wiring
- Add the core compiler concepts:
  - register allocator
  - scope tracking
  - literal/builtin tables
  - rule worklists
  - instruction emit helpers
  - compiler-specific error types
  - context structs for rules, comprehensions, and loops to support later lowering passes.

# feat: Compile Rules/Queries

- add compiler::compile_from_policy workflow plus rule worklist, entry-point wiring, and recursion checks
- implement query lowering:
  - scheduling-aware statement ordering
  - loop hoisting
  - “every/some” semantics
  - context yields
  -  literal assertions
- finalize Program construction

# feat: Expression Lowering

- add compile_rego_expr and helpers to translate every AST expression into RVM instructions,
- interop with binding plans, comprehensions, and membership checks.
- implement collection literal builders (ArrayCreate, SetCreate, ObjectCreate)
  - dedupe literal keys and handle mixed literal/dynamic fields via instruction data blocks.
- operations:
  - arithmetic/boolean/bin operators
  - membership
  - unary minus
  - set unions/intersections
  - etc
- user-defined and builtin function calls
- reference handling
  - analyse chained refs
  - distinguishe data/input/local roots
  - perform rule dispatch or virtual document lookups
  - emits optimized Index/ChainedIndex instructions.

# feat: Comprehensions & Loops

- shared comprehension emitter
 - wraps array/set/object comprehensions with ComprehensionBegin/End
 - context management
- loop lowering utilities
 - read hoisting metadata
 - emit LoopStart/LoopNext
 - some in lowering
 - every quantifiers
 - index iteration
 - propagate binding plans into stored registers so downstream statements see bound variables.

# feat: Destructuring Lowering

- destructuring planner integration
 - assignment/parameter/loop bindings use hoisted plans instead of re-walking ASTs.
- handle :=, =, wildcard matches, and equality
 - evaluate RHS
 - applying destructuring plans
 - emit assert condition as needed
- support nested array/object destructuring, dynamic keys, and some ... in forms

# test: Shared Testing + RVM Suites

- move YAML test helpers into test_utils.rs and re-export via common.rs for use by interpreter and vm test suites
- comprehensive compiler test suite
  - compiles policies with the new Rego→RVM compiler
  - runs them through RegoVM
  - compares against interpreter behavior
  - supports multiple entry points
  - provides assembly listings
  - filterable YAML suites.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-11-24 12:08:37 -06:00
Anand Krishnamoorthi
688e6128d4 feat: Detect incorrect multi-threaded use from c based ffi (#499)
Add runtime detection for shared handle misuse

wrap the FFI engine handle with parking_lot::RwLock when the new
contention_checks feature is enabled, surfacing a clear “handle is already
in use” error instead of allowing undefined behavior
keep the feature optional so no_std builds or environments that supply
their own synchronization can opt out
caution users that this guards the handle itself but does not make the
engine’s operations globally thread-safe on its own

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-11-17 14:21:13 -06:00
Anand Krishnamoorthi
ad8c543fb5 feat: Add Azure RBAC condition parser (#496)
* feat: Add Azure RBAC condition parser

- declare an `azure-rbac` feature and expose the Azure RBAC module with parser, AST, and YAML-driven tests
- extend the shared lexer with RBAC-specific tokens, single-quoted strings, and corrected raw-string spans
- verify the parser via comprehensive test cases covering every operator and complex chaining

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



---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-11-17 14:15:35 -06:00
Anand Krishnamoorthi
49bd3c22f3 feat!: add Rego Virtual Machine (RVM) implementation (#495)
* feat!: add Rego Virtual Machine (RVM) implementation

This commit introduces a register-based virtual machine for executing Rego
policies with bytecode-style instructions. Unlike the existing tree-walking
interpreter, the RVM compiles policies into instruction sequences that operate
on virtual registers, offering better performance and optimization potential.

Core Components:

Instruction Set Architecture:
- Define instruction types for data operations, control flow, and builtins
- Implement instruction parameter encoding and display formatting
- Add instruction parser with comprehensive test coverage

Virtual Machine Engine:
- Register-based execution model with program counter management
- Loop execution supporting iterators, comprehensions, and quantifiers
- Function call handling with argument evaluation and context management
- Rule evaluation with default value resolution and virtual data support
- Arithmetic and comparison operation implementations

Program Representation:
- Program listing builder with instruction sequencing
- Rule tree construction for organizing policy rules
- Binary and JSON serialization for compiled programs
- Recompilation support for program modification

Testing Infrastructure:
- Extensive YAML test suites covering all VM features
- Rust unit tests for VM execution and instruction parsing
- Test suites for loops, comprehensions, builtins, and control flow

BREAKING CHANGE: Introduces new VM execution path alongside interpreter

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

* docs: add detailed RVM architecture references

Introduce architecture.md explaining program artifacts, serialization, and runtime subsystems.
Document the full opcode catalog in instruction-set.md, including operands, parameter tables, and outcomes.
Walk through execution flow, stacks, and operational guidance in vm-runtime.md, tying the runtime to the new architecture docs.

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-11-14 11:43:19 -06:00
Anand Krishnamoorthi
6dc505c88b build: Add xtask automation for binding version management (#491)
* build: Add xtask automation for binding version management

Introduces a dedicated xtask crate that keeps language binding versions
in sync with the core regorus crate, following the workflow pattern used
by rust-analyzer, gitoxide, and ripgrep.

Key features:
- Git-based change detection: compares binding source files against a
  base ref (merge-base with origin/main by default) plus unstaged/
  untracked files to identify which bindings have been modified
- SemVer-aware bumping: binding edits trigger a minor version increment
  (e.g. 0.5.1 → 0.6.0) under pre-1.0 semantics, signaling potential
  breaking changes; clean bindings simply align to the root version
- Multi-language support: updates Cargo manifests (Rust FFI, Java,
  Python, WASM, Ruby), Maven pom.xml (Java), Ruby version constants,
  and C# project files in a single pass
- CI integration: --check mode fails fast when manifests are out of
  sync, ensuring pre-commit and release-plz workflows catch stale
  versions before merge

Integration points:
- release-plz.toml: runs cargo xtask bindings --base-ref origin/main
  after bumping the root crate, so binding versions are updated
  atomically during the release process
- scripts/pre-commit: invokes cargo xtask bindings --check to block
  commits that would leave bindings out of sync
- .cargo/config.toml: defines cargo xtask alias for convenience

Documentation includes inline examples showing how version bumps behave
when bindings are ahead/behind the root, and notes that the minor
field acts as the major version under SemVer 0.y.z initial development
phase.

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

* build: refresh xtask tooling, workflows, and locks

- cargo xtask bindings: keep the binding version-sync pipeline intact
- cargo xtask update-deps: new helper to regenerate workspace/binding Cargo.lock files
- workflows: auto-detect the Java jar version in CI and temporarily disable the Ruby workflow
- lock files: refresh root + binding snapshots after the dependency sweep

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-10-28 16:10:54 -05:00
Anand Krishnamoorthi
091bbb2e5c feat: Optimize C# binding interop (#488)
- Introduce Utf8Marshaller helpers and SafeHandle wrappers so the managed API centralizes UTF-8 conversions and lifetime management for native pointers.
- Update Engine, Compiler, CompiledPolicy, SchemaRegistry, and TargetRegistry to rely on the new marshaller/safe handles, tightening disposal and reducing transient allocations during interop calls.
- Add allocation guard coverage in Regorus.Tests and report bytes/op in the compiled policy benchmark to surface future regressions.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-10-27 12:35:40 -05:00
Anand Krishnamoorthi
1e4ff952e6 feat!: Introduce structured destructuring plans for bindings (#485)
- add a dedicated `compiler/destructuring_planner` feature that precomputes binding plans for assignments, parameters, and `some in` expressions
- enrich `ScopeContext` with same-scope tracking, local scheduling hints, and module globals so the planner enforces := shadowing rules without blocking parent scopes
- wire the planner through compiler, hoist, interpreter, and engine paths while updating binding plan variants and adding query traversal helpers for dependency analysis
- document the new planner architecture and ship interpreter regressions that exercise nested destructuring, shadowing, and error reporting

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-10-21 15:57:49 -05:00
dependabot[bot]
25a7ddad0a build(deps): bump clap from 4.5.45 to 4.5.49 (#487)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.45 to 4.5.49.
- [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.45...clap_complete-v4.5.49)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.49
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 09:26:05 -05:00
Anand Krishnamoorthi
5d8387f4d9 feat(hoist): pre-compute loop hoisting metadata at compilation time (#483)
Introduce a compiler pass that analyzes and pre-computes loop hoisting information
during policy compilation. This hoisted metadata is stored in lookup tables and made
available to downstream consumers:

- interpreter: use HoistedLoop entries during evaluation (replaces runtime scanning)
- type inference: can leverage pre-computed loop structure for type propagation
- RVM compiler: will consume hoisting metadata for optimized bytecode generation

Changes:
- populate loop hoisting tables during engine preparation and query snippet execution
- refactor eval_stmts_in_loop and eval_output_expr_in_loop to consume HoistedLoop directly
- add helper methods for accessing loop expressions, collections, and indices from HoistedLoop
- extend Lookup with get_checked and into_slots for safe query context access and merging

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-10-08 11:11:30 -05:00
Denis Komissarov
9604fe86f1 Bump the version of the C# bindings (#482) 2025-09-30 15:31:46 -05:00
Anand Krishnamoorthi
ac388684bc fix: CodeQL reported printf format specifier issues (#480)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-09-25 18:45:20 -05:00
Anand Krishnamoorthi
57f2e7703c ci: Add CodeQl workflow (#478)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-09-22 18:01:19 -05:00
Kirill Zabelin
4ec9e76440 Set input in with_document too when reuse engine (#474) 2025-09-08 07:37:46 -05:00
Tyler Schade
1b0c2d4072 feat: Implement net.cidr_contains builtin (#471)
Major changes:
- Implement the `net.cidr_contains` builtin
- Enable the v0 and v1 test for `net.cidr_contains`
- Add the `netip` crate to standardize CIDR searching and other
  operations

Key Concept:
- Allow users to leverage the `net.cidr_contains` builtin to check
  whether an IPv4 or IPv6 CIDR contains a specified IP address or
  subnet.

Testing:
- All tests passing.

Signed-off-by: tjons <tylerschade99@gmail.com>
2025-09-05 15:21:41 -05:00
Anand Krishnamoorthi
85753aaf37 feat: Implement efficient node lookup table using node indices (#463)
Major Changes:
- Add generic Lookup<T> structure for efficient O(1) module-level data access
- Combine separate scope and order lookups into unified QuerySchedule structure
- Add query_schedule field to Interpreter for dedicated user query scheduling
- Refactor loop hoising to separate module
- Use efficient lookup for loop vars
- Also added more tests for loops

Key Concept:
- Ensure module context and indexing stay synchronized during function calls

Testing:
- All scheduler and interpreter tests passing

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-26 15:01:45 -05:00
Anand Krishnamoorthi
c43c94559a feat: modernize GitHub Actions with security hardening and centralized Rust toolchain (#470)
Security Improvements:
- Pin all GitHub Actions to specific commit hashes instead of version tags
- Update actions/checkout from v4 to commit 08eba0b27e820071cde6df949e0beb9ba4906955
- Update actions/setup-python from v5 to commit a26af69be951a213d495a4c3e4e4022e16d87065 (v5.6.0)
- Update actions/setup-java from v4 to commit dded0888837ed1f317902acf8a20df0ad188d165 (v5.0.0)
- Update actions/setup-node from v4 to commit 1e60f620b9541d16bece96c5465dc8ee9832be0b (v4.4.0)
- Update actions/setup-go from v5 to commit 41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed (v5.1.0)
- Update actions/setup-dotnet from v4 to commit 3e891b0cb619bf60e2c25674b222b8940e2c1c25 (v4.1.0)
- Update actions/upload-artifact from v4 to commit ea165f8d65b6e75b540449e92b4886f43607fa02 (v4.6.2)
- Update actions/download-artifact from v4 to commit 634f93cb2916e3fdff6788551b99b062d0335ce0 (v5.0.0)
- Update github/codeql-action from v3 to commit 01fe2e8c43536ad5e1085bad5e7cd6fbc8a30988 (v3.29.11)

Rust Toolchain Consolidation:
- Create custom composite action .github/actions/toolchains/rust/action.yml
- Standardize on Rust 1.89.0 (latest stable) with clippy and rustfmt components
- Add optional targets parameter for cross-compilation support
- Replace dtolnay/rust-toolchain@stable across 16 workflows

This creates a more secure, maintainable, and consistent CI/CD pipeline
with centralized Rust toolchain management across all workflows.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-26 12:42:02 -05:00
Anand Krishnamoorthi
2a0b4ae6b5 feat! Mimalloc as the default allocator (#434)
This change integrates mimalloc as the default memory allocator for Regorus,
delivering significant performance improvements across all evaluation modes
and language bindings.

Technical Implementation:
- Build mimalloc in vendored mode from C sources (following QSharp approach)
- Implement GlobalAlloc trait for seamless Rust integration
- Add optional 'mimalloc' feature flag for conditional compilation
- Add comprehensive ACI benchmarks to measure evaluation performance

Performance Impact:

Rust Engine Evaluation:
- Single-threaded: ~29% improvement (423 vs 328 Kelem/s)
- Multi-threaded: Better scaling with reduced thread contention
- Fresh engines: ~24% improvement (56 vs 45 Kelem/s)

Rust Compiled Policy Evaluation:
- Single-threaded: ~41% improvement (426 vs 303 Kelem/s)
- Multi-threaded: Improved allocation efficiency under contention
- Fresh compilation: ~26% improvement (53 vs 42 Kelem/s)

C# FFI Bindings:
- Engine evaluation: ~27% improvement (279 vs 219 Kelem/s)
- Compiled policies: ~29% improvement (273 vs 211 Kelem/s)
- Better threading characteristics through improved underlying allocation

Key Benefits:
- Reduced allocation-related contention in multi-threaded scenarios
- More consistent performance across different thread counts
- Improved memory allocation efficiency for both native Rust and FFI workloads
- Better scaling characteristics for production deployments

The mimalloc integration provides substantial performance gains while
maintaining full compatibility with existing code through feature flags.

Reference: QSharp allocator implementation
(https://github.com/microsoft/qsharp/tree/main/source/allocator)

Fixes #297

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-25 15:01:38 -05:00
Burak Varlı
6c5338228b Update release-plz GitHub workflow (#466)
- Disable publishing for `ensure_no_std` test crate
- Add write content permissions to release-plz job expliclity

Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
2025-08-22 12:04:12 -05:00
Anand Krishnamoorthi
d561531613 feat: add multi-threaded evaluation benchmark suite with comprehensive C# implementation (#457)
This commit introduces a complete multi-threaded evaluation benchmark suite for both Rust and C# implementations of Regorus.

- Implemented engine evaluation benchmark with input and engine cloning strategies
- Implemented compiled policy evaluation benchmark with input cloning and shared compiled policy strategies.

- Created EngineEvaluationBenchmark.cs and CompiledPolicyEvaluationBenchmark.cs with time-based execution (3s warmup + 3s evaluation)
- Implemented configuration options matching Rust implementation (useClonedEngines, useSharedPolicies parameters)

- Created markdown analysis documentation with cross-platform performance analysis
- C# seems to achieve 58-89% of Rust performance on test machine.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-22 11:40:39 -05:00
Burak Varlı
a53c7c8192 fix: Use license instead of license-file in Cargo.toml (#464)
Since Regorus is MIT-licensed, it would be better to specify `license = "MIT"` rather than using `licene-file` to point to the MIT license file.
Some tools do not support parsing of `license-file`, for example crates.io classifies Regorus' license as "non-standard":

     $ curl -s https://crates.io/api/v1/crates/regorus/0.4.0 | jq .version.license
     "non-standard"

Using `license` would provide better compatability with various tools.

Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
2025-08-22 09:29:28 -05:00
dependabot[bot]
5c71debcb9 Bump the nuget group with 1 update (#462)
Bumps System.Text.Json from 8.0.0 to 8.0.5

---
updated-dependencies:
- dependency-name: System.Text.Json
  dependency-version: 8.0.5
  dependency-type: direct:production
  dependency-group: nuget
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-20 05:54:13 -05:00
Anand Krishnamoorthi
cc917ea75d feat: Complete target system with C# bindings and resource inference (#458)
* feat: Add Schema Registry and Validation Framework

This commit introduces a comprehensive schema registry and validation framework, providing schema-based validation of resources and policy effects.

- Thread-safe, in-memory registry for schema storage and management
- Global registry patterns for effects and resources
- Concurrent access with proper error handling
- Unicode schema names support

- JSON Schema-compliant validation for all primitive types
- Advanced constraint validation (patterns, ranges, length limits)
- Discriminated union support with anyOf schemas
- Detailed error reporting with nested validation paths
- Discriminated subobject validation for polymorphic schemas

- **Registry Tests**: All registry operations
- **Effect Tests**: Policy effect validation
- **Resource Tests**: Resource validation
- **Validation Tests**: Core validation engine
- Thread-safety, error handling, integration scenarios, edge cases

- **Dependencies**: dashmap, once_cell, regex
- **Thread Safety**: Minimal locking with Rc<Schema> sharing
- **Error Types**: TypeMismatch, OutOfRange, PatternMismatch, etc.

- Complete schema registry and validation subsystem
- Comprehensive test coverage
- Foundation for policy validation in Regorus

Benchmarks:

- Criterion benchmarks for basic types, effects and Azure resources
- Performance range: 3.22ns (string) to 34.74µs (Azure VM resource schema validation)
- String withs patterns validation: 30.2µs. Need to explore whether regex caching helps
  bring this down.
- Azure policy effects: 188ns-1.4µs

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

* feat: Complete target system with C# bindings and resource inference

- Add comprehensive target system with TargetRegistry and target-aware compilation
- Implement resource type inference from policy equality expressions
- Create modular C# bindings with separate wrapper classes for each concept
- Add thread-safe CompiledPolicy with reference counting for safe disposal
- Enhance FFI with detailed error propagation and target functionality
- Create TargetExampleApp demonstrating Azure Policy integration
- Add CI/CD pipeline testing for all C# applications
- Support target definitions with schema validation and resource selectors
- Implement PolicyModule struct and target-aware compilation methods
- Add comprehensive test coverage for target functionality

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-19 20:23:43 -05:00
dependabot[bot]
3c33d31d08 build(deps): bump clap from 4.5.43 to 4.5.45 (#459)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.43 to 4.5.45.
- [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.43...clap_complete-v4.5.45)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-18 06:40:36 -05:00
dependabot[bot]
965daa0a46 build(deps): bump dashmap from 5.5.3 to 6.1.0 (#460)
Bumps [dashmap](https://github.com/xacrimon/dashmap) from 5.5.3 to 6.1.0.
- [Release notes](https://github.com/xacrimon/dashmap/releases)
- [Commits](https://github.com/xacrimon/dashmap/compare/v.5.5.3...v6.1.0)

---
updated-dependencies:
- dependency-name: dashmap
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-18 05:03:37 -05:00
Anand Krishnamoorthi
db718654b5 feat: Add Schema Registry and Validation Framework (#456)
* feat: Add Schema Registry and Validation Framework

This commit introduces a comprehensive schema registry and validation framework, providing schema-based validation of resources and policy effects.

- Thread-safe, in-memory registry for schema storage and management
- Global registry patterns for effects and resources
- Concurrent access with proper error handling
- Unicode schema names support

- JSON Schema-compliant validation for all primitive types
- Advanced constraint validation (patterns, ranges, length limits)
- Discriminated union support with anyOf schemas
- Detailed error reporting with nested validation paths
- Discriminated subobject validation for polymorphic schemas

- **Registry Tests**: All registry operations
- **Effect Tests**: Policy effect validation
- **Resource Tests**: Resource validation
- **Validation Tests**: Core validation engine
- Thread-safety, error handling, integration scenarios, edge cases

- **Dependencies**: dashmap, once_cell, regex
- **Thread Safety**: Minimal locking with Rc<Schema> sharing
- **Error Types**: TypeMismatch, OutOfRange, PatternMismatch, etc.

- Complete schema registry and validation subsystem
- Comprehensive test coverage
- Foundation for policy validation in Regorus

Benchmarks:

- Criterion benchmarks for basic types, effects and Azure resources
- Performance range: 3.22ns (string) to 34.74µs (Azure VM resource schema validation)
- String withs patterns validation: 30.2µs. Need to explore whether regex caching helps
  bring this down.
- Azure policy effects: 188ns-1.4µs

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

* Address PR feedback

- move error to a separate file
- use meaningful var names

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

* Refactor

- Reusable Registry struct
- Split and simplify tests

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-14 15:59:30 -05:00
Anand Krishnamoorthi
77f8544868 feat: Type System (#452)
Details:

- Implement complete Type enum with 12 variants: Any, Integer, Number, Boolean,
  Null, String, Array, Set, Object, Enum, Const, AnyOf
- Add Schema wrapper struct with reference counting for efficient sharing
- Support JSON Schema-compatible deserialization with serde
- Implement discriminated subobjects for polymorphic type definitions
- Add comprehensive test suite covering all type variants
- Include Azure resource schema examples (Storage, VM, Key Vault, App Service)
- Create meta-schema validation system with lazy static validator
- Add extensive edge case and corner case test coverage
- Implement custom deserializers for complex schema patterns

This establishes the foundation for type checking and validation of Rego
policies, particularly useful for cloud resource schemas and policy validation.

Regorus's type system is a first of many features intended to
enable type checking and various other constraints on Rego policies.

The type system is inspired from:
   - JSON schema
   - Bicep

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-11 15:40:45 -05:00
dependabot[bot]
2fcd5e3eb9 build(deps): bump clap from 4.5.42 to 4.5.43 (#454)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.42 to 4.5.43.
- [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.42...clap_complete-v4.5.43)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.43
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-11 07:07:53 -05:00
dependabot[bot]
30f0d4e781 build(deps): bump criterion from 0.6.0 to 0.7.0 (#455)
Bumps [criterion](https://github.com/bheisler/criterion.rs) from 0.6.0 to 0.7.0.
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.6.0...0.7.0)

---
updated-dependencies:
- dependency-name: criterion
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-11 06:34:50 -05:00
Anand Krishnamoorthi
de6aa2bcd1 fix: Fix broken build (#453)
The clone optimization PR didn't have the latest changes for "azure_policy".
Integration resulted in compile errors.

Also fix errors due to updated clippy lints.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-07 12:52:09 -05:00
Anand Krishnamoorthi
dbba57f499 perf: Optimize clone (#443)
Introduce the notion of CompiledPolicy to hold stuff that
remains immutable during evaluation - e.g. rules, function,
schedules etc

Cloning takes about 60 nano seconds for an engine loaded with
ACI policies. Earlier it used to take 40 microseconds.
Thus there is easily more than 100x speedup.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-06 20:50:47 -05:00
Tyler Schade
a8384da070 feat: implement net.cidr_is_valid builtin (#422)
Signed-off-by: tjons <tylerschade99@gmail.com>
2025-08-06 05:17:10 -05:00
Denis Komissarov
c5b2b0df97 build: Run format and clippy during C# binding build (#451) 2025-08-04 19:57:38 -05:00
Anand Krishnamoorthi
fc09802bfb deps: Update dependencies across all Cargo.toml files and ignore .sln files (#450)
Dependencies updated:
- cc: 1.2.29 -> 1.2.31
- chrono-tz: 0.10.3 -> 0.10.4
- clap: 4.5.40 -> 4.5.42
- clap_builder: 4.5.40 -> 4.5.42
- clap_derive: 4.5.40 -> 4.5.41
- phf: 0.11.3 -> 0.12.1
- phf_shared: 0.11.3 -> 0.12.1
- rand: 0.9.1 -> 0.9.2
- rb-sys: 0.9.116 -> 0.9.117
- rb-sys-build: 0.9.116 -> 0.9.117
- redox_syscall: 0.5.13 -> 0.5.17
- rustix: 1.0.7 -> 1.0.8
- serde_json: 1.0.140 -> 1.0.142
- windows-targets: 0.53.2 -> 0.53.3
- winnow: 0.7.11 -> 0.7.12

Removed obsolete build dependencies:
- chrono-tz-build, parse-zoneinfo, phf_codegen, phf_generator, rand_core

Updated in: main, ffi, java, python, ruby, and wasm bindings
Added *.sln to .gitignore to exclude Visual Studio solution files

Also fix python publishing pipeline by removing non-existent dependency.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-08-04 19:56:27 -05:00
Denis Komissarov
9fce2ccc00 feat: Implement methods to get package names and parameters (#425) 2025-08-04 15:02:22 -05:00
Milian Wolff
3b802c14cb fix: Fix install of regorus_ffi C++ bindings (#449)
When using a clean build dir the install failed as it referenced
a non-existing `regorus_ffiCorrosion.cmake` file. I believe I used
the shorter `regorus_ffi` as my `EXPORT` in the `corrosion_install`
at some point and then later didn't notice that I referenced a stale
generated file when I initially handed in this PR. We must make sure
that the same identifier is used here too. See also the documentation
from `corrosion_install`:

> * **EXPORT**: Creates an export that can be installed with `install(EXPORT)`. <export-name> must be globally unique.
>             Also creates a file at ${CMAKE_BINARY_DIR}/corrosion/<export-name>Corrosion.cmake that must be included in the installed config file.
2025-08-04 09:40:52 -05:00
Anand Krishnamoorthi
90b4ec6823 fix: Tweak release-plz config to handle dirty files. (#433)
bindings/ruby/bin/console and bindings/ruby/bin/setup show up
as dirty to release-plz and causes it to fail to update.

As a workaround, set allow_dirty to true to enable update.
However ensure that no dirty files are published by setting
publish_allow_dirty to false.

Also default to not publishing any packages except regorus.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-09 11:27:12 -05:00
Anand Krishnamoorthi
9487defa20 chore: Regorus v0.5.0 release (#432)
Also update binding versions and lock files.
Note:
- Ruby binding is not updated
- C# binding is v0.7.0. We will make it match Regorus version later.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-08 16:40:06 -05:00
Anand Krishnamoorthi
48d2064c14 ci: Update release-plz action to v0.5.108 (#431)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-08 14:22:49 -05:00
Kirill Zabelin
a29bfeeb4f Early return for 'some in' statement (#427)
* fix: use early exit in 'some in' statements

Update kata tests:
Since 'early return' now works with 'some in' statement, interpreter
does not do any evaluation after it found match for rule, therefore
we don't have other rule checks after interpreter found match
2025-07-08 13:47:06 -05:00
Anand Krishnamoorthi
0a9864f3ec fix: emit import warning to stderr (#430)
fixes #429

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-08 12:23:54 -05:00
Anand Krishnamoorthi
9cba07b778 fix: Fix mut_from_ref clippy warning (#428)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-08 11:48:13 -05:00
Denis Komissarov
168b2a9c88 build: Support manually generating C# bindings via Github action and add a README (#423) 2025-07-03 12:31:32 -05:00
Anand Krishnamoorthi
8ee1cf3298 fix: Clippy warnings (#424)
Also schedule works to be run at 8:00 AM everyday.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-07-02 20:04:27 -05:00
Anand Krishnamoorthi
444b2970a1 feat!: Indexes for nodes in the AST (#414)
Indexes allow associating extra data with nodes in the AST
using an array and then quickly looking up the array to fetch
the extra data.

- Index eidx for expressions
- Index sidx for statements
- Index qidx for queries.

AST nodes are not cloneable. Therefore once a module is created,
it is not possible to accidentally create two nodes with the same
index inadvertently via clone.

Also added IndexChecker in debug builds. When a module is parsed,
it will assert that indexes have been constructed correctly.

AST Cleanup
- Make literal expressions (null, val, number, string etc) also structs
  to match all other expressions
- Merge True and False nodes into a single Bool node.

Also update dependencies.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-06-20 15:09:07 -05:00
Milian Wolff
620f8a4547 Make the bindings/cpp CMake project installable (#416)
This leverages the still-undocumented `corrosion_install` to get
an installable ffi binding that can be consumed by other projects,
e.g. from yocto:

```
$ ninja install
[4/5] Install the project...
-- Install configuration: "Debug"
-- Up-to-date: /home/milian/projects/compiled/regorus-test/lib/libregorus_ffi.so
-- Up-to-date: /home/milian/projects/compiled/regorus-test/include/regorus_ffi/regorus.hpp
-- Up-to-date: /home/milian/projects/compiled/regorus-test/include/regorus_ffi/regorus.ffi.hpp
-- Up-to-date: /home/milian/projects/compiled/regorus-test/lib/cmake/regorus_ffi/regorus_ffi_targets.cmake
-- Up-to-date: /home/milian/projects/compiled/regorus-test/lib/cmake/regorus_ffi/regorus_ffiConfig.cmake
-- Up-to-date: /home/milian/projects/compiled/regorus-test/lib/cmake/regorus_ffi/regorus_ffiCorrosion.cmake
```

This can then be consumed as such:

```
find_package(regorus_ffi CONFIG REQUIRED)

add_executable(test test.cpp)
target_link_libraries(test PRIVATE regorus_ffi::regorus_ffi)
```
2025-06-02 11:45:22 -05:00
dependabot[bot]
c631d44154 build(deps): bump clap from 4.5.38 to 4.5.39 (#415)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.38 to 4.5.39.
- [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.38...clap_complete-v4.5.39)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.39
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 08:59:41 -05:00
Anand Krishnamoorthi
39f10326cc build(deps): Update criterion and other deps (#412)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-05-23 14:00:14 -05:00
Burak Varlı
60ac4a7a7c Basic benchmarking setup with Criterion (#408)
* Basic benchmarking setup with Criterion
* Fix Clippy warnings

Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
2025-05-14 10:41:06 -05:00
Burak Varlı
5caac47b38 Default to Rego v1 in regorus parse (#407)
Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
2025-05-14 09:28:18 -05:00
dependabot[bot]
2f6c39753c build(deps): bump clap from 4.5.37 to 4.5.38 (#406)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.37 to 4.5.38.
- [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.37...clap_complete-v4.5.38)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 10:33:03 -05:00
Anand Krishnamoorthi
130f9685fd feat: Updates for Policy Framework (#405)
- Documentation
  - Regorus Engine is intended to be used from a single thread
  - Clone the engine after adding policies and data to use from another thread

- Builtin errors strictness:
  - default to less strict for OPA compatibility
  - Provide API to change strictness

- Expose GetAstAsJson to C#,
  This can allow writing policy validations in C#.

- Use spectre mitigated msvc crt libs (binskim compliance)

- Update dependencies

fixes #404

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-30 15:33:40 -05:00
Anand Krishnamoorthi
b11007a1be fix: Disallow else blocks for set rules (#403)
else blocks following contains and old-style sets will raise
a parse error. Consistent with OPA.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-29 11:14:37 -05:00
Anand Krishnamoorthi
6719456468 build: Update dependencies (#401)
Also bump up C# package version

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-29 11:06:05 -05:00
Anthony Martin
962c0cc459 Add C# test examples (#397) 2025-04-23 08:01:51 -05:00
Anand Krishnamoorthi
9e43bd9878 fix!: Remove cryptographic builtins (#396)
Cryptographic builtins are removed due to various reasons like FIPS
compliance. Users needing crypto builtins are encouraged to use
extensions.

Deprecated functions are also removed.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-16 12:24:19 -07:00
dependabot[bot]
667cb0d90f build(deps): bump clap from 4.5.35 to 4.5.36 (#395)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.35 to 4.5.36.
- [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.35...clap_complete-v4.5.36)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.36
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 08:33:41 -07:00
Anand Krishnamoorthi
f46ab5b697 fix!: Fix glob.match behavior in presence of : (#390)
glob.match("api://*/appId", null, "api://foo.com/appId") wasn't
being handled correctly. Switch to globset crate.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-09 11:11:57 -07:00
Anand Krishnamoorthi
757edcc8fb build: Python binding portability (#388)
- Specify compatibility = linux in pyproject.toml to ensure
  manylinux compatibility.
- Use abi-py310 pyo3 feature to ensure compatibility with python
  3.10 and later.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-07 13:18:01 -07:00
dependabot[bot]
77cdac0fef build(deps): bump clap from 4.5.34 to 4.5.35 (#389)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.34 to 4.5.35.
- [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.34...clap_complete-v4.5.35)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.35
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-06 21:14:33 -07:00
Anand Krishnamoorthi
ab93c07773 fix: C# EvalRule (#387)
- Fix EvalRule to call EvalRule instead of EvalQuery
- Also fix clippy errors

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-04-04 15:44:16 -07:00
dependabot[bot]
2749e820c4 build(deps): bump pyo3 (#386)
Bumps the cargo group with 1 update in the /bindings/python directory: [pyo3](https://github.com/pyo3/pyo3).


Updates `pyo3` from 0.24.0 to 0.24.1
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/v0.24.1/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.24.0...v0.24.1)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-type: direct:production
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-02 07:13:48 -07:00
Anand Krishnamoorthi
a164f342bf build: Use VersionPrefix and VersionSuffix (#385)
The suffix can be customised during dotnet pack.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-31 18:24:59 -07:00
Anand Krishnamoorthi
c28bde3f56 build: Check-in Cargo.lock files and lockdown .net (#384)
Use frozen and locked builds

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-31 07:55:47 -07:00
Anand Krishnamoorthi
2858b63cd4 feat: Regorus nuget package (#383)
Organize C# binding example into separate Regorus nuget package and
a test app.

The nuget package targets netstandard 2.0 and 2.1.
The test app is tested for netframework 8.0.

Implement IDisposable for Engine cleanup.

Also remove net40 example. Can be added back later if needed.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-25 11:31:13 -07:00
Anand Krishnamoorthi
c7bf460bc1 chore: release (#382)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-14 14:56:25 -07:00
Anand Krishnamoorthi
4d2b205ef4 fix!: Update ruby json dependency (#381)
Previous version has Out-of-bounds Read in Ruby JSON Parser

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-14 13:31:12 -07:00
Anand Krishnamoorthi
4f7b9a4292 fix!: Remove ring dependency (#380)
Remove dependency on jsonwebtoken which brings in the ring crate.
Ring crate triggers governance violations.

Support for JWT will be implemented in future using a more governance
compliant crate.

BREAKING CHANGE

Prior to this PR, support for jwt builtins was minimially implemented.
Only io.jwt.decode and io.jwt.decode_verify was implemented.
With this PR, those builtins will no longer be available. They are
planned to be implemented in the future. In the meantime, they can be
brought back in via Engine::add_extension.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-14 10:49:57 -07:00
Anand Krishnamoorthi
4a2df93ae2 fix!: Remove sha1 dependency (#379)
Removed cryptographically insecure sha1. This existed only for OPA
compatibility.

Also exclude bindings from main workspace

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-13 12:34:11 -07:00
Anand Krishnamoorthi
c6a5f1d852 build: Specify optimization flags (#378)
In release profile, enable lto and codgen-units = 1 to enable more
optimizations.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-10 17:50:31 -07:00
Anand Krishnamoorthi
2901481c51 chore: release (#376)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-10 12:33:59 -07:00
Anand Krishnamoorthi
c963e477a3 feat: Update to OPA v1.2.0 (#373)
Regorus now defaults to rego v1. `import rego.v1` is no longer needed.
Additionally, `future` keywords are automatically imported.

See
https://www.openpolicyagent.org/docs/latest/v0-upgrade/#changes-to-rego-in-opa-v10
to understand the differences between rego v1 and v0.

BREAKING CHANGE:

v0 style policies will error out by default. To enable v0 behavior, call engine.set_rego_v0(true) before
loading policies.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-10 11:56:01 -07:00
dependabot[bot]
cbd772623a build(deps): update pyo3 requirement from 0.23.5 to 0.24.0 (#375)
Updates the requirements on [pyo3](https://github.com/pyo3/pyo3) to permit the latest version.
- [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.23.5...v0.24.0)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-09 20:25:44 -07:00
thedavemarshall
a07beca983 Update ruby binding deps, ruby gem version 0.2.3 (#374)
* Update ruby binding deps, ruby gem version 0.2.3

* and gem version to 0.2.3

* specify bunlder and rubygems version for CI
2025-03-07 16:24:29 -08:00
Anand Krishnamoorthi
a3edb6c88c build(deps): update pyo3 requirement from 0.22.0 to 0.23.5 (#372)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-04 15:44:24 -08:00
Anand Krishnamoorthi
a1777fb7d3 build(deps): update rand requirement from 0.8.5 to 0.9.0 (#370)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-04 07:06:43 -08:00
dependabot[bot]
11aaa555aa build(deps): update cbindgen requirement from 0.27.0 to 0.28.0 (#361)
Updates the requirements on [cbindgen](https://github.com/mozilla/cbindgen) to permit the latest version.
- [Release notes](https://github.com/mozilla/cbindgen/releases)
- [Changelog](https://github.com/mozilla/cbindgen/blob/master/CHANGES)
- [Commits](https://github.com/mozilla/cbindgen/compare/v0.27.0...0.28.0)

---
updated-dependencies:
- dependency-name: cbindgen
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-03 16:23:44 -08:00
Amaury Chamayou
f1580a55a3 Fix typo in README.md (#366)
Force merging since it is only a typo fix.

Signed-off-by: Amaury Chamayou <amaury@xargs.fr>
2025-03-03 15:58:31 -08:00
Anand Krishnamoorthi
6174af1781 Update dependencies (#369)
Specify `js` feature for `uuid` when building wasm by
specifying it as a non-optional dependency in wasm binding's Cargo.toml.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2025-03-03 15:53:47 -08:00
thedavemarshall
5fa55d7274 Fix clippy warning for result? (#362) 2025-01-21 08:37:54 -08:00
dependabot[bot]
748c11cfa1 build(deps): update itertools requirement from 0.13.0 to 0.14.0 (#357)
Updates the requirements on [itertools](https://github.com/rust-itertools/itertools) to permit the latest version.
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: itertools
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-06 10:35:00 -08:00
dependabot[bot]
fb035d3d93 build(deps): update jsonschema requirement from 0.26.1 to 0.28.1 (#356)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema) to permit the latest version.
- [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.26.1...rust-v0.28.1)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-06 10:01:06 -08:00
thedavemarshall
ba3a128e84 resolve anyhow compile errors (#355) 2025-01-06 09:13:01 -08:00
dependabot[bot]
d955ae10a5 build(deps): update prettydiff requirement from 0.7.0 to 0.8.0 (#348)
---
updated-dependencies:
- dependency-name: prettydiff
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 09:39:27 -08:00
Anand Krishnamoorthi
cabd086619 chore: release (#344)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-06 13:28:25 -08:00
Anand Krishnamoorthi
4ec25f37a1 build(deps): update jsonschema requirement from 0.24.0 to 0.26.1 (#343)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-11-06 13:05:48 -08:00
Anand Krishnamoorthi
c281d28474 chore: Update to OPA v0.70.0 (#341)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-11-06 12:20:11 -08:00
Anand Krishnamoorthi
1bfe38f9af fix: Lock wasm-bindgen version to 0.2.94 (#342)
v0.2.95 causes a crash with wasm tests in CI

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-11-06 12:04:59 -08:00
dependabot[bot]
5bf7cd7cc8 build(deps): bump rexml (#337)
Bumps the bundler group with 1 update in the /bindings/ruby directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.3.6 to 3.3.9
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.6...v3.3.9)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-04 08:53:08 -08:00
Anand Krishnamoorthi
c56da34843 chore: release (#335)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-22 14:05:26 -07:00
Anand Krishnamoorthi
61f82d1b34 fix: docs failing to build (#334)
Added #![cfg_attr(docsrs, feature(doc_cfg))]

fixes #333
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-10-22 13:37:31 -07:00
dependabot[bot]
00f45c70fe build(deps): update jsonschema requirement from 0.23.0 to 0.24.0 (#332)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema-rs) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/jsonschema-rs/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema-rs/compare/rust-v0.23.0...rust-v0.24.0)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-21 07:30:12 -07:00
Anand Krishnamoorthi
df73b20192 build(deps): update jsonschema requirement from 0.22.3 to 0.23.0 (#331) 2024-10-15 10:29:41 -07:00
Anand Krishnamoorthi
992b202f60 chore: release (#329)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-09 12:52:25 -07:00
Anand Krishnamoorthi
ce6ecd6fd6 feat: integer conversion functions for Value (#328)
closes #324

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-10-09 12:19:54 -07:00
Anand Krishnamoorthi
37262ccf8f chore: update to OPA v0.69.0 (#327)
Also fix CRLF vs LF related test failures in two doc tests on Windows

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-10-09 10:57:09 -07:00
dependabot[bot]
dcd040cf40 build(deps): update jsonschema requirement from 0.21.0 to 0.22.3 (#326)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema-rs) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/jsonschema-rs/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema-rs/compare/rust-v0.21.0...rust-v0.22.3)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 13:30:05 -07:00
dependabot[bot]
f0a3cf26a0 build(deps): update jsonschema requirement from 0.20.0 to 0.21.0 (#325)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema-rs) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/jsonschema-rs/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema-rs/compare/rust-v0.20.0...rust-v0.21.0)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 08:34:48 -07:00
Anand Krishnamoorthi
13d8289a58 chore: update to jsonschema 0.20.0 (#323) 2024-09-23 13:01:24 -07:00
Anand Krishnamoorthi
d2b27ee512 chore: release (#320)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-18 14:17:17 -07:00
Anand Krishnamoorthi
adb9da0c0c fix: Null terminate C# strings in Rust boundary (#318)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-09-18 13:58:51 -07:00
dependabot[bot]
dc0f48f6bc Update jsonschema requirement from 0.18.0 to 0.19.1 (#317)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema-rs) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/jsonschema-rs/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema-rs/compare/rust-v0.18.0...rust-v0.19.1)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 09:12:00 -07:00
dependabot[bot]
334db3d6ce Update chrono-tz requirement from 0.8.5 to 0.10.0 (#316)
Updates the requirements on [chrono-tz](https://github.com/chronotope/chrono-tz) to permit the latest version.
- [Release notes](https://github.com/chronotope/chrono-tz/releases)
- [Commits](https://github.com/chronotope/chrono-tz/compare/v0.8.5...v0.10.0)

---
updated-dependencies:
- dependency-name: chrono-tz
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 08:42:19 -07:00
Anand Krishnamoorthi
7565ec3ecf feat: or keyword (#315)
Add `or` operator to Rego languages. Available via `rego-extensions`
Cargo feature.

If the evaluated lhs value is not false, null or undefined it is returned.
Otherwise rhs is evaluated and returned.

or operator has least precedence, and is left-associative.

closes #314
2024-09-13 16:39:19 -07:00
Sumedh Alok Sharma
8498274356 Add tests for builtin strings::lower method (#313)
Signed-off-by: Sumedh Alok Sharma <sumsharma@microsoft.com>
2024-09-11 09:15:28 -07:00
Sumedh Alok Sharma
ecd341bbcc Add tests for builtin strings::indexof method (#311)
Signed-off-by: Sumedh Alok Sharma <sumsharma@microsoft.com>
2024-09-11 09:14:21 -07:00
Anand Krishnamoorthi
b6935d1add fix: Update readme with correct path to example policy (#312)
Also add test to lock down example policy path.
Also Fix clippy warning by using unwrap_or_default

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-09-10 11:47:46 -07:00
Anand Krishnamoorthi
502b830c19 chore: release (#310)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-04 10:22:55 -07:00
Anand Krishnamoorthi
8003cfc5b7 fix: Handle parsing corner cases (#309)
A block with a single or expression needs to be treated as a comprehension instead of a
set/array with 1 item. e.g.: {1 | 1 }, [2 | foo]

Allow successfully parsing object comprehensions as rule body
x if { 1:2 | 1 }

fixes #306, fixes #307

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-09-04 10:02:14 -07:00
Anand Krishnamoorthi
a4a80d7fc6 fix: Propagate errors encountered in argument evaluation (#308)
fixes #301

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-31 13:46:55 -07:00
Anand Krishnamoorthi
af5071446b feat: OPA v0.68.0. Engine::set_rego_v1 (#305)
Provide ability in the engine to treat subsequently loaded policies
as rego.v1.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-30 15:11:09 -07:00
Anand Krishnamoorthi
edd0ccca5b fix: Issues #302, #303 (#304)
Handle undefined values correctly in ordered-else. Previously an undefined value
in one of the blocks could cause the entire rule to evaluate to undefined.

Handle undefined values correctly in generic rule refs to prevent them from
propagating to output.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-29 09:47:45 -07:00
dependabot[bot]
fec6f8f8b4 Bump rexml in /bindings/ruby in the bundler group across 1 directory (#300)
Bumps the bundler group with 1 update in the /bindings/ruby directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.3.3 to 3.3.6
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.3...v3.3.6)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 14:13:29 -07:00
Anand Krishnamoorthi
dff65f0329 chore: release (#298)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-16 08:45:52 -07:00
dependabot[bot]
6bf40c7394 Update cbindgen requirement from 0.26.0 to 0.27.0 (#296)
Updates the requirements on [cbindgen](https://github.com/mozilla/cbindgen) to permit the latest version.
- [Release notes](https://github.com/mozilla/cbindgen/releases)
- [Changelog](https://github.com/mozilla/cbindgen/blob/master/CHANGES)
- [Commits](https://github.com/mozilla/cbindgen/compare/0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: cbindgen
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 09:09:53 -07:00
Anand Krishnamoorthi
a488a84969 fix: Match OPA behavior for split (#295)
In case of empty delimiter, Rust's split returns leading and trailing
empty strings whereas Golang's doesn't.
Change behavior to match Golang/OPA.

fixes #291

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-08 14:53:01 -07:00
dependabot[bot]
e4a58ad1dc Bump rexml in /bindings/ruby in the bundler group across 1 directory (#294)
Bumps the bundler group with 1 update in the /bindings/ruby directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.2...v3.3.3)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 13:08:09 -07:00
dependabot[bot]
430a453fde Update csbindgen requirement from =1.9.0 to =1.9.3 (#292)
Updates the requirements on [csbindgen](https://github.com/Cysharp/csbindgen) to permit the latest version.
- [Release notes](https://github.com/Cysharp/csbindgen/releases)
- [Commits](https://github.com/Cysharp/csbindgen/compare/1.9.0...1.9.3)

---
updated-dependencies:
- dependency-name: csbindgen
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 11:39:56 -07:00
Anand Krishnamoorthi
ef549a6528 fix: Merge data to init document (#293)
Init document is the aggregated data documen that the user has
specified using multiple `add_data` calls. Each query evaluation
starts of by initializing the current data to the init document.

Previously `add_data` was incorrectly added to the current document,
causing the added data to be lost if the addition happened after query
evaluation.

With this fix, scenarios where data addition may be interspersed with
query evaluation calls are supported.

Also provide a get_data method to obtain the (init) data document.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-07 11:39:23 -07:00
Anand Krishnamoorthi
52afcbe5c5 chore: release (#289)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-07-28 13:19:09 +05:30
Anand Krishnamoorthi
f0576cef77 Update readme (#288)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-28 13:04:47 +05:30
Anand Krishnamoorthi
20eece58ed Update binding versions (#287)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-28 12:32:39 +05:30
Anand Krishnamoorthi
6599ce6001 feat: Update to opa v0.67.0 (#286)
Implement strings.count builtin

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-28 11:46:07 +05:30
Anand Krishnamoorthi
7095e269b7 fix: Handle aliases in scheduler (#285)
Earlier scheduler only recognized rules and would raise an
`unsafe var` error on alias.

Register alias var names to fix this.

fixes #284

Also fix clippy warning treated as error

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-27 23:26:53 +05:30
Stuart Neivandt
6e1f8cdb36 build.rs create hooks dir if not exists (#283) 2024-07-21 10:27:51 +05:30
Jie Yang
fb5151e0e4 add extension_list example (#281)
- Created an example of extension policy
- Added C# binding support of .NET framework 4.0 and created a Nuget
  spec for it.
- Added a pytest in python bindings to test the extension policy and the
  python binding
- Restructured the example and Csharp binding directories due to above
  changes.
- Added copyrights.
- Added a Windows workflow for .NET 4.0 build and test.
2024-07-16 11:08:37 +05:30
Anand Krishnamoorthi
37d283cb38 Fix build break (#278)
- Fix warning due to use of deprecated function.
  This was causing a build issue in the hava and csharp bindings
- Lock use of csbindgen@1.9.0
  The newer version 1.9.2  causes a "type of namespace C could not be fond" error
  In the generated code, struct inherits from C instead of uint

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-07-12 05:28:25 +05:30
dependabot[bot]
25dbd27d82 Update pyo3 requirement from 0.21.0 to 0.22.0 (#275)
Updates the requirements on [pyo3](https://github.com/pyo3/pyo3) to permit the latest version.
- [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.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 09:51:50 +05:30
Anand Krishnamoorthi
292948a694 Update to OPA v0.66.0 (#274)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-28 08:57:03 +05:30
Anand Krishnamoorthi
5a0048cd64 chore: release (#271)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-06-19 16:09:03 -07:00
Anand Krishnamoorthi
4a2a724a80 Fix c,cpp,no-std binding examples (#272)
- Use regorus_ffi in target_link_libraries instead of regorus-ffi.
  Something seems to have changed in corrosion-rs to need this.
- Workaround for cmake issue where FFI header may not be generated in time

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-19 16:01:29 -07:00
Anand Krishnamoorthi
46e28b36f8 feat: get_policies: Way to obtain policy files and content (#267)
closes #254

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-19 00:01:55 -07:00
Anand Krishnamoorthi
ee898e112e Update binding versions for next release (#270)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-18 23:39:12 -07:00
balcanuc
e6f2ec825d rename method from 'Clone' to 'clone' in 'Engine' class to match the java naming convention and definiont in the of java.lang.Object. (#268)
Co-authored-by: Cristi Balcanu <balcanu@amazon.com>
2024-06-18 20:46:21 -07:00
Anand Krishnamoorthi
45627aa64a Suppress clippy unused warning (#269)
The field will be used later.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-18 10:28:02 -07:00
Anand Krishnamoorthi
df98c8d168 Provide ability to get JSON representation of policy AST (#266)
closes #265

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-08 18:58:30 -07:00
Anand Krishnamoorthi
25902bab57 Update OPA tests to v0.65.0 (#264)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-04 20:19:35 -07:00
Anand Krishnamoorthi
e62bfdf161 Allow lexer to be used for other policy languages (#262)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-06-04 19:59:54 -07:00
Anand Krishnamoorthi
658f34753b chore: release (#260)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-30 06:35:12 -07:00
Anand Krishnamoorthi
5eda76fd00 Add release-plz config to publish only regorus package (#259)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-30 06:26:10 -07:00
Anand Krishnamoorthi
97914d5596 Revert "chore: release v0.2.0 (#257)" (#258)
This reverts commit ffb79f1b30.
2024-05-30 06:09:47 -07:00
Anand Krishnamoorthi
ffb79f1b30 chore: release v0.2.0 (#257)
v0.2.0

Signed-off-by: Anand Krishnamoorthi <35780660+anakrish@users.noreply.github.com>

---------

Signed-off-by: Anand Krishnamoorthi <35780660+anakrish@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-30 05:45:43 -07:00
Anand Krishnamoorthi
536c6ede5c Fix release-plz hash (#256)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-30 05:12:24 -07:00
Anand Krishnamoorthi
ff0adf054e every: non collections should evaluate to false (#253)
See https://github.com/open-policy-agent/opa/pull/6763

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-28 06:25:13 -07:00
Anand Krishnamoorthi
2b82f2842f Fix merge issue (#252)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-25 14:01:12 -07:00
Anand Krishnamoorthi
d09c445add Update bindings to include newer APIs (#250)
- c, cpp
- csharp
- ffi
- go
- Java
- Python
- WASM

`arc` feature is turned on for all bindings
Use pretty string instead of colored string.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-25 10:24:06 -07:00
thedavemarshall
33fe9d5039 update ruby bindings version to 0.1.5, bump deps (#251)
* update ruby bindings version to 0.1.5, bump deps

* update ruby bindings for coverage and print methods
2024-05-24 07:57:39 -07:00
Anand Krishnamoorthi
3d08f6eef9 Use correct docsrs feature annotation (#248)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-23 08:31:09 -07:00
Anand Krishnamoorthi
3ac9b1121e Lockdown kata test prints as well as prints of various values (#249)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-23 08:30:41 -07:00
Anand Krishnamoorthi
9894f00829 Fix bindings and add CI tests (#247)
Also update version numbers of binding Rust projects to match Regorus

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-22 08:15:42 -07:00
thedavemarshall
495e91c75a Add test-ruby CI for github actions (#244)
https://github.com/oxidize-rb/actions/blob/main/setup-ruby-and-rust/readme.md
2024-05-20 15:53:36 -07:00
Burak
25ac1b6c95 Update README.md for Java bindings to mention we don't publish to (#246)
Maven Central currently
2024-05-20 08:15:39 -07:00
dependabot[bot]
209ece9dee Update itertools requirement from 0.12.1 to 0.13.0 (#245)
Updates the requirements on [itertools](https://github.com/rust-itertools/itertools) to permit the latest version.
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.12.1...v0.13.0)

---
updated-dependencies:
- dependency-name: itertools
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-19 20:33:23 -07:00
thedavemarshall
0952d42432 Update ruby bindings for add_policy and add_policy_from_file to return package name (#240) 2024-05-18 08:18:36 -07:00
Anand Krishnamoorthi
56bd8841d0 Provide a way to obtain package names of loaded policies (#239)
Engine::
- add_policy and add_policy_from_file return the package name.
- get_packages returns the names of loaded packages.

closes #229

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-18 08:02:54 -07:00
Anand Krishnamoorthi
67f65c0561 c_no_std binding to show use in C freestanding environments. (#238)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-17 18:31:11 -07:00
dependabot[bot]
5422bde391 Bump rexml in /bindings/ruby in the bundler group across 1 directory (#236)
Bumps the bundler group with 1 update in the /bindings/ruby directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.2.6 to 3.2.8
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.6...v3.2.8)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-17 07:25:21 -07:00
dependabot[bot]
68a0f8fcce Update prettydiff requirement from 0.6.4 to 0.7.0 (#234)
---
updated-dependencies:
- dependency-name: prettydiff
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 07:09:58 -07:00
dependabot[bot]
97b70a1c1b Update jsonschema requirement from 0.17.1 to 0.18.0 (#235)
Updates the requirements on [jsonschema](https://github.com/Stranger6667/jsonschema-rs) to permit the latest version.
- [Release notes](https://github.com/Stranger6667/jsonschema-rs/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema-rs/compare/rust-v0.17.1...rust-v0.18.0)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 06:54:07 -07:00
Anand Krishnamoorthi
e86b590f91 no_std support (#232)
- Disable default features in dependencies
- Use anyhow::Error::msg to map errors. Note: anyhow will itself be removed later.
- lazy_static/spin_no_std used in no_std environments
- ensure_no_std binary is built to target  thumbv7m-none-eabi to ensure that
  there are no std dependencies.  thumbv7m-none-eabi target has no std support.
- The opa-no-std feature enables only those Regorus features that work with no_std.
- Enable tests with no_std
- Update sizes of regorus binary in  README.md
- Ensure that regorus example can be built with only std
- Ensure that regorus example can be built with no_std

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-13 06:42:35 -07:00
Anand Krishnamoorthi
01fc234a33 add std feature (#231)
- `std` feature is enabled by default
- By default enable #![no_std] compilation
- Import std create if `std` feature is enabled or if testing
- Use core, alloc types
- Make it clear where std types are being used
- In no std, use BTreeMap in place of HashMap.
   HashMap is not available in no std due to lack of a
   secure random number generator

Note: The project does not yet compile without std feature being specified.
But it's really close to being able to do so.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-09 11:28:42 -07:00
Anand Krishnamoorthi
e9cd6d6afc Tests from MSFT fork of kata-containers (#230)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-08 17:38:50 -07:00
Anand Krishnamoorthi
69d6426663 Use alloc, core instead of std (#225)
- Replace std with alloc, core in most places in src
  Tests, bindings aren't changed.
- Introduce BuiltinsMap type alias inplace of HashMap.
  In no_std case, this could be aliases to BTreeMap
- Fix clippy warnings

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-07 18:41:09 -07:00
Anand Krishnamoorthi
0a39e434db chore: release (#226)
* chore: release
2024-05-07 18:26:50 -07:00
Anand Krishnamoorthi
9832a297ed Improve example in readme (#224)
- Use eval_rule
- Show functions add_policy, add_data, set_input
- Show Engine, Value types

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-02 12:46:01 -07:00
Anand Krishnamoorthi
c6fb8cf044 Add tests for kata containers policies (#221)
closes #220

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-27 06:31:09 -07:00
Anand Krishnamoorthi
55abbb2b42 Update to OPA v0.64.0 (#219)
Implement json.marshal_with_options builtin

closes #215, closes #218

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-26 04:52:11 -07:00
Anand Krishnamoorthi
3743f32edc Enable policy files greater than 64KB in size (#217)
fixes #214

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-26 01:04:21 -07:00
Anand Krishnamoorthi
744dad6126 OPA Conformance: Do not interpret # within regular string (#216)
fixes #213

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-25 23:51:52 -07:00
Anand Krishnamoorthi
7fde3382f6 chore: release (#210)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-22 15:09:01 -07:00
Anand Krishnamoorthi
316f3a7692 early return (#189)
If a rule is written to produce a constant value, then not all iterations of loops
within it need to be executed. Execution can stop via early return once the first iteration
that produces a value has been executed.

This brings forth the question : What if one of the subsequent iterations would have resulted
in an error?
e.g:
x {
  [1, "hello"][_] + 1
}

Such errors are not raised; consistent with OPA.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-22 07:41:35 -07:00
Anand Krishnamoorthi
de56cce7cb Fix anyhow dependency issues (#208)
- Do not require backtrace feature
- Starting version 1.0.77, anyhow gathers backtrace is std feature (enabled by default)
  is specified even if backtrace feature is not enabled.
  Therefore specify default features as false.
- Specify version 1.0.45 since that is the minimul version required to successfully
  compile regorus

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-20 09:57:52 -07:00
Anand Krishnamoorthi
72ced23366 build: remove unused compact-rc dependency (#207)
Remove unused compact-rc dependency, to avoid a build error:

error[E0658]: use of unstable library feature 'ptr_addr_eq'
<...>/registry/src/index.crates.io-6f17d22bba15001f/compact-rc-0.5.4/src/base.rs:319:9
    |
319 |         std::ptr::addr_eq(Self::as_ptr(this), Self::as_ptr(other))

Signed-off-by: Dan Mihai <dmihai@microsoft.com>
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
Co-authored-by: Dan Mihai <dmihai@microsoft.com>
2024-04-20 06:47:18 -07:00
Anand Krishnamoorthi
3fa2847e6f chore: release (#205)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-11 07:03:10 -07:00
Anand Krishnamoorthi
82c86437cb Add a note in example to prefer eval_rule over eval_query (#204)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-11 06:38:45 -07:00
Anand Krishnamoorthi
05e91da06e Do not enable serde_json/arbitrary_precision by default (#203)
The feature does not interoperate well with other serde_json features like untagged enums.

Fixes #199

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-11 06:23:14 -07:00
Anand Krishnamoorthi
8c69dd491b Rewrite so that code compiles with chrono_tz 0.8.5 and 0.9.0 (#201)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-11 04:37:38 +05:30
thedavemarshall
6a167143cb update ruby bindings (#200) 2024-04-10 07:28:25 +05:30
Anand Krishnamoorthi
d2049d07f3 Store Value instances in AST for strings, numbers and idents (#197)
This avoids having to create value instances during evaluation

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-07 18:21:32 +05:30
Anand Krishnamoorthi
e326f3c629 From<serde_json::Value> and From<serde_yaml::Value> (#196)
Provide wrappers around serde_json::from_value and serde_yaml::from_value since
they may not be apparent and the user may end up serializing to json/yaml and
rereading as a regorus::Value

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-06 16:40:57 +05:30
Anand Krishnamoorthi
3c7674e7c2 Build dependency on git only if opa.runtime feature is enabled. (#194)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-03 10:17:51 +05:30
Anand Krishnamoorthi
947c9490fa Update to opa v0.63.0 (#192)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-04-03 09:28:55 +05:30
dependabot[bot]
0ebcb568cc Update pyo3 requirement from 0.20.2 to 0.21.0 (#190)
Updates the requirements on [pyo3](https://github.com/pyo3/pyo3) to permit the latest version.
- [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.20.2...v0.21.0)

---
updated-dependencies:
- dependency-name: pyo3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-02 08:14:50 +05:30
thedavemarshall
e86801bdd9 Ruby bindings for existing FFI methods, plus eval_rule() (#188)
* Ruby binding

* use relative regorus crate in ruby instead of a published version, also cargo fmt

* remove unnecessary Cargo.toml, include the top level Cargo.lock in ruby gem

* ruby bindings continued- add eval_rule, fix _json methods, update README.md

also added rubocop-minitest and rubocop-rake, and added more test coverage

* update README.md to include Ruby bindings

Closes #191
2024-04-02 08:14:29 +05:30
Anand Krishnamoorthi
3d98c3b12e eval_rule: Evaluate rules directly instead of queries (#186)
closes #185

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-03-26 05:24:21 +05:30
Anand Krishnamoorthi
330a6dff72 Remove cruft. (#184)
Logging wasn't implemented fully nor getting used much.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-03-24 18:37:24 +05:30
Anand Krishnamoorthi
b80ef2d015 chore: release (#183)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-03-22 23:08:46 +05:30
Anand Krishnamoorthi
7e3fc08a14 Handle non simple refs in chained expressions (#182)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-03-15 07:13:54 -07:00
Anand Krishnamoorthi
48982222c5 Ability to gather print statements (#179)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-03-11 16:44:05 -07:00
Anand Krishnamoorthi
90757210bc Top-down evaluation (#177)
When executing a query, only those rules that are used
by the query will be evaluated.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-03-10 19:46:11 -07:00
Anand Krishnamoorthi
7bc9a50a52 Make unary - operator OPA compatible. (#175)
OPA supports unary - operator only in the following cases:
-\s+numeric literal

We match OPAs behavior for now. This can be revisited later.
2024-03-10 09:03:11 -07:00
Burak
08f3007b5c Don't use deprecated chrono Duration methods (#173)
Some panicking methods on `Duration` are deprecated
as part of chrono `0.4.35`. We switched to use `Duration::try_*`
of those APIs.
2024-03-09 14:35:08 -08:00
Anand Krishnamoorthi
863601c2d5 Propagate Undefined in object expressions (#171)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-03-05 09:56:15 -08:00
Anand Krishnamoorthi
976c04be8a Bump to OPA v0.62.0 (#169)
No code changes seem to be needed

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-03-01 09:24:14 -08:00
Anand Krishnamoorthi
fbfed6b49c Fix regression (#164)
Second lookup of an object rule without fully qualified path, resulted
in returning the object instead of the requested field.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-26 17:31:48 -08:00
Anand Krishnamoorthi
595f9d34d5 Separately keep track of whether rules have been evaluated or not (#163)
Previously we used to rely on whether there was a value in the
data document for a given rule path. This approach cannot handle
the case of evaluating a.b when a.b.c has been evaluated but
a.b.d has not been evaluated. Upon evaluating a.b.c, the data document
will already have a value of a.b even though a.b.d has not yet
been evaluated.

Hence we need to keep track of evaluated rules separately.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-25 23:36:48 -08:00
Burak
a8c0588426 bindings/java: Link Linux libraries against glibc 2.17 using cargo-zigbuild (#158) 2024-02-23 10:25:32 -08:00
Anand Krishnamoorthi
0e053832db chore: release (#157)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-22 21:18:49 -08:00
Anand Krishnamoorthi
f51731e584 Handle else block without body (#155)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-22 21:04:20 -08:00
Anand Krishnamoorthi
10f2caf0c0 Ignore errors from builtin functions in non strict mode (#154)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-22 15:46:51 -08:00
Burak
22047287b4 Java publishing (#151)
* bindings/java: Add prefix to native methods

* bindings/java: Add javadocs and missing methods to Engine

* Setup publishing uber-JAR via GitHub workflow

* bindings/java: Update README

* bindings/java: Improve native library loading from JAR

* bindings/java: Fix usage of `working-directory`

* bindings/java: Pass required `distribution` parameter to `actions/setup-java@v4`

* bindings/java: Use Corretto distribution

This is because Microsoft doesn't provide JDK8,
see https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-8.

* bindings/java: Install GCC toolchain for `aarch64-unknown-linux-gnu`

* bindings/java: Upload artifacts with different names from each step

* bindings/java: Upload built JARs to GitHub
2024-02-22 15:00:07 -08:00
Anand Krishnamoorthi
3a86c83827 Document coverage feature; Convenience query functions (#152)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-22 14:59:06 -08:00
Anand Krishnamoorthi
d3d5367fd4 Policy Coverage (#149)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-19 19:10:13 -08:00
Anand Krishnamoorthi
f3d9652a73 Initial implementation of policy coverage (#146)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-18 22:16:53 -08:00
Burak
bdb2aba596 Java bindings (#147) 2024-02-18 08:21:58 -08:00
Anand Krishnamoorthi
8d282f1ffd Preserve false in single-expression queries (#145)
Note: 1 = 2 is different from 1 == 2
See issue for details

fixes #144

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-16 06:11:07 -08:00
Anand Krishnamoorthi
7d32bd9377 Create rust-clippy.yml (#143) 2024-02-14 19:31:35 -08:00
Anand Krishnamoorthi
53b990f97d arc feature to enable using Engine and other data structures from multiple threads (#142)
* `arc` feature to make engine usable from multiple threads.

`arc` is turned on by default. When enabled, std::sync::Arc
will be used instead of std::rc::Rc. The former makes regorus
types like Engine, Value, ast nodes etc Send, allowing for
usability from multiple threads.
Arc would add a performance overhead though since the reference
counting will now become atomic.

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

* Make engine and related types Debug

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

* Input, Data as json. Evaluate bool queries.

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-13 10:23:19 -08:00
Anand Krishnamoorthi
13eb06e4be genpolicy tweaks (#141)
Allow `import input` instead of erroring out.
This import is redundant and has no effect.

Emit `print` messages to stderr onstead of stdout.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-11 16:52:12 -08:00
Anand Krishnamoorthi
3b2e639918 io.jwt.decode (#140)
* io.jwt.decode

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

* Update README

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

* Install musl-tools to compile ring crate

ring crate is a dependency of jsonwebtoken

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-09 23:36:16 -08:00
Anand Krishnamoorthi
a381c38a90 Use compact_rc (#139)
There is not much use for weak_counts in Rc for us.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-09 23:22:09 -08:00
Anand Krishnamoorthi
5044d54d18 Scripting tweaks (#138)
- No need to build with coverage by default on linux platforms
- Will add coverage formally in CI later
- Rename rust.yml to pr.yml

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-08 16:24:40 -08:00
Anand Krishnamoorthi
bb1b25ff2f chore: release (#137)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-08 07:09:22 -08:00
Anand Krishnamoorthi
5fd826d79a Update docs (#136)
* Add `time` to opa.passing. Disable WASM from rust.yml

Bindings will be tested using a separate workflow.
Also remove scripts that are no longer useful

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

* Remove alpha tag from version

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-08 06:56:14 -08:00
Anand Krishnamoorthi
d4dcbe7b9e Update README. Add link to playground. (#135)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-07 22:18:31 -08:00
Anand Krishnamoorthi
fc68bf9c8b Add devcontainer (#133)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-07 15:40:50 -08:00
Anand Krishnamoorthi
a95a9d21b3 Ability to add custom builtin functions (#132)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-07 15:15:55 -08:00
Burak
5717f9c249 Partially implement Go's time format (#130)
* Partially implement Go's time format

* Parse date only values

* Fix leap year handling in `time.diff`

* Disable failing test case
2024-02-07 15:12:34 -08:00
Anand Krishnamoorthi
dda525b989 C++ binding (#129)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-05 00:22:27 -08:00
Anand Krishnamoorthi
22260ac46f Bindings for C, C#, Golang (#124)
* FFI bindings

Generate C FFI as well as C# FFI

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

* Regorus C binding

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

* C# binding

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

* Golang binding

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-04 19:50:44 -08:00
dependabot[bot]
25b1ffe6d7 Update env_logger requirement from 0.10.0 to 0.11.1 (#123)
Updates the requirements on [env_logger](https://github.com/rust-cli/env_logger) to permit the latest version.
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.10.0...v0.11.1)

---
updated-dependencies:
- dependency-name: env_logger
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-02 16:17:16 -08:00
Anand Krishnamoorthi
bcde71b8f0 Create dependabot.yml 2024-02-02 14:54:58 -08:00
Anand Krishnamoorthi
1ab27b253b chore: release (#121)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-01 16:16:33 -08:00
Anand Krishnamoorthi
761d11ef48 Document bindings (#119)
* Instructions for WASM/JS binding

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

* Document Python, WASM/JS bindings

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-02-01 13:47:27 -08:00
Anand Krishnamoorthi
beea2274d3 Conform to OPA 0.61.0. (#118)
Implement `import rego.v1`
https://www.openpolicyagent.org/docs/latest/policy-language/#the-regov1-import

- `if` required before rule body
- import rego.v1 automatically imports future.keywords
- handle import shadowing
- data, input cannot be shadowed
- deprecated functions as disallowed
- rules must have assignment or body
- `contains` required for parital set

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-01-31 21:39:02 -08:00
Anand Krishnamoorthi
5799a3e6c4 Update publish-python.yml 2024-01-28 23:55:15 -08:00
Anand Krishnamoorthi
ca91c0ea20 Publish python packages (#117)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-01-28 23:12:06 -08:00
Anand Krishnamoorthi
bf75813c43 Publish wasm (#116)
* Create jekyll-gh-pages.yml

* Fix publish-wasm

- --release instead of -r
- set working directory
- use v4

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

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-01-28 16:45:52 -08:00
Anand Krishnamoorthi
35ec9c03ad Set working-directory for wasm-pack 2024-01-28 15:30:41 -08:00
Anand Krishnamoorthi
8ca863c661 Python bindings (#115)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-01-28 14:39:59 -08:00
Anand Krishnamoorthi
055bdd295f WASM binding (#114)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-01-27 22:14:29 -08:00
876 changed files with 210602 additions and 3789 deletions

View File

@@ -1,14 +1,2 @@
[build]
#target = "x86_64-unknown-linux-musl"
# Flags to enable code-coverage for all builds.
# These can be removed later.
#rustflags = ["-Cinstrument-coverage"]
incremental = true
[env]
# Name of coverage instrumentation log file.
LLVM_PROFILE_FILE="target/cargo-test-%p-%m.profraw"
[target.x86_64-unknown-linux-gnu]
rustflags = ["-Cinstrument-coverage"]
[alias]
xtask = "run --package xtask --"

View File

@@ -0,0 +1,44 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
{
"name": "Rust",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye",
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit"
]
}
},
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "8.0"
},
"ghcr.io/devcontainers/features/python:1": {}
}
// Use 'mounts' to make the cargo cache persistent in a Docker Volume.
// "mounts": [
// {
// "source": "devcontainer-cargo-cache-${devcontainerId}",
// "target": "/usr/local/cargo",
// "type": "volume"
// }
// ]
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "rustc --version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@@ -0,0 +1,29 @@
name: rust-toolchain
description: Setup Rust toolchain with specified version and components
inputs:
toolchain:
description: 'Rust toolchain version'
required: false
default: '1.92.0'
components:
description: 'Additional components to install'
required: false
default: 'clippy rustfmt'
targets:
description: 'Target architectures to install'
required: false
default: ''
runs:
using: composite
steps:
- shell: bash
run: |
rustup override set ${{ inputs.toolchain }}
if [ -n "${{ inputs.components }}" ]; then
rustup component add ${{ inputs.components }}
fi
if [ -n "${{ inputs.targets }}" ]; then
rustup target add ${{ inputs.targets }}
fi
cargo --version
rustc --version

101
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,101 @@
# 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:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
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"
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:
- "*"

193
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,193 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: "CodeQL Security Analysis"
on:
schedule:
# Run weekly on Wednesdays at 3:17 AM UTC
- cron: '17 3 * * 3'
workflow_dispatch:
# Allow manual triggering
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
# Rust analysis for main crate and Rust-based bindings
- language: rust
build-mode: none
working-directory: .
# C/C++ analysis for FFI bindings
- language: c-cpp
build-mode: manual
working-directory: bindings/ffi
# Python analysis for Python bindings
- language: python
build-mode: none
working-directory: bindings/python
# Java analysis for Java bindings
- language: java-kotlin
build-mode: manual
working-directory: bindings/java
# Go analysis for Go bindings
- language: go
build-mode: manual
working-directory: bindings/go
# C# analysis for C# bindings
- language: csharp
build-mode: manual
working-directory: bindings/csharp
# JavaScript analysis for WASM bindings
- language: javascript-typescript
build-mode: none
working-directory: bindings/wasm
steps:
- name: Checkout repository
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@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch workspace dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
if: matrix.language == 'c-cpp' || matrix.language == 'go' || matrix.language == 'csharp'
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
- name: Fetch Java crate dependencies
if: matrix.language == 'java-kotlin'
run: cargo fetch --locked --manifest-path bindings/java/Cargo.toml
- name: Setup Python
if: matrix.language == 'python'
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@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'corretto'
java-version: '8'
- name: Setup Go
if: matrix.language == 'go'
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: '1.21'
- name: Setup .NET
if: matrix.language == 'csharp'
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
global-json-file: ./bindings/csharp/global.json
- name: Invoke dotnet directly
if: matrix.language == 'csharp'
run: dotnet --info
- name: Setup Node.js
if: matrix.language == 'javascript-typescript'
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '18'
- name: Initialize CodeQL
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# Install additional build dependencies
- name: Install system dependencies
if: matrix.language == 'rust' || matrix.language == 'c-cpp'
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake
- name: Install Python build dependencies
if: matrix.language == 'python'
working-directory: ${{ matrix.working-directory }}
run: |
python -m pip install --upgrade pip
pip install maturin[patchelf] pytest
- name: Setup Ruby
if: matrix.language == 'rust' && contains(matrix.working-directory, 'ruby')
uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1.299.0
with:
ruby-version: '3.4.2'
bundler-cache: true
working-directory: bindings/ruby
- name: Install WASM build dependencies
if: matrix.language == 'javascript-typescript'
run: |
cargo install wasm-pack
# Manual build steps for different languages
- name: Build C/C++ bindings via xtask
if: matrix.language == 'c-cpp'
run: |
cargo xtask test-c --release --frozen
cargo xtask test-cpp --release --frozen --skip-ffi
cargo xtask test-c-no-std --release --frozen --skip-ffi
- name: Build Java bindings via xtask
if: matrix.language == 'java-kotlin'
run: cargo xtask test-java --release --frozen
- name: Build Go bindings via xtask
if: matrix.language == 'go'
run: cargo xtask test-go --release --frozen
- name: Build C# bindings manually
if: matrix.language == 'csharp'
working-directory: ${{ matrix.working-directory }}
run: |
# Temporary workaround: CodeQL's tracer replaces dotnet with a missing shim when cargo xtask test-csharp runs,
# so invoke dotnet directly here until the upstream fix lands.
# Ideal command once fixed: cargo xtask test-csharp --release
# Build the FFI library that C# bindings access via P/Invoke
cd ../ffi
cargo build --release --locked
cd ../csharp
# Restore NuGet packages and build .NET assemblies in release mode
dotnet restore Regorus/Regorus.csproj
dotnet build Regorus/Regorus.csproj --no-restore /p:Configuration=Release /p:IgnoreMissingArtifacts=true
- name: Build WASM bindings via xtask
if: matrix.language == 'javascript-typescript'
run: cargo xtask build-wasm --release
- name: Perform CodeQL Analysis
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

37
.github/workflows/pr-extensions.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: tests/release-extensions
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-latest
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
- name: Fetch dependencies
run: cargo fetch --locked
- name: Run rego extensions CI suite
run: >-
cargo xtask ci-release --frozen --features rego-extensions
--skip-all-features-build --skip-no-default-features-tests
--skip-azure-policy --skip-azure-rbac
--opa-features "opa-testutil,serde_json/arbitrary_precision,rego-extensions"

33
.github/workflows/pr.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: tests/release
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-latest
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
- name: Fetch dependencies
run: cargo fetch --locked
- name: Run release CI suite
run: cargo xtask ci-release --frozen

94
.github/workflows/publish-java.yml vendored Normal file
View File

@@ -0,0 +1,94 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: publish-java
on:
workflow_dispatch:
permissions:
contents: read
jobs:
build:
name: Build for ${{ matrix.target }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
extension: so
build_cmd: zigbuild
glibc: "2.17"
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
extension: so
build_cmd: zigbuild
glibc: "2.17"
- target: x86_64-apple-darwin
os: macos-latest
extension: dylib
- target: aarch64-apple-darwin
os: macos-latest
extension: dylib
- target: x86_64-pc-windows-msvc
os: windows-latest
extension: dll
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: 8
distribution: "corretto"
- uses: ./.github/actions/toolchains/rust
with:
targets: ${{ matrix.target }}
- if: ${{ matrix.build_cmd == 'zigbuild' }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- if: ${{ matrix.build_cmd == 'zigbuild' }}
run: pip install cargo-zigbuild
- run: cargo fetch --locked
- run: cargo fetch --locked --manifest-path bindings/java/Cargo.toml
- 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: native-libraries-${{ matrix.target }}
path: native/
release:
name: Release
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 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@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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: built-jars
path: ./bindings/java/target/regorus-java-*.jar
- run: mvn deploy
working-directory: ./bindings/java
env:
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}

130
.github/workflows/publish-python.yml vendored Normal file
View File

@@ -0,0 +1,130 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# This file is autogenerated by maturin v1.4.0
# To update, run
#
# maturin generate-ci --manifest-path bindings/python/Cargo.toml github
#
name: publish-python
on:
workflow_dispatch:
permissions:
contents: read
jobs:
linux:
runs-on: ubuntu-latest
strategy:
matrix:
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
steps:
- 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
- name: Build Python extension
run: |
cargo fetch --locked
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --target ${{ matrix.target }} --frozen
working-directory: bindings/python
- name: Build wheels
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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: wheels-linux-${{ matrix.target }}
path: dist
windows:
runs-on: windows-latest
strategy:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.10'
architecture: ${{ matrix.target }}
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
cargo fetch --locked
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --target ${{ matrix.host.target }} --frozen
working-directory: bindings/python
- name: Build wheels
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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: wheels-windows-${{ matrix.target }}
path: dist
macos:
runs-on: macos-latest
strategy:
matrix:
target: [x86_64, aarch64, universal2-apple-darwin]
steps:
- 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
- name: Build Python extension
run: |
cargo fetch --locked
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --target ${{ matrix.host.target }} --frozen
working-directory: bindings/python
- name: Build wheels
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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: wheels-macos-${{ matrix.host.target }}
path: dist
release:
name: Release
runs-on: ubuntu-latest
# Commented out for initial release.
# if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos]
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: wheels-*
merge-multiple: true
path: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:
command: upload
args: --non-interactive --skip-existing *

35
.github/workflows/publish-wasm.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: publish-wasm
permissions:
pull-requests: write
contents: write
on:
workflow_dispatch:
jobs:
publish-wasm:
name: publish
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Build
run: wasm-pack build --target nodejs --release
working-directory: ./bindings/wasm
- name: Publish
run: wasm-pack publish --target nodejs
working-directory: ./bindings/wasm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}

View File

@@ -1,24 +1,29 @@
# 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:
name: Release-plz
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: ./.github/actions/toolchains/rust
- name: Run release-plz
uses: MarcoIeni/release-plz-action@v0.5
uses: MarcoIeni/release-plz-action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

58
.github/workflows/rust-clippy.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
# 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
# documentation.
# rust-clippy is a tool that runs a bunch of lints to catch common
# mistakes in your Rust code and help improve your Rust code.
# More details at https://github.com/rust-lang/rust-clippy
# and https://rust-lang.github.io/rust-clippy/
name: rust-clippy analyze
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
workflow_dispatch:
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
rust-clippy-analyze:
name: Run rust-clippy analyzing
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
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@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
- name: Install required cargo
run: cargo install clippy-sarif sarif-fmt
- name: Fetch
run: cargo fetch --locked
- name: Run rust-clippy
run: cargo xtask clippy --sarif rust-clippy-results.sarif
- name: Upload analysis results to GitHub
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,39 +0,0 @@
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Add musl target
run: rustup target add x86_64-unknown-linux-musl
- name: Format Check
run: cargo fmt --check
- name: Build
run: cargo build --verbose
- name: Build Tests
run: cargo build --all-targets --verbose
- name: Clippy
run: cargo clippy --all-targets --no-deps -- -Dwarnings
- name: Run tests
run: cargo test -r --verbose
- name: Build (MUSL)
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl
- name: Run tests (MUSL)
run: cargo test -r --verbose --target x86_64-unknown-linux-musl
- name: Run tests (ACI)
run: cargo test -r --test aci
- name: Run tests (OPA Conformance)
run: >-
cargo test -r --test opa -- $(tr '\n' ' ' < tests/opa.passing)

44
.github/workflows/test-c-cpp.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/c-cpp
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
- name: Setup gcc, g++, cmake, ninja
run: sudo apt update && sudo apt install -y gcc g++ cmake ninja-build
- name: Test C binding via xtask
run: cargo xtask test-c --release --frozen
- name: Test C (no-std) binding via xtask
run: cargo xtask test-c-nostd --release --frozen --skip-ffi
- name: Test C++ binding via xtask
run: cargo xtask test-cpp --release --frozen --skip-ffi

166
.github/workflows/test-csharp.yml vendored Normal file
View File

@@ -0,0 +1,166 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/csharp
on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
VersionSuffix: ${{ github.event_name == 'workflow_dispatch' && 'manualtrigger' || null }}
jobs:
build-ffi:
name: 'Build Regorus FFI: (${{ matrix.runtime.target }})'
runs-on: ${{ matrix.runtime.os }}
strategy:
# let us get failures from other jobs even if one fails
fail-fast: false
matrix:
runtime:
- os: windows-latest
target: x86_64-pc-windows-msvc
libpath: |
**/release/regorus_ffi.dll
**/release/regorus_ffi.pdb
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
libpath: |
**/release/libregorus_ffi.so
- os: macos-latest
target: aarch64-apple-darwin
libpath: |
**/release/libregorus_ffi.dylib
steps:
- name: Checkout repository
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@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml --target ${{ matrix.runtime.target }}
- name: Build Regorus FFI via xtask
run: cargo xtask build-ffi --release --target ${{ matrix.runtime.target }}
- name: Upload regorus ffi shared library
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.
path: ${{ matrix.runtime.libpath }}
if-no-files-found: error
retention-days: 1
build-csharp:
name: 'Build Regorus nuget'
runs-on: ubuntu-latest
needs: build-ffi
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- 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@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Download regorus ffi shared libraries
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: regorus-ffi-artifacts-*
merge-multiple: true
path: ./bindings/csharp/Regorus/tmp
- name: Display regorus ffi artifacts
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 --repository-commit ${{ github.sha }} --include-symbols
- name: Upload Regorus nuget
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: regorus-nuget
path: |
bindings/csharp/Regorus/bin/Release/Regorus*.nupkg
bindings/csharp/Regorus/bin/Release/Regorus*.snupkg
if-no-files-found: error
retention-days: 1
test-nuget:
name: 'Test Regorus Nuget: (${{ matrix.runtime.target }})'
needs: build-csharp
runs-on: ${{ matrix.runtime.os }}
strategy:
# let us get failures from other jobs even if one fails
fail-fast: false
matrix:
runtime:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- 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@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Download regorus nuget
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: regorus-nuget
path: ./bindings/csharp/Regorus/bin/Release
- name: Display regorus nuget
run: ls -R ./bindings/csharp/Regorus/bin/Release
- name: Run C# tests via xtask
run: cargo xtask test-csharp --release --clean --nuget-dir bindings/csharp/Regorus/bin/Release

34
.github/workflows/test-ffi.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/ffi
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
- name: Test FFI
run: cargo xtask test-ffi --release --frozen

38
.github/workflows/test-go.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/go
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
architecture: x64
- name: Test Go binding via xtask
run: cargo xtask test-go --release --frozen

39
.github/workflows/test-java.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/java
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 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@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch Java crate dependencies
run: cargo fetch --locked --manifest-path bindings/java/Cargo.toml
- name: Run Java smoke tests via xtask
run: cargo xtask test-java --release --frozen

38
.github/workflows/test-musl.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: musl
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-latest
steps:
- 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@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch MUSL target dependencies
run: cargo fetch --locked --target x86_64-unknown-linux-musl
- name: Install musl-gcc
run: sudo apt update && sudo apt install -y musl-tools
- name: Run MUSL suite via xtask
run: cargo xtask test-musl --release --frozen --target x86_64-unknown-linux-musl

37
.github/workflows/test-no-std.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/no-std
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/toolchains/rust
with:
targets: thumbv7m-none-eabi
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch ensure_no_std crate dependencies
run: cargo fetch --locked --manifest-path tests/ensure_no_std/Cargo.toml --target thumbv7m-none-eabi
- name: Test no-std
run: cargo xtask test-no-std --release --frozen

91
.github/workflows/test-python.yml vendored Normal file
View File

@@ -0,0 +1,91 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/python
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
build:
strategy:
matrix:
host:
- name: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- name: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.host.name }}
steps:
- name: Checkout repository
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@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch Python crate dependencies
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml --target ${{ matrix.host.target }}
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.10"
architecture: x64
- name: Install maturin
run: python -m pip install maturin==1.5.1
- name: Build Python wheel via xtask
run: cargo xtask build-python --release --target ${{ matrix.host.target }} --target-dir bindings/python/dist --frozen
- name: Upload wheel artefacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: regorus-wheel-${{ matrix.host.name }}
path: bindings/python/dist/regorus-*.whl
test:
needs: build
strategy:
matrix:
host: [ubuntu-24.04, ubuntu-22.04, windows-latest]
python-version: ["3.10", "3.11", "3.12", "3.13"]
runs-on: ${{ matrix.host }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch Python crate dependencies
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: Install maturin
run: python -m pip install maturin==1.5.1
- name: Run Python smoke tests via xtask
run: cargo xtask test-python --release --python python

44
.github/workflows/test-ruby.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/ruby
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
if: false # temporarily disabled
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup Ruby and Rust
uses: oxidize-rb/actions/setup-ruby-and-rust@e5f9a49a7812a078584072f6e3f657ad247c8771 # v1.26
with:
bundler: 2.6.5
rubygems: 3.6.5
ruby-version: "3.4.2"
rustup-toolchain: "stable"
bundler-cache: true
cargo-cache: true
working-directory: "bindings/ruby"
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch Ruby crate dependencies
run: cargo fetch --locked --manifest-path bindings/ruby/Cargo.toml
- name: Run ruby tests
run: cargo xtask test-ruby --release --frozen

44
.github/workflows/test-wasm.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/wasm
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
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@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch WASM crate dependencies
run: cargo fetch --locked --manifest-path bindings/wasm/Cargo.toml
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22
- name: Install wasmlpack
run: cargo install wasm-pack
- name: Test wasm binding via xtask
run: cargo xtask test-wasm --release --frozen --node node

33
.github/workflows/tests-debug.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: tests/debug
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-latest
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
- name: Fetch dependencies
run: cargo fetch --locked
- name: Run debug CI suite
run: cargo xtask ci-debug --frozen

53
.gitignore vendored
View File

@@ -1,10 +1,8 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
**/wheels/
**/__pycache__/
# These are backup files generated by rustfmt
**/*.rs.bk
@@ -13,4 +11,49 @@ Cargo.lock
.vscode/
# worktrees
worktrees/
worktrees/
# build folders
**/build
# Generated C# bindings
**/*.g.cs
# Generated C, C++ headers
bindings/ffi/regorus.h
bindings/ffi/regorus.ffi.hpp
bindings/*/target
# Temporary commit message files
.commit-msg.txt
# Local planning docs
docs/plans/
# C# build folders
**bin
**obj
bindings/csharp/.nuget/
# Bundler binstubs regenerated during ruby setup
bindings/ruby/bin/
# Visual Studio folders
**/*.vs
# Visual Studio solution files
*.sln
# JetBrains IDEs files
.idea/
# Java build artifacts
**/*.class
**/*.jar
bindings/java/.classpath
bindings/java/.project
bindings/java/.settings/
# Emacs temporary files
*~

View File

@@ -6,6 +6,399 @@ 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
- [**breaking**] Indexes for nodes in the AST ([#414](https://github.com/anakrish/regorus/pull/414))
- Updates for Policy Framework ([#405](https://github.com/anakrish/regorus/pull/405))
- Regorus nuget package ([#383](https://github.com/anakrish/regorus/pull/383))
### Fixed
- emit import warning to stderr ([#430](https://github.com/anakrish/regorus/pull/430))
- Clippy warnings ([#424](https://github.com/anakrish/regorus/pull/424))
- Disallow else blocks for set rules ([#403](https://github.com/anakrish/regorus/pull/403))
- [**breaking**] Remove cryptographic builtins ([#396](https://github.com/anakrish/regorus/pull/396))
- [**breaking**] Fix glob.match behavior in presence of : ([#390](https://github.com/anakrish/regorus/pull/390))
- C# EvalRule ([#387](https://github.com/anakrish/regorus/pull/387))
### Other
- Update release-plz action to v0.5.108 ([#431](https://github.com/anakrish/regorus/pull/431))
- Early return for 'some in' statement ([#427](https://github.com/anakrish/regorus/pull/427))
- Support manually generating C# bindings via Github action and add a README ([#423](https://github.com/anakrish/regorus/pull/423))
- Make the bindings/cpp CMake project installable ([#416](https://github.com/anakrish/regorus/pull/416))
- *(deps)* bump clap from 4.5.38 to 4.5.39 ([#415](https://github.com/anakrish/regorus/pull/415))
- *(deps)* Update criterion and other deps ([#412](https://github.com/anakrish/regorus/pull/412))
- Basic benchmarking setup with Criterion ([#408](https://github.com/anakrish/regorus/pull/408))
- Default to Rego v1 in `regorus parse` ([#407](https://github.com/anakrish/regorus/pull/407))
- *(deps)* bump clap from 4.5.37 to 4.5.38 ([#406](https://github.com/anakrish/regorus/pull/406))
- Update dependencies ([#401](https://github.com/anakrish/regorus/pull/401))
- Add C# test examples ([#397](https://github.com/anakrish/regorus/pull/397))
- *(deps)* bump clap from 4.5.35 to 4.5.36 ([#395](https://github.com/anakrish/regorus/pull/395))
- Python binding portability ([#388](https://github.com/anakrish/regorus/pull/388))
- *(deps)* bump clap from 4.5.34 to 4.5.35 ([#389](https://github.com/anakrish/regorus/pull/389))
- Use VersionPrefix and VersionSuffix ([#385](https://github.com/anakrish/regorus/pull/385))
- Check-in Cargo.lock files and lockdown .net ([#384](https://github.com/anakrish/regorus/pull/384))
## [0.4.0](https://github.com/microsoft/regorus/compare/regorus-v0.3.0...regorus-v0.4.0) - 2025-03-14
### Fixed
- [**breaking**] Update ruby json dependency ([#381](https://github.com/microsoft/regorus/pull/381))
- [**breaking**] Remove ring dependency ([#380](https://github.com/microsoft/regorus/pull/380))
- [**breaking**] Remove sha1 dependency ([#379](https://github.com/microsoft/regorus/pull/379))
### Other
- Specify optimization flags ([#378](https://github.com/microsoft/regorus/pull/378))
## [0.3.0](https://github.com/microsoft/regorus/compare/regorus-v0.2.8...regorus-v0.3.0) - 2025-03-10
### Added
- [**breaking**] Update to OPA v1.2.0 ([#373](https://github.com/microsoft/regorus/pull/373))
### Other
- *(deps)* update pyo3 requirement from 0.23.5 to 0.24.0 ([#375](https://github.com/microsoft/regorus/pull/375))
- Update ruby binding deps, ruby gem version 0.2.3 ([#374](https://github.com/microsoft/regorus/pull/374))
- *(deps)* update pyo3 requirement from 0.22.0 to 0.23.5 ([#372](https://github.com/microsoft/regorus/pull/372))
- *(deps)* update rand requirement from 0.8.5 to 0.9.0 ([#370](https://github.com/microsoft/regorus/pull/370))
- *(deps)* update cbindgen requirement from 0.27.0 to 0.28.0 ([#361](https://github.com/microsoft/regorus/pull/361))
- Fix typo in README.md ([#366](https://github.com/microsoft/regorus/pull/366))
- Update dependencies ([#369](https://github.com/microsoft/regorus/pull/369))
- Fix clippy warning for result? ([#362](https://github.com/microsoft/regorus/pull/362))
- *(deps)* update itertools requirement from 0.13.0 to 0.14.0 ([#357](https://github.com/microsoft/regorus/pull/357))
- *(deps)* update jsonschema requirement from 0.26.1 to 0.28.1 ([#356](https://github.com/microsoft/regorus/pull/356))
- resolve anyhow compile errors ([#355](https://github.com/microsoft/regorus/pull/355))
- *(deps)* update prettydiff requirement from 0.7.0 to 0.8.0 ([#348](https://github.com/microsoft/regorus/pull/348))
## [0.2.8](https://github.com/microsoft/regorus/compare/regorus-v0.2.7...regorus-v0.2.8) - 2024-11-06
### Other
- *(deps)* update jsonschema requirement from 0.24.0 to 0.26.1 ([#343](https://github.com/microsoft/regorus/pull/343))
- Update to OPA v0.70.0 ([#341](https://github.com/microsoft/regorus/pull/341))
## [0.2.7](https://github.com/microsoft/regorus/compare/regorus-v0.2.6...regorus-v0.2.7) - 2024-10-22
### Fixed
- docs failing to build ([#334](https://github.com/microsoft/regorus/pull/334))
### Other
- *(deps)* update jsonschema requirement from 0.23.0 to 0.24.0 ([#332](https://github.com/microsoft/regorus/pull/332))
- *(deps)* update jsonschema requirement from 0.22.3 to 0.23.0 ([#331](https://github.com/microsoft/regorus/pull/331))
## [0.2.6](https://github.com/microsoft/regorus/compare/regorus-v0.2.5...regorus-v0.2.6) - 2024-10-09
### Added
- integer conversion functions for Value ([#328](https://github.com/microsoft/regorus/pull/328))
### Other
- update to OPA v0.69.0 ([#327](https://github.com/microsoft/regorus/pull/327))
- *(deps)* update jsonschema requirement from 0.21.0 to 0.22.3 ([#326](https://github.com/microsoft/regorus/pull/326))
- *(deps)* update jsonschema requirement from 0.20.0 to 0.21.0 ([#325](https://github.com/microsoft/regorus/pull/325))
- update to jsonschema 0.20.0 ([#323](https://github.com/microsoft/regorus/pull/323))
## [0.2.5](https://github.com/microsoft/regorus/compare/regorus-v0.2.4...regorus-v0.2.5) - 2024-09-18
### Added
- or keyword ([#315](https://github.com/microsoft/regorus/pull/315))
### Fixed
- Null terminate C# strings in Rust boundary ([#318](https://github.com/microsoft/regorus/pull/318))
- Update readme with correct path to example policy ([#312](https://github.com/microsoft/regorus/pull/312))
### Other
- Update jsonschema requirement from 0.18.0 to 0.19.1 ([#317](https://github.com/microsoft/regorus/pull/317))
- Update chrono-tz requirement from 0.8.5 to 0.10.0 ([#316](https://github.com/microsoft/regorus/pull/316))
- Add tests for builtin strings::lower method ([#313](https://github.com/microsoft/regorus/pull/313))
- Add tests for builtin strings::indexof method ([#311](https://github.com/microsoft/regorus/pull/311))
## [0.2.4](https://github.com/microsoft/regorus/compare/regorus-v0.2.3...regorus-v0.2.4) - 2024-09-04
### Added
- OPA v0.68.0. Engine::set_rego_v1 ([#305](https://github.com/microsoft/regorus/pull/305))
### Fixed
- Handle parsing corner cases ([#309](https://github.com/microsoft/regorus/pull/309))
- Propagate errors encountered in argument evaluation ([#308](https://github.com/microsoft/regorus/pull/308))
- Issues [#302](https://github.com/microsoft/regorus/pull/302), [#303](https://github.com/microsoft/regorus/pull/303) ([#304](https://github.com/microsoft/regorus/pull/304))
## [0.2.3](https://github.com/microsoft/regorus/compare/regorus-v0.2.2...regorus-v0.2.3) - 2024-08-16
### Fixed
- Match OPA behavior for split ([#295](https://github.com/microsoft/regorus/pull/295))
- Merge data to init document ([#293](https://github.com/microsoft/regorus/pull/293))
### Other
- Update cbindgen requirement from 0.26.0 to 0.27.0 ([#296](https://github.com/microsoft/regorus/pull/296))
- Bump rexml in /bindings/ruby in the bundler group across 1 directory ([#294](https://github.com/microsoft/regorus/pull/294))
- Update csbindgen requirement from =1.9.0 to =1.9.3 ([#292](https://github.com/microsoft/regorus/pull/292))
## [0.2.2](https://github.com/microsoft/regorus/compare/regorus-v0.2.1...regorus-v0.2.2) - 2024-07-28
### Added
- Update to opa v0.67.0 ([#286](https://github.com/microsoft/regorus/pull/286))
### Fixed
- Handle aliases in scheduler ([#285](https://github.com/microsoft/regorus/pull/285))
### Other
- Update readme ([#288](https://github.com/microsoft/regorus/pull/288))
- Update binding versions ([#287](https://github.com/microsoft/regorus/pull/287))
- build.rs create hooks dir if not exists ([#283](https://github.com/microsoft/regorus/pull/283))
- add extension_list example ([#281](https://github.com/microsoft/regorus/pull/281))
- Fix build break ([#278](https://github.com/microsoft/regorus/pull/278))
- Update pyo3 requirement from 0.21.0 to 0.22.0 ([#275](https://github.com/microsoft/regorus/pull/275))
- Update to OPA v0.66.0 ([#274](https://github.com/microsoft/regorus/pull/274))
## [0.2.1](https://github.com/microsoft/regorus/compare/regorus-v0.2.0...regorus-v0.2.1) - 2024-06-19
### Added
- get_policies: Way to obtain policy files and content ([#267](https://github.com/microsoft/regorus/pull/267))
### Other
- Fix c,cpp,no-std binding examples ([#272](https://github.com/microsoft/regorus/pull/272))
- Update binding versions for next release ([#270](https://github.com/microsoft/regorus/pull/270))
- rename method from 'Clone' to 'clone' in 'Engine' class to match the java naming convention and definiont in the of java.lang.Object. ([#268](https://github.com/microsoft/regorus/pull/268))
- Suppress clippy unused warning ([#269](https://github.com/microsoft/regorus/pull/269))
- Provide ability to get JSON representation of policy AST ([#266](https://github.com/microsoft/regorus/pull/266))
- Update OPA tests to v0.65.0 ([#264](https://github.com/microsoft/regorus/pull/264))
- Allow lexer to be used for other policy languages ([#262](https://github.com/microsoft/regorus/pull/262))
## [0.2.0](https://github.com/microsoft/regorus/compare/regorus-v0.1.5...regorus-v0.2.0) - 2024-05-30
### Other
- Add release-plz config to publish only regorus package ([#259](https://github.com/microsoft/regorus/pull/259))
- Revert "chore: release v0.2.0 ([#257](https://github.com/microsoft/regorus/pull/257))" ([#258](https://github.com/microsoft/regorus/pull/258))
- release v0.2.0 ([#257](https://github.com/microsoft/regorus/pull/257))
- Fix release-plz hash ([#256](https://github.com/microsoft/regorus/pull/256))
- non collections should evaluate to false ([#253](https://github.com/microsoft/regorus/pull/253))
- Fix merge issue ([#252](https://github.com/microsoft/regorus/pull/252))
- Update bindings to include newer APIs ([#250](https://github.com/microsoft/regorus/pull/250))
- update ruby bindings version to 0.1.5, bump deps ([#251](https://github.com/microsoft/regorus/pull/251))
- Use correct docsrs feature annotation ([#248](https://github.com/microsoft/regorus/pull/248))
- Lockdown kata test prints as well as prints of various values ([#249](https://github.com/microsoft/regorus/pull/249))
- Fix bindings and add CI tests ([#247](https://github.com/microsoft/regorus/pull/247))
- Add test-ruby CI for github actions ([#244](https://github.com/microsoft/regorus/pull/244))
- Update `README.md` for Java bindings to mention we don't publish to ([#246](https://github.com/microsoft/regorus/pull/246))
- Update itertools requirement from 0.12.1 to 0.13.0 ([#245](https://github.com/microsoft/regorus/pull/245))
- Update ruby bindings for add_policy and add_policy_from_file to return package name ([#240](https://github.com/microsoft/regorus/pull/240))
- Provide a way to obtain package names of loaded policies ([#239](https://github.com/microsoft/regorus/pull/239))
- `c_no_std` binding to show use in C freestanding environments. ([#238](https://github.com/microsoft/regorus/pull/238))
- Bump rexml in /bindings/ruby in the bundler group across 1 directory ([#236](https://github.com/microsoft/regorus/pull/236))
- Update prettydiff requirement from 0.6.4 to 0.7.0 ([#234](https://github.com/microsoft/regorus/pull/234))
- Update jsonschema requirement from 0.17.1 to 0.18.0 ([#235](https://github.com/microsoft/regorus/pull/235))
- no_std support ([#232](https://github.com/microsoft/regorus/pull/232))
- add `std` feature ([#231](https://github.com/microsoft/regorus/pull/231))
- Tests from MSFT fork of kata-containers ([#230](https://github.com/microsoft/regorus/pull/230))
- Use alloc, core instead of std ([#225](https://github.com/microsoft/regorus/pull/225))
## [0.1.5](https://github.com/microsoft/regorus/compare/regorus-v0.1.4...regorus-v0.1.5) - 2024-05-07
### Added
- Support policy files greater than 64KB in size ([#217](https://github.com/microsoft/regorus/pull/217))
- Add tests for kata containers policies ([#221](https://github.com/microsoft/regorus/pull/221))
- Support for OPA v0.64.0 ([#219](https://github.com/microsoft/regorus/pull/219))
- New builtin `json.marshal_with_options`
### Changed
- Improve example in readme ([#224](https://github.com/microsoft/regorus/pull/224))
### Fixed
- OPA Conformance: Do not interpret # within regular string ([#216](https://github.com/microsoft/regorus/pull/216))
## [0.1.4](https://github.com/microsoft/regorus/compare/regorus-v0.1.3...regorus-v0.1.4) - 2024-04-22
### Other
- early return ([#189](https://github.com/microsoft/regorus/pull/189))
- Fix anyhow dependency issues ([#208](https://github.com/microsoft/regorus/pull/208))
- remove unused compact-rc dependency ([#207](https://github.com/microsoft/regorus/pull/207))
## [0.1.3](https://github.com/microsoft/regorus/compare/regorus-v0.1.2...regorus-v0.1.3) - 2024-04-11
### Other
- Add a note in example to prefer eval_rule over eval_query ([#204](https://github.com/microsoft/regorus/pull/204))
- Do not enable serde_json/arbitrary_precision by default ([#203](https://github.com/microsoft/regorus/pull/203))
- Rewrite so that code compiles with chrono_tz 0.8.5 and 0.9.0 ([#201](https://github.com/microsoft/regorus/pull/201))
- update ruby bindings ([#200](https://github.com/microsoft/regorus/pull/200))
- Store Value instances in AST for strings, numbers and idents ([#197](https://github.com/microsoft/regorus/pull/197))
- :Value> and From<serde_yaml::Value> ([#196](https://github.com/microsoft/regorus/pull/196))
- Build dependency on git only if opa.runtime feature is enabled. ([#194](https://github.com/microsoft/regorus/pull/194))
- Update to opa v0.63.0 ([#192](https://github.com/microsoft/regorus/pull/192))
- Update pyo3 requirement from 0.20.2 to 0.21.0 ([#190](https://github.com/microsoft/regorus/pull/190))
- Ruby bindings for existing FFI methods, plus eval_rule() ([#188](https://github.com/microsoft/regorus/pull/188))
- Evaluate rules directly instead of queries ([#186](https://github.com/microsoft/regorus/pull/186))
- Remove cruft. ([#184](https://github.com/microsoft/regorus/pull/184))
## [0.1.2](https://github.com/microsoft/regorus/compare/regorus-v0.1.1...regorus-v0.1.2) - 2024-03-22
### Other
- Handle non simple refs in chained expressions ([#182](https://github.com/microsoft/regorus/pull/182))
- Ability to gather print statements ([#179](https://github.com/microsoft/regorus/pull/179))
- Top-down evaluation ([#177](https://github.com/microsoft/regorus/pull/177))
- Make unary `-` operator OPA compatible. ([#175](https://github.com/microsoft/regorus/pull/175))
- Don't use deprecated chrono `Duration` methods ([#173](https://github.com/microsoft/regorus/pull/173))
- Propagate Undefined in object expressions ([#171](https://github.com/microsoft/regorus/pull/171))
- Bump to OPA v0.62.0 ([#169](https://github.com/microsoft/regorus/pull/169))
- Fix regression ([#164](https://github.com/microsoft/regorus/pull/164))
- Separately keep track of whether rules have been evaluated or not ([#163](https://github.com/microsoft/regorus/pull/163))
- Link Linux libraries against glibc 2.17 using `cargo-zigbuild` ([#158](https://github.com/microsoft/regorus/pull/158))
## [0.1.1](https://github.com/microsoft/regorus/compare/regorus-v0.1.0...regorus-v0.1.1) - 2024-02-23
### Other
- Handle else block without body ([#155](https://github.com/microsoft/regorus/pull/155))
- Ignore errors from builtin functions in non strict mode ([#154](https://github.com/microsoft/regorus/pull/154))
- Java publishing ([#151](https://github.com/microsoft/regorus/pull/151))
- Document coverage feature; Convenience query functions ([#152](https://github.com/microsoft/regorus/pull/152))
- Policy Coverage ([#149](https://github.com/microsoft/regorus/pull/149))
- Initial implementation of policy coverage ([#146](https://github.com/microsoft/regorus/pull/146))
- Java bindings ([#147](https://github.com/microsoft/regorus/pull/147))
- Preserve false in single-expression queries ([#145](https://github.com/microsoft/regorus/pull/145))
- Create rust-clippy.yml ([#143](https://github.com/microsoft/regorus/pull/143))
- `arc` feature to enable using Engine and other data structures from multiple threads ([#142](https://github.com/microsoft/regorus/pull/142))
- genpolicy tweaks ([#141](https://github.com/microsoft/regorus/pull/141))
- io.jwt.decode ([#140](https://github.com/microsoft/regorus/pull/140))
- Use compact_rc ([#139](https://github.com/microsoft/regorus/pull/139))
- Scripting tweaks ([#138](https://github.com/microsoft/regorus/pull/138))
## [0.1.0-alpha.3](https://github.com/microsoft/regorus/compare/regorus-v0.1.0-alpha.2...regorus-v0.1.0-alpha.3) - 2024-02-01
### Fixed
- fix bitwise.and and add tests ([#19](https://github.com/microsoft/regorus/pull/19))
### Other
- Document bindings ([#119](https://github.com/microsoft/regorus/pull/119))
- Conform to OPA 0.61.0. ([#118](https://github.com/microsoft/regorus/pull/118))
- Update publish-python.yml
- Publish python packages ([#117](https://github.com/microsoft/regorus/pull/117))
- Publish wasm ([#116](https://github.com/microsoft/regorus/pull/116))
- Set working-directory for wasm-pack
- Python bindings ([#115](https://github.com/microsoft/regorus/pull/115))
- WASM binding ([#114](https://github.com/microsoft/regorus/pull/114))
- release ([#112](https://github.com/microsoft/regorus/pull/112))
- Improve crate documentation ([#111](https://github.com/microsoft/regorus/pull/111))
- Try out manual trigger for release-plz ([#110](https://github.com/microsoft/regorus/pull/110))
- - Document Location, Expression, QueryResult ([#109](https://github.com/microsoft/regorus/pull/109))
- Update Cargo.toml ([#108](https://github.com/microsoft/regorus/pull/108))
- Change version to `0.1.0-alpha.1` ([#107](https://github.com/microsoft/regorus/pull/107))
- Add crate documentation ([#106](https://github.com/microsoft/regorus/pull/106))
- Release preparation ([#105](https://github.com/microsoft/regorus/pull/105))
- Update READEME.md with current status, grammar etc. ([#102](https://github.com/microsoft/regorus/pull/102))
- Implement builtin `time.parse_duration_ns` method ([#100](https://github.com/microsoft/regorus/pull/100))
- Implement import keyword ([#101](https://github.com/microsoft/regorus/pull/101))
- OPA conformance: Pass refheads test suite ([#90](https://github.com/microsoft/regorus/pull/90))
- OPA conformance: Ensure that `withkeyword` OPA tests pass ([#88](https://github.com/microsoft/regorus/pull/88))
- Handle walk builtin as a loop expression ([#86](https://github.com/microsoft/regorus/pull/86))
- Implement most of the builtin `time` module ([#82](https://github.com/microsoft/regorus/pull/82))
- OPA Conformance
- OPA conformance ([#81](https://github.com/microsoft/regorus/pull/81))
- More OPA conformance ([#77](https://github.com/microsoft/regorus/pull/77))
- OPA conformance ([#71](https://github.com/microsoft/regorus/pull/71))
- Builtin UUID module ([#68](https://github.com/microsoft/regorus/pull/68))
- Add tests for builtin `string::format_int` method ([#65](https://github.com/microsoft/regorus/pull/65))
- More builtins and semantic improvements ([#66](https://github.com/microsoft/regorus/pull/66))
- More OPA conformance; in-progress: ability to trace interpreter ([#63](https://github.com/microsoft/regorus/pull/63))
- More OPA conformant semantics ([#62](https://github.com/microsoft/regorus/pull/62))
- Updated readme. Added bundle support. ([#61](https://github.com/microsoft/regorus/pull/61))
- crypto builtins ([#57](https://github.com/microsoft/regorus/pull/57))
- Regex and Glob builtins ([#56](https://github.com/microsoft/regorus/pull/56))
- Formalize concept of a Number ([#55](https://github.com/microsoft/regorus/pull/55))
- Lock down ACI tests and more OPA test folders ([#54](https://github.com/microsoft/regorus/pull/54))
- Fix scheduling regression ([#53](https://github.com/microsoft/regorus/pull/53))
- add full api to engine ([#50](https://github.com/microsoft/regorus/pull/50))
- Use Rc<str> instead of string. ([#52](https://github.com/microsoft/regorus/pull/52))
- More library functions ([#51](https://github.com/microsoft/regorus/pull/51))
- Added semver.is_valid and semver.compare ([#49](https://github.com/microsoft/regorus/pull/49))
- OPA conformance tests ([#45](https://github.com/microsoft/regorus/pull/45))
- Avoid dependency on `source lifetime. ([#43](https://github.com/microsoft/regorus/pull/43))
- Allow with modifier for builtin and user functions ([#42](https://github.com/microsoft/regorus/pull/42))
- Special cases of refs to data ([#41](https://github.com/microsoft/regorus/pull/41))
- Fix scheduling statements that don't create bindings ([#40](https://github.com/microsoft/regorus/pull/40))
- Ability to run the OPA testsuite ([#39](https://github.com/microsoft/regorus/pull/39))
- Engine ([#38](https://github.com/microsoft/regorus/pull/38))
- Use Ref for storing ast nodes in collections. ([#37](https://github.com/microsoft/regorus/pull/37))
- all, any deprecated functions ([#35](https://github.com/microsoft/regorus/pull/35))
- all, any deprecated functions ([#34](https://github.com/microsoft/regorus/pull/34))
- Improvements ([#33](https://github.com/microsoft/regorus/pull/33))
- Order query expression results ([#32](https://github.com/microsoft/regorus/pull/32))
- Scheduling of statements in user queries ([#31](https://github.com/microsoft/regorus/pull/31))
- eval, lex, parse commands ([#30](https://github.com/microsoft/regorus/pull/30))
- eval_user_query for OPA style results ([#29](https://github.com/microsoft/regorus/pull/29))
- Arity for builtins ([#28](https://github.com/microsoft/regorus/pull/28))
- Handle chained _ ([#27](https://github.com/microsoft/regorus/pull/27))
- Minimize PR 22 ([#26](https://github.com/microsoft/regorus/pull/26))
- improve errors location ([#23](https://github.com/microsoft/regorus/pull/23))
- Fix clippy warning ([#25](https://github.com/microsoft/regorus/pull/25))
- negation of an undefined value should return true ([#21](https://github.com/microsoft/regorus/pull/21))
- Ensure that scopes are cleaned up correctly upon error. ([#20](https://github.com/microsoft/regorus/pull/20))
- support of or-functions ([#18](https://github.com/microsoft/regorus/pull/18))
- Statement Scheduler Implementation
- Remove unnecessary lifetime
- json.filter, object.filter, object.get, object.keys, object.remove
- :to_number builtin
- :trace builtin
- bitwise builtins
- :print builtin
- Partial sprintf implementation.
- All string functions except sprintf. TODO: Add tests
- More string functions without tests
- More string functions
- concat and contains
- string concat (WIP)
- Support build on non Linux platforms
- Prepare for upstreaming
- Test for multi-assign
- Support dependencies between vars defined in same statement
- Statement scheduler (WIP)
- Print small-form table of files without 100% coverage.
- Code tweaks to improve coverage
- Tests for aggregates builtins
- Tests for numbers builtins
- Tests for arrays builtins
- Tests for types functions
- Destructuring of arrays and objects in some-in expressions
- `some .. in` implementation
- Fix key, value in membership and some-in
- refactor
- Arrays and Aggregates
- Implement `every` statement ([#4](https://github.com/microsoft/regorus/pull/4))
- Set loop index variable if not "_" ([#3](https://github.com/microsoft/regorus/pull/3))
- Allow comprehensions in default value. ([#2](https://github.com/microsoft/regorus/pull/2))
- Lock down numbers
- mod function
- Builtin functions for numbers (WIP)
- Implement comparison operators. Formalize semantics.
- Rework assign operations ([#6](https://github.com/microsoft/regorus/pull/6))
- Locked down supported values in default rule.
- Improvements to github workflow ([#4](https://github.com/microsoft/regorus/pull/4))
- Update name to regorus
- Update rust.yml
- Add simple git action
- Add missing config.toml
- Update license to MIT
- Code from github.com/anakrish/rego-rs
- SUPPORT.md committed
- SECURITY.md committed
- README.md committed
- LICENSE committed
- CODE_OF_CONDUCT.md committed
- Initial commit
## [0.1.0-alpha.2](https://github.com/microsoft/regorus/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) - 2024-01-19
### Other
@@ -122,3 +515,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- LICENSE committed
- CODE_OF_CONDUCT.md committed
- Initial commit

2304
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,102 +1,166 @@
[workspace]
members = [
"tests/ensure_no_std",
"xtask",
]
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.1.0-alpha.2"
version = "0.9.1"
edition = "2021"
license-file = "LICENSE"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
repository = "https://github.com/microsoft/regorus"
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
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
[features]
default = ["full-opa"]
[lib]
doctest = false
[features]
default = ["full-opa", "arc", "rvm"]
arc = []
ast = []
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"]
crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha1", "dep:sha2"]
deprecated = []
coverage = []
hex = ["dep:data-encoding"]
http = []
jwt = []
glob = ["dep:wax"]
glob = ["dep:globset"]
graph = []
jsonschema = ["dep:jsonschema"]
mimalloc = ["dep:mimalloc"]
net = ["dep:ipnet"]
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
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", "indexmap?/std", "msvc_spectre_libs", "dep:parking_lot" ]
time = ["dep:chrono", "dep:chrono-tz"]
uuid = ["dep:uuid"]
urlquery = ["dep:url"]
time = ["dep:chrono", "dep:chrono-tz"]
yaml = ["serde_yaml"]
full-opa = [
"base64",
"base64url",
"crypto",
"deprecated",
"coverage",
"glob",
"graph",
"hex",
"http",
"jwt",
"jsonschema",
"net",
"opa-runtime",
"regex",
"cache",
"semver",
"std",
"time",
"uuid",
"urlquery",
"yaml"
"yaml",
#"rego-extensions"
]
# Features that can be used in no_std environments.
# Note that: the spin_no_std feature in lazy_static must be specified.
opa-no-std = [
"arc",
"base64",
"base64url",
"coverage",
"graph",
"hex",
"no_std",
"opa-runtime",
"regex",
"semver",
# Configure lazy_static to use spinlocks.
"lazy_static/spin_no_std"
]
# Rego language extensions
rego-extensions = []
# This feature enables some testing utils for OPA tests.
opa-testutil = []
rand = ["dep:rand"]
[dependencies]
anyhow = {version = "1.0.66", features = ["backtrace"] }
serde = {version = "1.0.150", features = ["derive", "rc"] }
serde_json = {version = "1.0.89", features = ["arbitrary_precision"] }
serde_yaml = {version = "0.9.16", optional = true }
log = "0.4.17"
env_logger="0.10.0"
lazy_static = "1.4.0"
rand = "0.8.5"
num = "0.4.1"
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 }
# Crypto
constant_time_eq = {version = "0.3.0", optional = true}
hmac = {version = "0.12.1", optional = true}
sha2 = {version= "0.10.8", optional = true}
hex = {version = "0.4.3", optional = true}
sha1 = {version = "0.10.6", optional = true}
md-5 = {version = "0.10.6", optional = true}
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 }
parking_lot = { version = "0.12", optional = true }
spin = { version = "0.10.0", default-features = false, features = ["mutex", "spin_mutex"] }
data-encoding = { version = "2.4.0", optional = true }
scientific = { version = "0.5.2" }
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
regex = {version = "1.12.3", optional = true, default-features = false }
semver = {version = "1.0.25", optional = true, default-features = false }
url = { version = "2.5.4", optional = true }
uuid = { version = "1.22.0", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.45.0", default-features = false, optional = true }
chrono = { version = "0.4.44", optional = true }
chrono-tz = { version = "0.10.1", optional = true }
ipnet = { version = "2.12.0", optional = true, default-features = false }
icu_casemap = { version = "2.1", optional = true, default-features = false, features = ["compiled_data"] }
regex = {version = "1.10.2", optional = true}
semver = {version = "1.0.20", optional = true}
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
url = { version = "2.5.0", optional = true }
uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.17.1", default-features = false, optional = true }
chrono = { version = "0.4.31", optional = true }
chrono-tz = { version = "0.8.5", optional = true }
document-features = "0.2.8"
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
# Specify thread_rng for in order to use random_range
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 }
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]
clap = { version = "4.4.7", features = ["derive"] }
colored-diff = "0.2.3"
anyhow = "1.0.102"
cfg-if = "1.0.0"
clap = { version = "4.5.53", features = ["derive"] }
prettydiff = { version = "0.9.0", default-features = false }
serde_yaml = "0.9.16"
test-generator = "0.3.1"
walkdir = "2.3.2"
criterion = { version = "0.8" }
num_cpus = "1.16"
[build-dependencies]
anyhow = "1.0.66"
anyhow = "1.0"
[profile.release]
debug = true
lto = true
codegen-units = 1
[[test]]
name="opa"
@@ -108,3 +172,56 @@ required-features = ["full-opa"]
name="aci"
harness=false
test=false
[[test]]
name="kata"
harness=false
test=false
[[bench]]
name = "regorus_benchmark"
harness = false
[[bench]]
name = "schema_validation_benchmark"
harness = false
required-features = ["azure_policy"]
[[bench]]
name = "engine_evaluation_benchmark"
path = "benches/evaluation/engine_evaluation_benchmark.rs"
harness = false
[[bench]]
name = "compiled_policy_evaluation_benchmark"
path = "benches/evaluation/compiled_policy_evaluation_benchmark.rs"
harness = false
[[bench]]
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
test=false
doctest=false
[package.metadata.docs.rs]
# To build locally:
# 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)'] }

235
LICENSE
View File

@@ -19,3 +19,238 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
The file src/builtins/time/diff.rs contains code derived from Go's `time`
package, which carries the following license:
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Some files are licensed Apache-2.0 (LICENSE-2.0.txt).
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

185
README.md
View File

@@ -3,64 +3,130 @@
**Regorus** is
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
interpreter written in Rust.
interpreter written in Rust.
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
Regorus is available as a library that can be easily integrated into your Rust projects.
Regorus is also
- *cross-platform* - Written in platform-agnostic Rust.
- *no_std compatible* - Regorus can be used in `no_std` environments too. Most of the builtins are supported.
- *current* - We strive to keep Regorus up to date with latest OPA release. Regorus defaults to `v1` of the Rego language.
- *compliant* - Regorus is mostly compliant with the latest [OPA release v1.2.0](https://github.com/open-policy-agent/opa/releases/tag/v1.2.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
- *extensible* - Extend the Rego language by implementing custom stateful builtins in Rust.
See [add_extension](https://github.com/microsoft/regorus/blob/fc68bf9c8bea36427dae9401a7d1f6ada771f7ab/src/engine.rs#L352).
Support for extensibility using other languages coming soon.
- *polyglot* - In addition to Rust, Regorus can be used from *C*, *C++*, *C#*, *Golang*, *Java*, *Javascript*, *Python*, and *Ruby*.
This is made possible by the excellent FFI tools available in the Rust ecosystem. See [bindings](#bindings) for information on how to use Regorus from different languages.
To try out a *Javascript(WASM)* compiled version of Regorus from your browser, visit [Regorus Playground](https://anakrish.github.io/regorus-playground/).
Regorus is available as a library that can be easily integrated into your Rust projects.
Here is an example of evaluating a simple Rego policy:
```rust
use anyhow::Result;
use regorus::*;
use serde_json;
fn main() -> anyhow::Result<()> {
// Create an engine for evaluating Rego policies.
let mut engine = regorus::Engine::new();
fn main() -> Result<()> {
// Create an engine for evaluating Rego policies.
let mut engine = Engine::new();
let policy = String::from(
r#"
package example
// Add policy to the engine.
engine.add_policy(
// Filename to be associated with the policy.
"hello.rego".to_string(),
allow if {
## All actions are allowed for admins.
input.principal == "admin"
} else if {
## Check if action is allowed for given user.
input.action in data.allowed_actions[input.principal]
}
"#,
);
// Rego policy that just sets a message.
r#"
package test
message = "Hello, World!"
"#.to_string()
)?;
// Add policy to the engine.
engine.add_policy(String::from("policy.rego"), policy)?;
// Evaluate the policy, fetch the message and print it.
let results = engine.eval_query("data.test.message".to_string(), false)?;
println!("{}", serde_json::to_string_pretty(&results)?);
// Add data to engine.
engine.add_data(regorus::Value::from_json_str(
r#"{
"allowed_actions": {
"user1" : ["read", "write"],
"user2" : ["read"]
}}"#,
)?)?;
Ok(())
// Set input and evaluate whether user1 can write.
engine.set_input(regorus::Value::from_json_str(
r#"{
"principal": "user1",
"action": "write"
}"#,
)?);
let r = engine.eval_rule(String::from("data.example.allow"))?;
assert_eq!(r, regorus::Value::from(true));
// Set input and evaluate whether user2 can write.
engine.set_input(regorus::Value::from_json_str(
r#"{
"principal": "user2",
"action": "write"
}"#,
)?);
let r = engine.eval_rule(String::from("data.example.allow"))?;
assert_eq!(r, regorus::Value::Undefined);
Ok(())
}
```
Regorus is designed with [Confidential Computing](https://confidentialcomputing.io/about/) in mind. In Confidential Computing environments,
it is important to be able to control exactly what is being run. Regorus allows enabling and disabling various components using cargo
it is important to be able to control exactly what is being run. Regorus allows enabling and disabling various components using cargo
features. By default all features are enabled.
The default build of regorus example program is 6.4M:
The default build of regorus example program is 6.3M:
```bash
$ cargo build -r --example regorus; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
$ cargo build -r --example regorus; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 6.4M Jan 19 11:23 target/release/examples/regorus*
-rwxr-xr-x 1 anand staff 6.3M May 11 22:03 target/release/examples/regorus*
```
When all features except for `yaml` are disabled, the binary size drops down to 2.9M.
When all default features are disabled, the binary size drops down to 1.9M.
```bash
$ cargo build -r --example regorus --features "yaml" --no-default-features; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 2.9M Jan 19 11:26 target/release/examples/regorus*
$ cargo build -r --example regorus --no-default-features; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 1.9M May 11 22:04 target/release/examples/regorus*
```
Regorus passes the [OPA v0.60.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
Regorus passes the [OPA v1.2.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
builtins. See [OPA Conformance](#opa-conformance) below.
## Bindings
Regorus can be used from a variety of languages:
- *C*: C binding is generated using [cbindgen](https://github.com/mozilla/cbindgen).
[corrosion-rs](https://github.com/corrosion-rs/corrosion) can be used to seamlessly use Regorous
in your CMake based projects. See [bindings/c](https://github.com/microsoft/regorus/tree/main/bindings/c).
- *C freestanding*: [bindings/c_no_std](https://github.com/microsoft/regorus/tree/main/bindings/c_no_std) shows how to use Regorus from C environments without a libc.
- *C++*: C++ binding is generated using [cbindgen](https://github.com/mozilla/cbindgen).
[corrosion-rs](https://github.com/corrosion-rs/corrosion) can be used to seamlessly use Regorous
in your CMake based projects. See [bindings/cpp](https://github.com/microsoft/regorus/tree/main/bindings/cpp).
- *C#*: C# binding is generated using [csbindgen](https://github.com/Cysharp/csbindgen). See [bindings/csharp](https://github.com/microsoft/regorus/tree/main/bindings/csharp) for an example of how to build and use Regorus in your C# projects.
- *Golang*: The C bindings are exposed to Golang via [CGo](https://pkg.go.dev/cmd/cgo). See [bindings/go](https://github.com/microsoft/regorus/tree/main/bindings/go) for an example of how to build and use Regorus in your Go projects.
- *Python*: Python bindings are generated using [pyo3](https://github.com/PyO3/pyo3). Wheels are created using [maturin](https://github.com/PyO3/maturin). See [bindings/python](https://github.com/microsoft/regorus/tree/main/bindings/python).
- *Java*: Java bindings are developed using [jni-rs](https://github.com/jni-rs/jni-rs).
See [bindings/java](https://github.com/microsoft/regorus/tree/main/bindings/java).
- *Javascript*: Regorus is compiled to WASM using [wasmpack](https://github.com/rustwasm/wasm-pack).
See [bindings/wasm](https://github.com/microsoft/regorus/tree/main/bindings/wasm) for an example of using Regorus from nodejs.
To try out a *Javascript(WASM)* compiled version of Regorus from your browser, visit [Regorus Playground](https://anakrish.github.io/regorus-playground/).
- *Ruby*: Ruby bindings are developed using [magnus](https://github.com/matsadler/magnus).
See [bindings/ruby](https://github.com/microsoft/regorus/tree/main/bindings/ruby).
To avoid operational overhead, we currently don't publish these bindings to various repositories.
It is straight-forward to build these bindings yourself.
## Getting Started
[examples/regorus](https://github.com/microsoft/regorus/blob/main/examples/regorus.rs) is an example program that
@@ -79,6 +145,7 @@ $ regorus
Usage: regorus <COMMAND>
Commands:
ast Parse a Rego policy and dump AST
eval Evaluate a Rego Query
lex Tokenize a Rego policy
parse Parse a Rego policy
@@ -117,11 +184,11 @@ This produces the following output
}
```
Next, evaluate a sample [policy](examples/example.rego) and [input](examples/input.json)
Next, evaluate a sample [policy](https://github.com/microsoft/regorus/blob/main/examples/server/allowed_server.rego) and [input](https://github.com/microsoft/regorus/blob/main/examples/server/input.json)
(borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)):
```bash
$ regorus eval -d examples/example.rego -i examples/input.json data.example
$ regorus eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
```
Finally, evaluate real-world [policies](tests/aci/) used in Azure Container Instances (ACI)
@@ -130,6 +197,23 @@ Finally, evaluate real-world [policies](tests/aci/) used in Azure Container Inst
$ regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.policy.mount_overlay=x
```
## Policy coverage
Regorus allows determining which lines of a policy have been executed using the `coverage` feature (enabled by default).
We can try it out using the `regorus` example program by passing in the `--coverage` flag.
```shell
$ regorus eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example --coverage
```
It produces the following coverage report which shows that all lines are executed except the line that sets `allow` to true.
![coverage.png](https://github.com/microsoft/regorus/blob/main/docs/coverage.png?raw=true)
See [Engine::get_coverage_report](https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report) for details.
Policy coverage information is useful for debugging your policy as well as to write tests for your policy so that all
lines of the policy are exercised by the tests.
## ACI Policies
@@ -190,28 +274,34 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
Range (min … max): 43.8 ms … 46.7 ms 62 runs
```
## Contributor Workflow
Regorus uses a small companion CLI under the `xtask` package to keep CI and local development in sync.
The commands mirror our GitHub Actions jobs, making it easy to dry-run CI steps before sending a pull request.
- Run the full release pipeline with `cargo xtask ci-release` and the debug checks with `cargo xtask ci-debug`.
- Exercise language bindings through focused helpers such as `cargo xtask test-java --release --frozen` or `cargo xtask test-go`.
- Use `cargo xtask test-musl --release --frozen` for the cross-compilation matrix and `cargo xtask test-no-std` for embedded targets.
- Formatting (`cargo xtask fmt`) and linting (`cargo xtask clippy --sarif`) wrap the usual Cargo tooling while matching CI defaults.
The workflows in `.github/workflows` invoke the same commands, so keeping local runs green is usually enough to satisfy the checks enforced on `main`.
## OPA Conformance
Regorus has been verified to be compliant with [OPA v0.60.0](https://github.com/open-policy-agent/opa/releases/tag/v0.60.0)
using a [test driver](https://github.com/microsoft/regorus/blob/main/tests/opa.rs) that loads and runs the OPA testsuite using Regorus, and verifies that expected outputs
are produced.
Regorus has been verified to be compliant with [OPA v1.2.0](https://github.com/open-policy-agent/opa/releases/tag/v1.2.0)
using a [test driver](https://github.com/microsoft/regorus/blob/main/tests/opa.rs) that loads and runs the OPA testsuite using Regorus, and verifies that expected outputs are produced.
The test driver can be invoked by running:
```bash
$ cargo test -r --test opa
$ cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision
```
Currently, Regorus passes all the non-builtin specific tests.
See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing).
The following test suites don't pass fully due to mising builtins:
- `cryptoparsersaprivatekeys`
- `cryptox509parseandverifycertificates`
- `cryptox509parsecertificaterequest`
- `cryptox509parsecertificates`
- `cryptox509parsekeypair`
- `cryptox509parsersaprivatekey`
The following test suites don't pass fully due to missing builtins:
- `globsmatch`
- `graphql`
- `invalidkeyerror`
@@ -219,16 +309,15 @@ The following test suites don't pass fully due to mising builtins:
- `jwtbuiltins`
- `jwtdecodeverify`
- `jwtencodesign`
- `jwtencodesignheadererrors`
- `jwtencodesignpayloaderrors`
- `jwtencodesignraw`
- `jwtverifyhs256`
- `jwtverifyhs384`
- `jwtverifyhs512`
- `jwtverifyrsa`
- `netcidrcontains`
- `netcidrcontainsmatches`
- `netcidrexpand`
- `netcidrintersects`
- `netcidrisvalid`
- `netcidrmerge`
- `netcidroverlap`
- `netlookupipaddr`
@@ -237,10 +326,10 @@ The following test suites don't pass fully due to mising builtins:
- `regometadatarule`
- `regoparsemodule`
- `rendertemplate`
- `time`
They are captured in the following [github issues](https://github.com/microsoft/regorus/issues?q=is%3Aopen+is%3Aissue+label%3Alib).
Cryptographic builtins are not supported by design. Users that need cryptographic builtins are encouraged to use [extensions](https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_extension).
### Grammar

80
benches/aci_benchmark.rs Normal file
View File

@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use regorus::{Engine, Value};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
use std::path::Path;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestCase {
note: String,
data: Value,
input: Value,
modules: Vec<String>,
query: String,
want_result: Value,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct YamlTest {
cases: Vec<TestCase>,
}
fn aci_policy_eval(c: &mut Criterion) {
let dir = Path::new("tests/aci");
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 test");
let yaml = String::from_utf8_lossy(&yaml);
let test: YamlTest = serde_yaml::from_str(&yaml).expect("failed to deserialize yaml test");
for case in &test.cases {
let rule = case.query.replace("=x", "");
c.bench_with_input(
BenchmarkId::new("case ", format!("{} {}", &case.note, &rule)),
&case,
|b, case| {
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") {
let path = dir.join(rego);
let path = path.to_str().expect("not a valid path");
engine
.add_policy_from_file(path)
.expect("failed to add policy");
} else {
engine
.add_policy(format!("rego{idx}.rego"), rego.clone())
.expect("failed to add policy");
}
}
b.iter(|| {
engine.eval_rule(rule.clone()).unwrap();
})
},
);
}
}
}
criterion_group!(aci_benches, aci_policy_eval);
criterion_main!(aci_benches);

View File

@@ -0,0 +1,157 @@
# Regorus Multi-Threaded Evaluation Benchmark
A benchmark suite for measuring the multi-threaded performance of the Regorus policy evaluation engine.
## Overview
This benchmark evaluates the throughput and scalability of Regorus policy evaluation across different thread counts and configuration strategies. It measures performance variations between fresh and cloned engine instances, as well as fresh and cloned input data.
## Features
- **Multi-threaded evaluation** testing from 1 to `num_cpus * 2` threads
- **Configurable engine strategies**: Fresh vs. cloned engine instances
- **Configurable input strategies**: Fresh parsing vs. cloned input data
- **Complex policy evaluation** using realistic RBAC and data sensitivity policies
- **Criterion-based benchmarking** with statistical analysis
- **Performance metrics** including throughput and timing
## Benchmark Structure
### Test Configurations
The benchmark tests four different configuration combinations:
1. **Cloned Engines + Cloned Inputs**: Pre-instantiated engines with pre-parsed input data
2. **Cloned Engines + Fresh Inputs**: Pre-instantiated engines with fresh JSON parsing
3. **Fresh Engines + Cloned Inputs**: New engine instances with pre-parsed input data
4. **Fresh Engines + Fresh Inputs**: New engine instances with fresh JSON parsing
### Thread Scaling
Tests are performed with thread counts: 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32 (up to `num_cpus * 2`)
Each thread performs 1000 policy evaluations to ensure statistically significant measurements.
## Running the Benchmark
### Prerequisites
- Rust 1.70+
- Cargo
### Execution
Run the complete benchmark suite:
```bash
cargo bench evaluation_benchmark
```
Run specific benchmarks:
```bash
# Run only cloned engines with cloned inputs
cargo bench "cloned_engines , cloned_inputs"
# Run only single-threaded tests
cargo bench "1 threads"
```
### Output
Results are generated in the `target/criterion/` directory and include:
- Detailed timing statistics
- Throughput measurements (Kelem/s)
- Performance comparison with previous runs
- HTML reports with graphs and analysis
## Test Policies
The benchmark uses complex Rego policies that simulate real-world scenarios:
### RBAC Policy
- Role-based access control with hierarchical permissions
- User-role-resource mapping
- Action-based authorization
### Data Sensitivity Policy
- Multi-level data classification (public, internal, confidential, secret)
- Access level validation
- Clearance-based filtering
### Time-based Access Policy
- Business hours validation
- Temporal access control
- Schedule-based permissions
### Azure Resource Policies
- **VM Deployment**: VM size restrictions, regional compliance, security configurations
- **Storage Account Security**: Encryption requirements, network ACLs, HTTPS enforcement
- **Key Vault Access**: Service principal validation, soft delete requirements, conditional access
- **Network Security Groups**: Port restrictions, CIDR validation, priority-based rules
### Policy Complexity Features
- **Multi-condition validation**: Complex nested object property checks
- **Network operations**: CIDR matching and IP range validation
- **Time-based constraints**: Timestamp comparisons and business hour logic
- **Security compliance**: Encryption, authentication, and access control patterns
- **Azure Resource Manager**: Real-world cloud governance scenarios
## Configuration
### Benchmark Parameters
- **Evaluations per thread**: 1000
- **Measurement iterations**: 100 samples per configuration
- **Warm-up time**: 3 seconds
- **Measurement time**: 10 seconds (extended for high thread counts)
### Customization
The benchmark can be customized by modifying `evaluation_benchmark.rs`:
```rust
// Adjust evaluations per thread
let evals_per_thread = 1000;
// Modify thread count calculation
let max_threads = num_cpus::get() * 2;
// Configure test scenarios
let scenarios = [
(true, true), // cloned_engines, cloned_inputs
(true, false), // cloned_engines, fresh_inputs
(false, true), // fresh_engines, cloned_inputs
(false, false), // fresh_engines, fresh_inputs
];
```
## Understanding Results
### Metrics
- **Total Evaluation Time**: Total execution time for all evaluations across all threads (ms)
- **Throughput**: Evaluations per second measured in Kelem/s
- **Kelem/s**: Thousands of elements (policy evaluations) per second
- Example: 98.71 Kelem/s = 98,710 policy evaluations per second
### Interpretation
- **Lower time** = better performance
- **Higher throughput** = better performance
- **Consistent results** across runs indicate stable performance
- **Outliers** may indicate system interference or measurement variance
### Tips
- Run on dedicated hardware for consistent results
- Disable other applications during benchmarking
- Use release builds for accurate performance measurements
- Consider CPU affinity for highly controlled testing
## Files
- `evaluation_benchmark.rs`: Main benchmark implementation
- Results are saved to `../../target/criterion/` directory

View File

@@ -0,0 +1,160 @@
# Compiled Policy Evaluation Benchmark Results
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **Rust Version**: 1.82.0
- **Allocator**: mimalloc (default allocator)
- **Benchmark Framework**: Criterion.rs
- **Test Data**: 20,000 inputs per evaluation (1000 per thread)
- **Policy**: Complex authorization policy with nested rules
## Benchmark Overview
The compiled policy evaluation benchmark tests Regorus compiled policy performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of compiled policy and input data reuse strategies.
## Configuration Combinations
1. **Compiled Shared Policies, Cloned Inputs**: Each thread uses shared compiled policies and clones of parsed input data - optimal for performance
2. **Compiled Shared Policies, Fresh Inputs**: Each thread uses shared compiled policies but parses new inputs each time
3. **Compiled Per Iteration, Cloned Inputs**: Each thread compiles the policy each iteration but reuses input data
4. **Compiled Per Iteration, Fresh Inputs**: Each thread compiles new policies and parses new inputs for each iteration
## Performance Results
### Compiled Shared Policies, Cloned Inputs (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2.35 | 426 |
| 2 | 5.36 | 373 |
| 4 | 11.70 | 342 |
| 6 | 20.33 | 295 |
| 8 | 43.26 | 185 |
| 10 | 61.93 | 162 |
| 12 | 79.30 | 151 |
| 14 | 94.45 | 148 |
| 16 | 113.39 | 141 |
| 18 | 154.41 | 117 |
| 20 | 184.37 | 108 |
| 22 | 204.00 | 108 |
| 24 | 220.45 | 109 |
| 26 | 237.07 | 110 |
| 28 | 252.58 | 111 |
| 30 | 273.57 | 110 |
| 32 | 292.69 | 109 |
### Compiled Shared Policies, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 3.34 | 299 |
| 2 | 7.29 | 274 |
| 4 | 15.19 | 263 |
| 6 | 24.90 | 241 |
| 8 | 49.22 | 163 |
| 10 | 68.45 | 146 |
| 12 | 86.55 | 139 |
| 14 | 104.77 | 134 |
| 16 | 136.07 | 118 |
| 18 | 169.05 | 106 |
| 20 | 198.25 | 101 |
| 22 | 217.05 | 101 |
| 24 | 234.75 | 102 |
| 26 | 254.53 | 102 |
| 28 | 276.06 | 101 |
| 30 | 296.12 | 101 |
| 32 | 318.81 | 100 |
### Compiled Per Iteration, Cloned Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 18.11 | 55 |
| 2 | 36.89 | 54 |
| 4 | 75.46 | 53 |
| 6 | 114.66 | 52 |
| 8 | 152.80 | 52 |
| 10 | 192.17 | 52 |
| 12 | 232.32 | 52 |
| 14 | 301.47 | 46 |
| 16 | 380.36 | 42 |
| 18 | 424.64 | 42 |
| 20 | 484.76 | 41 |
| 22 | 531.62 | 41 |
| 24 | 582.88 | 41 |
| 26 | 631.39 | 41 |
| 28 | 671.99 | 42 |
| 30 | 717.65 | 42 |
| 32 | 766.05 | 42 |
### Compiled Per Iteration, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 19.07 | 52 |
| 2 | 38.89 | 51 |
| 4 | 79.52 | 50 |
| 6 | 120.89 | 50 |
| 8 | 161.08 | 50 |
| 10 | 202.37 | 49 |
| 12 | 244.04 | 49 |
| 14 | 316.66 | 44 |
| 16 | 398.02 | 40 |
| 18 | 449.54 | 40 |
| 20 | 500.57 | 40 |
| 22 | 557.97 | 39 |
| 24 | 605.71 | 40 |
| 26 | 656.88 | 40 |
| 28 | 710.03 | 39 |
| 30 | 741.09 | 40 |
| 32 | 801.26 | 40 |
## Analysis
The compiled policy benchmark demonstrates the following performance characteristics with mimalloc as the default allocator:
1. **Best Performance**: Compiled shared policies with cloned inputs provide the highest throughput
2. **Compilation Impact**:
- Pre-compiled policies: Significantly faster than per-iteration compilation
- Per-iteration compilation: Major overhead (~7-8x slower than pre-compiled)
3. **Scaling Patterns with mimalloc**:
- Best throughput achieved at 1 thread for shared policy configurations
- mimalloc provides better thread scaling characteristics compared to the default allocator
- Higher thread counts show performance degradation due to contention, but less severe with mimalloc
- Per-iteration compilation shows poor scaling across all thread counts
4. **Input Processing**: Fresh inputs add ~30% overhead across all configurations
5. **Thread Performance with mimalloc**:
- Peak performance at 1 thread for most configurations
- Reasonable performance maintained up to 12-16 threads for shared policies
- Compiled policies show better thread scaling than per-iteration compilation
- mimalloc helps reduce allocation-related contention in multi-threaded scenarios
## Comparison with Engine Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:---------------------|:-------------------|:--------------------|:--------------------|
| | CP / EE | CP / EE | CP / EE |
| Shared/Cloned | 426 / 423 | 342 / 406 | 185 / 341 |
| Shared/Fresh | 299 / 309 | 263 / 297 | 163 / 266 |
| Per-iteration/Cloned | 55 / 56 | 53 / 54 | 52 / 53 |
| Per-iteration/Fresh | 52 / 53 | 50 / 51 | 50 / 51 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:---------------------|:----------------------|:--------------------------|:-----------------------|
| | Avg CP / EE | Avg CP / EE | Avg CP / EE |
| Shared/Cloned | 384 / 414 | 203 / 329 | 123 / 250 |
| Shared/Fresh | 284 / 302 | 176 / 235 | 108 / 201 |
| Per-iteration/Cloned | 54 / 55 | 50 / 52 | 42 / 42 |
| Per-iteration/Fresh | 51 / 52 | 47 / 50 | 40 / 40 |
The compiled policy evaluation shows performance characteristics that are generally comparable to engine evaluation, though with some notable differences. While single-threaded performance is very close between the systems, there are observable impacts from the compilation approach that become more apparent under different threading scenarios.
**Key Observations:**
- **Single-threaded performance**: Very close parity between systems, though results may vary between runs
- **Threading behavior**: Engine evaluation demonstrates better scaling characteristics under higher thread contention (4+ threads)
- **Multi-threaded impact**: Compiled policies show more pronounced performance degradation under thread contention in shared policy configurations
- **Contention resistance**: Per-iteration compilation shows more consistent (though lower absolute) performance across thread counts
- **Optimal usage**: Both systems achieve best results with minimal threading (1-4 threads), though engine evaluation maintains better performance at higher thread counts for shared configurations

View File

@@ -0,0 +1,232 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use regorus::{compile_policy_with_entrypoint, CompiledPolicy, PolicyModule, Value};
use std::collections::HashMap;
use std::hint::black_box;
use std::sync::{Arc, Barrier, Mutex};
use std::thread;
use std::time::Duration;
mod policy_data;
fn multi_threaded_compiled_eval(
num_threads: usize,
evals_per_thread: usize,
use_shared_policies: bool,
use_cloned_inputs: bool,
) -> (std::time::Duration, HashMap<String, usize>, usize) {
// Complex policies with multiple valid inputs for each
let policies_with_inputs = policy_data::policies_with_inputs();
// Policy names for tracking
let policy_names = policy_data::policy_names()
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
// Pre-compile all policies and share them between threads (only if using shared policies)
let compiled_policies: Option<Arc<Vec<CompiledPolicy>>> = if use_shared_policies {
Some(Arc::new(
policies_with_inputs
.iter()
.map(|(policy, _)| {
let module = PolicyModule {
id: "policy.rego".into(),
content: policy.as_str().into(),
};
compile_policy_with_entrypoint(
Value::new_object(),
&[module],
"data.bench.allow".into(),
)
.unwrap()
})
.collect(),
))
} else {
None
};
// Initialize policy evaluation counters
let policy_counters = Arc::new(Mutex::new(HashMap::new()));
for policy_name in &policy_names {
policy_counters
.lock()
.unwrap()
.insert(policy_name.to_string(), 0);
}
let total_evals = Arc::new(Mutex::new(0usize));
let barrier = Arc::new(Barrier::new(num_threads));
let mut handles = Vec::with_capacity(num_threads);
for thread_id in 0..num_threads {
let barrier = barrier.clone();
let policies_with_inputs = policies_with_inputs.clone();
let compiled_policies = compiled_policies.clone();
let policy_names = policy_names.clone();
let policy_counters = policy_counters.clone();
let total_evals = total_evals.clone();
handles.push(thread::spawn(move || {
let mut elapsed = std::time::Duration::ZERO;
// Pre-parse inputs if using cloned inputs
let parsed_inputs = if use_cloned_inputs {
Some(
policies_with_inputs
.iter()
.map(|(_, inputs)| {
inputs
.iter()
.map(|input_str| Value::from_json_str(input_str).unwrap())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
)
} else {
None
};
barrier.wait();
for i in 0..evals_per_thread {
// Use different policy for each iteration - thread_id ensures different threads
// start with different policies for better load distribution
let policy_idx = (thread_id + i) % policies_with_inputs.len();
let (_, inputs) = &policies_with_inputs[policy_idx];
// Use different input for the same policy based on iteration - thread_id ensures
// different threads start with different inputs for better load distribution
let input_idx = (thread_id + i) % inputs.len();
let input = &inputs[input_idx];
let start = std::time::Instant::now();
let input_value = if use_cloned_inputs {
parsed_inputs.as_ref().unwrap()[policy_idx][input_idx].clone()
} else {
Value::from_json_str(input).unwrap()
};
let result = if let Some(ref compiled_policies_vec) = compiled_policies {
// Use pre-compiled policy
let compiled_policy = &compiled_policies_vec[policy_idx];
compiled_policy.eval_with_input(input_value)
} else {
// Compile policy in each iteration
let (policy, _) = &policies_with_inputs[policy_idx];
let module = PolicyModule {
id: "policy.rego".into(),
content: policy.as_str().into(),
};
let compiled_policy = compile_policy_with_entrypoint(
Value::new_object(),
&[module],
"data.bench.allow".into(),
)
.unwrap();
compiled_policy.eval_with_input(input_value)
};
elapsed += start.elapsed();
// Track total and successful evaluations
{
let mut total = total_evals.lock().unwrap();
*total += 1;
}
if result.is_ok() {
if let Some(policy_name) = policy_names.get(policy_idx) {
let mut counters = policy_counters.lock().unwrap();
*counters.entry(policy_name.to_string()).or_insert(0) += 1;
}
}
}
elapsed
}));
}
let mut total = std::time::Duration::ZERO;
for handle in handles {
total += handle.join().unwrap();
}
let final_counters = policy_counters.lock().unwrap().clone();
let total_evals = *total_evals.lock().unwrap();
(total, final_counters, total_evals)
}
fn criterion_benchmark(c: &mut Criterion) {
let max_threads = num_cpus::get() * 2;
println!(
"Running compiled policy benchmark with max_threads: {}",
max_threads
);
let evals_per_thread = 1000;
// Benchmark all combinations of compilation strategy and input strategy
for use_shared_policies in [true, false] {
for use_cloned_inputs in [true, false] {
let group_name = match (use_shared_policies, use_cloned_inputs) {
(true, true) => "compiled_shared_policies, cloned_inputs ",
(true, false) => "compiled_shared_policies, fresh_inputs ",
(false, true) => "compiled_per_iteration , cloned_inputs ",
(false, false) => "compiled_per_iteration , fresh_inputs ",
};
let mut group = c.benchmark_group(group_name);
group.measurement_time(Duration::from_secs(5));
// Test specific thread counts: powers of 2 + some intermediate values
let thread_counts: Vec<usize> = (1..=max_threads)
.filter(|&n| {
n == 1 || // Always test single-threaded
n % 2 == 0 || // Always test even threads
n == max_threads // Maximum threads
})
.collect();
for threads in thread_counts {
let total_evals = threads * evals_per_thread;
group.throughput(Throughput::Elements(total_evals as u64));
group.bench_with_input(
BenchmarkId::new("compiled_eval", format!(" {threads} threads")),
&threads,
|b, &threads| {
b.iter_custom(|iters| {
let evals_per_thread = evals_per_thread * (iters as usize);
let (duration, policy_counters, total_evals_aggregated) = multi_threaded_compiled_eval(
black_box(threads),
black_box(evals_per_thread),
black_box(use_shared_policies),
black_box(use_cloned_inputs),
);
// Sanity check: Ensure the expected number of evaluations matches the actual number performed per iteration batch.
// total_evals is the expected number for this batch, total_evals_aggregated is the sum over all iters.
assert_eq!(total_evals, total_evals_aggregated/iters as usize);
// On one iteration, print policy evaluation statistics
if iters == 1 {
// println!("\nCompiled Policy Evaluation Statistics:");
for (policy_name, count) in &policy_counters {
// println!(" {}: {} evaluations", policy_name, count);
if *count == 0 {
println!("\x1b[31mERROR: Policy '{}' was never evaluated successfully!\x1b[0m", policy_name);
}
}
}
duration
});
},
);
}
group.finish();
}
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -0,0 +1,127 @@
# Engine Evaluation Benchmark Results
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **Rust Version**: 1.82.0
- **Allocator**: mimalloc (default allocator)
- **Benchmark Framework**: Criterion.rs
- **Test Data**: 20,000 inputs per evaluation (1000 per thread)
- **Policy**: Complex authorization policy with nested rules
## Benchmark Overview
The engine evaluation benchmark tests Regorus policy evaluation performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of engine and input data reuse strategies.
## Configuration Combinations
1. **Cloned Engines, Cloned Inputs**: Each thread uses its own engine and clones of parsed input data - optimal for performance
2. **Cloned Engines, Fresh Inputs**: Each thread uses its own engine but parses new inputs each time
3. **Fresh Engines, Cloned Inputs**: Each thread creates a new engine each iteration but reuses input data
4. **Fresh Engines, Fresh Inputs**: Each thread creates new engines and parses new inputs for each iteration
## Performance Results
### Cloned Engines, Cloned Inputs (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2.36 | 423 |
| 2 | 4.85 | 412 |
| 4 | 9.86 | 406 |
| 6 | 15.02 | 399 |
| 8 | 23.46 | 341 |
| 10 | 33.34 | 300 |
| 12 | 40.69 | 295 |
| 14 | 48.26 | 290 |
| 16 | 58.61 | 273 |
| 18 | 77.35 | 233 |
| 20 | 86.74 | 231 |
| 22 | 94.17 | 234 |
| 24 | 102.58 | 234 |
| 26 | 110.17 | 236 |
| 28 | 118.97 | 235 |
| 30 | 126.54 | 237 |
| 32 | 135.89 | 235 |
### Cloned Engines, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 3.24 | 309 |
| 2 | 6.57 | 304 |
| 4 | 13.47 | 297 |
| 6 | 20.42 | 294 |
| 8 | 30.01 | 266 |
| 10 | 40.99 | 244 |
| 12 | 49.99 | 240 |
| 14 | 60.09 | 233 |
| 16 | 73.95 | 216 |
| 18 | 95.94 | 188 |
| 20 | 105.24 | 190 |
| 22 | 114.30 | 192 |
| 24 | 124.67 | 193 |
| 26 | 134.76 | 193 |
| 28 | 145.16 | 193 |
| 30 | 155.23 | 193 |
| 32 | 165.42 | 193 |
### Fresh Engines, Cloned Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 17.88 | 56 |
| 2 | 36.32 | 55 |
| 4 | 74.45 | 54 |
| 6 | 112.95 | 53 |
| 8 | 150.24 | 53 |
| 10 | 189.61 | 53 |
| 12 | 228.25 | 53 |
| 14 | 297.37 | 47 |
| 16 | 373.61 | 43 |
| 18 | 426.46 | 42 |
| 20 | 477.80 | 42 |
| 22 | 523.00 | 42 |
| 24 | 570.74 | 42 |
| 26 | 619.92 | 42 |
| 28 | 670.24 | 42 |
| 30 | 717.47 | 42 |
| 32 | 748.25 | 43 |
### Fresh Engines, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 18.69 | 53 |
| 2 | 38.03 | 53 |
| 4 | 77.82 | 51 |
| 6 | 118.30 | 51 |
| 8 | 157.65 | 51 |
| 10 | 197.97 | 51 |
| 12 | 239.05 | 50 |
| 14 | 310.06 | 45 |
| 16 | 391.36 | 41 |
| 18 | 441.63 | 41 |
| 20 | 495.88 | 40 |
| 22 | 543.69 | 40 |
| 24 | 591.51 | 41 |
| 26 | 645.98 | 40 |
| 28 | 697.37 | 40 |
| 30 | 749.37 | 40 |
| 32 | 784.63 | 41 |
## Analysis
The benchmark results demonstrate the following performance characteristics with mimalloc as the default allocator:
1. **Best Performance**: Cloned engines with cloned inputs consistently deliver the highest throughput
2. **Configuration Performance Hierarchy**:
- Cloned engines, cloned inputs: Best performance (optimal configuration)
- Cloned engines, fresh inputs: ~27% reduction from optimal
- Fresh engines, cloned inputs: ~87% reduction from optimal
- Fresh engines, fresh inputs: ~87% reduction from optimal
3. **Scaling Patterns with mimalloc**:
- Performance degrades with increased thread count due to contention, but mimalloc provides better thread scaling characteristics
- Best throughput achieved at 1 thread for cloned engine configurations
- Fresh engine configurations show poor scaling across all thread counts
- The use of mimalloc as the default allocator has improved multi-threaded performance and reduced contention
4. **Engine Creation Overhead**: Fresh engine creation is a significant performance bottleneck (~7-8x slower than cloned engines)
5. **Input Processing**: Fresh input generation adds moderate overhead (~27% impact compared to cloned inputs)
6. **Thread Contention**: Performance degradation occurs with higher thread counts across all configurations, though mimalloc helps mitigate some allocation-related contention

View File

@@ -0,0 +1,228 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use regorus::{Engine, Value};
use std::collections::HashMap;
use std::hint::black_box;
use std::sync::{Arc, Barrier, Mutex};
use std::thread;
use std::time::Duration;
mod policy_data;
fn multi_threaded_eval(
num_threads: usize,
evals_per_thread: usize,
use_cloned_engines: bool,
use_cloned_inputs: bool,
) -> (std::time::Duration, HashMap<String, usize>, usize) {
// Complex policies with multiple valid inputs for each
let policies_with_inputs = policy_data::policies_with_inputs();
// Policy names for tracking
let policy_names = policy_data::policy_names()
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
// Initialize policy evaluation counters
let policy_counters = Arc::new(Mutex::new(HashMap::new()));
for policy_name in &policy_names {
policy_counters
.lock()
.unwrap()
.insert(policy_name.to_string(), 0);
}
let barrier = Arc::new(Barrier::new(num_threads));
let mut handles = Vec::with_capacity(num_threads);
let total_evals = Arc::new(Mutex::new(0usize));
for thread_id in 0..num_threads {
let barrier = barrier.clone();
let policies_with_inputs = policies_with_inputs.clone();
let policy_names = policy_names.clone();
let policy_counters = policy_counters.clone();
let total_evals = total_evals.clone();
handles.push(thread::spawn(move || {
let mut elapsed = std::time::Duration::ZERO;
// Pre-create engines if using cloned engines
let engines = if use_cloned_engines {
Some(
policies_with_inputs
.iter()
.map(|(policy, _)| {
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy.to_string())
.unwrap();
{
// Warm up the engine to ensure it's fully prepared for evaluation.
// This prevents each cloned engine from repeating preparation work.
engine.set_input(Value::new_object());
let _ = engine.eval_rule("data.bench.allow".to_string());
}
engine
})
.collect::<Vec<_>>(),
)
} else {
None
};
// Pre-parse inputs if using cloned inputs
let parsed_inputs = if use_cloned_inputs {
Some(
policies_with_inputs
.iter()
.map(|(_, inputs)| {
inputs
.iter()
.map(|input_str| regorus::Value::from_json_str(input_str).unwrap())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
)
} else {
None
};
barrier.wait();
for i in 0..evals_per_thread {
// Use different policy for each iteration - thread_id ensures different threads
// start with different policies for better load distribution
let policy_idx = (thread_id + i) % policies_with_inputs.len();
let (policy, inputs) = &policies_with_inputs[policy_idx];
// Use different input for the same policy based on iteration - thread_id ensures
// different threads start with different inputs for better load distribution
let input_idx = (thread_id + i) % inputs.len();
let input = &inputs[input_idx];
let start = std::time::Instant::now();
let result = {
let mut engine = if use_cloned_engines {
engines.as_ref().unwrap()[policy_idx].clone()
} else {
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy.to_string())
.unwrap();
engine
};
let input_value = if use_cloned_inputs {
parsed_inputs.as_ref().unwrap()[policy_idx][input_idx].clone()
} else {
regorus::Value::from_json_str(input).unwrap()
};
engine.set_input(input_value);
engine.eval_rule("data.bench.allow".to_string())
// Engine cleanup/drop time is included in measurement to reflect
// real-world total cost of policy evaluation lifecycle
};
elapsed += start.elapsed();
// Track total and successful evaluations
{
let mut total = total_evals.lock().unwrap();
*total += 1;
}
if result.is_ok() {
if let Some(policy_name) = policy_names.get(policy_idx) {
let mut counters = policy_counters.lock().unwrap();
*counters.entry(policy_name.to_string()).or_insert(0) += 1;
}
}
}
elapsed
}));
}
let mut total = std::time::Duration::ZERO;
for handle in handles {
total += handle.join().unwrap();
}
let final_counters = policy_counters.lock().unwrap().clone();
let total_evals = *total_evals.lock().unwrap();
(total, final_counters, total_evals)
}
fn criterion_benchmark(c: &mut Criterion) {
let max_threads = num_cpus::get() * 2;
println!("Running benchmark with max_threads: {}", max_threads);
let evals_per_thread = 1000;
// Benchmark all combinations of cloned engines and inputs
for use_cloned_engines in [true, false] {
for use_cloned_inputs in [true, false] {
let group_name = match (use_cloned_engines, use_cloned_inputs) {
(true, true) => "cloned_engines , cloned_inputs ",
(true, false) => "cloned_engines , fresh_inputs ",
(false, true) => "fresh_engines , cloned_inputs ",
(false, false) => "fresh_engines , fresh_inputs ",
};
let mut group = c.benchmark_group(group_name);
group.measurement_time(Duration::from_secs(5));
// Test specific thread counts: powers of 2 + some intermediate values
let thread_counts: Vec<usize> = (1..=max_threads)
.filter(|&n| {
n == 1 || // Always test single-threaded
n % 2 == 0 || // Always test even threads
n == max_threads // Maximum threads
})
.collect();
for threads in thread_counts {
let total_evals = threads * evals_per_thread;
group.throughput(Throughput::Elements(total_evals as u64));
group.bench_with_input(
BenchmarkId::new("eval", format!(" {threads} threads")),
&threads,
|b, &threads| {
b.iter_custom(|iters| {
let evals_per_thread = evals_per_thread * (iters as usize);
let (duration, policy_counters, total_evals_aggregated) = multi_threaded_eval(
black_box(threads),
black_box(evals_per_thread),
black_box(use_cloned_engines),
black_box(use_cloned_inputs),
);
// Sanity check: Ensure the expected number of evaluations matches the actual number performed per iteration batch.
// total_evals is the expected number for this batch, total_evals_aggregated is the sum over all iters.
assert_eq!(total_evals, total_evals_aggregated/iters as usize);
// On one iteration, print policy evaluation statistics
if iters == 1 {
// println!("\nPolicy Evaluation Statistics:");
for (policy_name, count) in &policy_counters {
// println!(" {}: {} evaluations", policy_name, count);
if *count == 0 {
println!("\x1b[31mERROR: Policy '{}' was never evaluated successfully!\x1b[0m", policy_name);
}
}
}
duration
});
},
);
}
group.finish();
}
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -0,0 +1,117 @@
// This module provides the full set of policies, inputs, and policy names for evaluation benchmarks.
// Policies and inputs are now loaded from external files.
use std::fs;
use std::path::Path;
pub fn policies_with_inputs() -> Vec<(String, Vec<String>)> {
let policy_with_input_files = [
(
"rbac_policy.rego",
vec!["rbac_input.json", "rbac_input2.json", "rbac_input3.json"],
),
(
"api_access_policy.rego",
vec![
"api_access_input.json",
"api_access_input2.json",
"api_access_input3.json",
],
),
(
"data_sensitivity_policy.rego",
vec![
"data_sensitivity_input.json",
"data_sensitivity_input2.json",
"data_sensitivity_input3.json",
],
),
(
"time_based_policy.rego",
vec![
"time_based_input.json",
"time_based_input2.json",
"time_based_input3.json",
],
),
(
"data_processing_policy.rego",
vec![
"data_processing_input.json",
"data_processing_input2.json",
"data_processing_input3.json",
],
),
(
"azure_vm_policy.rego",
vec![
"azure_vm_input.json",
"azure_vm_input2.json",
"azure_vm_input3.json",
],
),
(
"azure_storage_policy.rego",
vec![
"azure_storage_input.json",
"azure_storage_input2.json",
"azure_storage_input3.json",
],
),
(
"azure_keyvault_policy.rego",
vec![
"azure_keyvault_input.json",
"azure_keyvault_input2.json",
"azure_keyvault_input3.json",
],
),
(
"azure_nsg_policy.rego",
vec![
"azure_nsg_input.json",
"azure_nsg_input2.json",
"azure_nsg_input3.json",
],
),
];
let mut policies_and_inputs = Vec::new();
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("benches")
.join("evaluation")
.join("test_data");
for (policy_file, input_files) in policy_with_input_files.iter() {
let policy_path = base_dir.join("policies").join(policy_file);
let policy_content = fs::read_to_string(&policy_path)
.unwrap_or_else(|e| panic!("Failed to read policy file {:?}: {}", policy_path, e));
let mut input_contents = Vec::new();
for input_file in input_files {
let input_path = base_dir.join("inputs").join(input_file);
let input_content = fs::read_to_string(&input_path)
.unwrap_or_else(|e| panic!("Failed to read input file {:?}: {}", input_path, e));
input_contents.push(input_content);
}
policies_and_inputs.push((policy_content, input_contents));
}
policies_and_inputs
}
pub fn policy_names() -> Vec<&'static str> {
vec![
"rbac_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
"azure_vm_policy",
"azure_storage_policy",
"azure_keyvault_policy",
"azure_nsg_policy",
]
}

View File

@@ -0,0 +1,15 @@
{
"request": {
"method": "GET",
"path": "/api/v1/users/123"
},
"user": {
"id": "user123",
"scope": ["read:users", "write:users"],
"department": "engineering"
},
"resource": {
"owner": "user123",
"type": "user"
}
}

View File

@@ -0,0 +1,15 @@
{
"request": {
"method": "POST",
"path": "/api/v1/users"
},
"user": {
"id": "user456",
"scope": ["write:users", "admin:users"],
"department": "engineering"
},
"resource": {
"owner": "user456",
"type": "user"
}
}

View File

@@ -0,0 +1,15 @@
{
"request": {
"method": "DELETE",
"path": "/api/v1/users/789"
},
"user": {
"id": "admin123",
"scope": ["admin:users"],
"department": "security"
},
"resource": {
"owner": "user789",
"type": "user"
}
}

View File

@@ -0,0 +1,16 @@
{
"vault": {
"name": "mykeyvault",
"location": "eastus",
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"enablePurgeProtection": true,
"networkAcls": {
"defaultAction": "Deny",
"bypass": "AzureServices"
},
"tags": {
"environment": "production"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"vault": {
"name": "devkeyvault",
"location": "westus2",
"enableSoftDelete": true,
"softDeleteRetentionInDays": 30,
"enablePurgeProtection": false,
"networkAcls": {
"defaultAction": "Allow",
"bypass": "AzureServices"
},
"tags": {
"environment": "development"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"vault": {
"name": "prodkeyvault",
"location": "eastus",
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"enablePurgeProtection": true,
"networkAcls": {
"defaultAction": "Deny",
"bypass": "AzureServices"
},
"tags": {
"environment": "production"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"operation": "Microsoft.Network/networkSecurityGroups/securityRules/write",
"rule": {
"direction": "Inbound",
"access": "Allow",
"protocol": "TCP",
"sourceAddressPrefix": "10.0.0.0/24",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "80",
"priority": 1001
}
}

View File

@@ -0,0 +1,13 @@
{
"operation": "Microsoft.Network/networkSecurityGroups/securityRules/write",
"rule": {
"direction": "Inbound",
"access": "Allow",
"protocol": "TCP",
"sourceAddressPrefix": "172.16.0.0/16",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "22",
"priority": 1200
}
}

View File

@@ -0,0 +1,13 @@
{
"operation": "Microsoft.Network/networkSecurityGroups/securityRules/write",
"rule": {
"direction": "Inbound",
"access": "Allow",
"protocol": "TCP",
"sourceAddressPrefix": "203.0.113.0/24",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "443",
"priority": 300
}
}

View File

@@ -0,0 +1,15 @@
{
"account": {
"name": "mystorageaccount",
"tier": "Standard",
"replication": "LRS",
"location": "eastus",
"tags": {
"environment": "production"
}
},
"container": {
"name": "data",
"publicAccess": "None"
}
}

View File

@@ -0,0 +1,15 @@
{
"account": {
"name": "devstorageaccount",
"tier": "Premium",
"replication": "LRS",
"location": "westus2",
"tags": {
"environment": "production"
}
},
"container": {
"name": "logs",
"publicAccess": "None"
}
}

View File

@@ -0,0 +1,15 @@
{
"account": {
"name": "prodstorageaccount",
"tier": "Standard",
"replication": "GRS",
"location": "eastus",
"tags": {
"environment": "production"
}
},
"container": {
"name": "backups",
"publicAccess": "None"
}
}

View File

@@ -0,0 +1,14 @@
{
"vm": {
"size": "Standard_D2s_v3",
"os": "Linux",
"location": "eastus",
"tags": {
"environment": "production",
"department": "engineering"
}
},
"user": {
"department": "engineering"
}
}

View File

@@ -0,0 +1,14 @@
{
"vm": {
"size": "Standard_B1s",
"os": "Windows",
"location": "westus2",
"tags": {
"environment": "dev",
"department": "marketing"
}
},
"user": {
"department": "marketing"
}
}

View File

@@ -0,0 +1,14 @@
{
"vm": {
"size": "Standard_D4s_v3",
"os": "Linux",
"location": "eastus",
"tags": {
"environment": "production",
"department": "engineering"
}
},
"user": {
"department": "engineering"
}
}

View File

@@ -0,0 +1,16 @@
{
"operation": "collect",
"data": {
"type": "email",
"source": "user_input"
},
"consent": {
"given": true,
"purpose": "marketing",
"date": "2023-01-15"
},
"user": {
"age": 25,
"location": "US"
}
}

View File

@@ -0,0 +1,16 @@
{
"operation": "process",
"data": {
"type": "survey_response",
"source": "user_input"
},
"consent": {
"given": true,
"purpose": "analytics",
"date": "2023-06-15"
},
"user": {
"age": 30,
"location": "US"
}
}

View File

@@ -0,0 +1,16 @@
{
"operation": "delete",
"data": {
"type": "user_profile",
"source": "database"
},
"consent": {
"given": false,
"purpose": "none",
"date": "2022-01-01"
},
"user": {
"age": 16,
"location": "EU"
}
}

View File

@@ -0,0 +1,13 @@
{
"data": {
"type": "user_profile",
"classification": "personal",
"contains_pii": true,
"region": "EU"
},
"user": {
"clearance": "confidential",
"location": "EU"
},
"operation": "read"
}

View File

@@ -0,0 +1,13 @@
{
"data": {
"type": "financial_report",
"classification": "confidential",
"contains_pii": false,
"region": "US"
},
"user": {
"clearance": "secret",
"location": "US"
},
"operation": "read"
}

View File

@@ -0,0 +1,13 @@
{
"data": {
"type": "public_announcement",
"classification": "public",
"contains_pii": false,
"region": "GLOBAL"
},
"user": {
"clearance": "public",
"location": "EU"
},
"operation": "read"
}

View File

@@ -0,0 +1,12 @@
{
"user": {
"name": "alice",
"roles": ["viewer", "editor"]
},
"resource": {
"name": "document1",
"type": "document",
"owner": "alice"
},
"action": "read"
}

View File

@@ -0,0 +1,12 @@
{
"user": {
"name": "bob",
"roles": ["admin"]
},
"resource": {
"name": "document2",
"type": "document",
"owner": "bob"
},
"action": "write"
}

View File

@@ -0,0 +1,12 @@
{
"user": {
"name": "charlie",
"roles": ["viewer"]
},
"resource": {
"name": "document3",
"type": "document",
"owner": "alice"
},
"action": "read"
}

View File

@@ -0,0 +1,11 @@
{
"time": "09:30:00",
"day": "monday",
"user": {
"role": "employee",
"shift": "day"
},
"request": {
"urgent": false
}
}

View File

@@ -0,0 +1,11 @@
{
"time": "14:30:00",
"day": "wednesday",
"user": {
"role": "employee",
"shift": "day"
},
"request": {
"urgent": false
}
}

View File

@@ -0,0 +1,11 @@
{
"time": "22:00:00",
"day": "friday",
"user": {
"role": "admin",
"shift": "night"
},
"request": {
"urgent": true
}
}

View File

@@ -0,0 +1,13 @@
package bench
default allow := false
valid_api_paths := ["/api/v1/", "/api/v2/", "/api/v3/"]
allow if {
input.request.method == "GET"
some path in valid_api_paths
startswith(input.request.path, path)
input.user.authenticated == true
time.now_ns() - input.user.login_time < 86400000000000 # 24 hours in nanoseconds
}

View File

@@ -0,0 +1,28 @@
package bench
default allow := false
# Azure Key Vault access policy
valid_operations := [
"Microsoft.KeyVault/vaults/keys/read",
"Microsoft.KeyVault/vaults/secrets/read",
"Microsoft.KeyVault/vaults/certificates/read"
]
vault_admins := ["admin@company.com", "security@company.com"]
allow if {
input.operation in valid_operations
input.principal.type == "ServicePrincipal"
input.principal.appId != ""
input.resource.properties.enableSoftDelete == true
input.resource.properties.enablePurgeProtection == true
time.now_ns() - input.principal.createdTime < 31536000000000000 # Less than 1 year old
}
allow if {
input.operation in valid_operations
input.principal.type == "User"
input.principal.userPrincipalName in vault_admins
input.context.conditionalAccess.compliant == true
}

View File

@@ -0,0 +1,31 @@
package bench
default allow := false
# Azure Network Security Group rules policy
dangerous_ports := [22, 3389, 1433, 3306, 5432, 6379, 27017]
internal_networks := ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
is_internal_source if {
some network in internal_networks
net.cidr_contains(network, input.rule.sourceAddressPrefix)
}
allow if {
input.operation == "Microsoft.Network/networkSecurityGroups/securityRules/write"
input.rule.direction == "Inbound"
input.rule.access == "Allow"
input.rule.destinationPortRange != "*"
not input.rule.destinationPortRange in dangerous_ports
input.rule.sourceAddressPrefix != "*"
input.rule.sourceAddressPrefix != "Internet"
}
allow if {
input.operation == "Microsoft.Network/networkSecurityGroups/securityRules/write"
input.rule.direction == "Inbound"
input.rule.access == "Allow"
input.rule.destinationPortRange in dangerous_ports
is_internal_source
input.rule.priority >= 1000
}

View File

@@ -0,0 +1,17 @@
package bench
default allow := false
# Azure Storage Account security policy
required_encryption_algorithms := ["AES256", "RSA-OAEP"]
allow if {
input.operation == "Microsoft.Storage/storageAccounts/write"
input.resource.properties.supportsHttpsTrafficOnly == true
input.resource.properties.minimumTlsVersion == "TLS1_2"
input.resource.properties.encryption.services.blob.enabled == true
input.resource.properties.encryption.keySource == "Microsoft.Storage"
input.resource.properties.allowBlobPublicAccess == false
input.resource.properties.networkAcls.defaultAction == "Deny"
count(input.resource.properties.networkAcls.ipRules) > 0
}

View File

@@ -0,0 +1,20 @@
package bench
default allow := false
# Azure VM deployment policy
allowed_vm_sizes := [
"Standard_B1s", "Standard_B2s", "Standard_B4ms",
"Standard_D2s_v3", "Standard_D4s_v3", "Standard_F2s_v2"
]
allowed_regions := ["eastus", "westus2", "northeurope", "southeastasia"]
allow if {
input.operation == "Microsoft.Compute/virtualMachines/write"
input.resource.properties.hardwareProfile.vmSize in allowed_vm_sizes
input.resource.location in allowed_regions
input.resource.properties.osProfile.adminPassword == null # Require SSH keys
count(input.resource.tags) > 0 # Must have tags
input.resource.tags.environment in ["dev", "test", "prod"]
}

View File

@@ -0,0 +1,28 @@
package bench
default allow := false
# Complex data filtering and aggregation
sensitive_fields := ["ssn", "credit_card", "password"]
contains_sensitive_data if {
some field in sensitive_fields
object.get(input.data, field, null) != null
}
user_clearance_level := object.get(input.user.attributes, "clearance", 0)
required_clearance := 3 if contains_sensitive_data else := 1
allow if {
user_clearance_level >= required_clearance
input.operation in ["read", "export"]
count(input.data) > 0
count(input.data) <= 1000 # Limit data size
}
allow if {
input.user.role == "data_processor"
input.operation == "transform"
not contains_sensitive_data
}

View File

@@ -0,0 +1,25 @@
package bench
default allow := false
rbac_roles := {
"admin": ["read", "write", "delete", "admin"],
"manager": ["read", "write"],
"user": ["read"]
}
user_permissions contains perm if {
some role in input.user.roles
perm := rbac_roles[role][_]
}
allow if {
input.action in user_permissions
input.resource.owner == input.user.id
}
allow if {
input.action in user_permissions
input.resource.public == true
input.action == "read"
}

View File

@@ -0,0 +1,10 @@
package bench
default allow := false
allow if {
input.user.role == "admin"
input.action in ["read", "write", "delete"]
input.resource.classification in ["public", "internal"]
count(input.user.permissions) > 0
}

View File

@@ -0,0 +1,23 @@
package bench
default allow := false
# Time-based access control with complex conditions
business_hours if {
hour := time.clock([time.now_ns(), "America/New_York"])[0]
hour >= 9
hour < 17
}
allow if {
input.user.department in ["engineering", "product"]
input.action == "deploy"
business_hours
count([x | x := input.approvals[_]; x.status == "approved"]) >= 2
}
allow if {
input.user.emergency_access == true
input.action in ["read", "diagnose"]
input.justification != ""
}

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);

View File

@@ -0,0 +1,186 @@
use std::hint::black_box;
use regorus::{Engine, Value};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use serde_json::json;
fn engine_with_policy(policy: &str) -> Engine {
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy.to_string())
.unwrap();
engine
}
fn eval_principal(engine: &mut Engine) {
engine.set_input(black_box(json!({"principal": "admin"}).into()));
let result = engine
.eval_rule(black_box("data.bench.allow".to_string()))
.unwrap();
assert_eq!(result, true.into());
}
fn allow_with_simple_equality(c: &mut Criterion) {
c.bench_function("simple equality check with constant", |b| {
let mut engine = engine_with_policy(
r#"
package bench
allow if input.principal == "admin"
"#,
);
b.iter(|| eval_principal(&mut engine))
});
c.bench_function("simple equality check with data", |b| {
let mut engine = engine_with_policy(
r#"
package bench
allow if input.principal == data.allowed_principal
"#,
);
engine
.add_data(json!({"allowed_principal": "admin"}).into())
.unwrap();
b.iter(|| eval_principal(&mut engine))
});
}
fn allow_with_simple_membership(c: &mut Criterion) {
let generate_principals = |n: usize| {
(0..n)
.map(|i| i.to_string())
.chain(std::iter::once("admin".to_string()))
.collect::<Vec<_>>()
};
let mut group = c.benchmark_group("allow with simple membership");
for size in [32, 64, 128, 512, 1024, 2048].iter() {
group.bench_with_input(BenchmarkId::new("with constant", size), size, |b, &size| {
let principals = generate_principals(size).join("\",\"");
let mut engine = engine_with_policy(&format!(
r#"
package bench
allowed_principals := {{
"{principals}"
}}
allow if input.principal in allowed_principals
"#
));
b.iter(|| eval_principal(&mut engine))
});
group.bench_with_input(BenchmarkId::new("with data", size), size, |b, &size| {
let principals = generate_principals(size);
let mut engine = engine_with_policy(
r#"
package bench
allow if input.principal in data.allowed_principals
"#,
);
engine
.add_data(json!({"allowed_principals": principals}).into())
.unwrap();
b.iter(|| eval_principal(&mut engine))
});
}
group.finish();
}
fn clone(c: &mut Criterion) {
// Use Arc<BtreeMap> as a reference. Clone will only increment
// the reference count.
let mut m = std::collections::BTreeMap::default();
m.insert(1, 2);
let m = std::sync::Arc::new(m);
c.bench_function("clone: Arc<BTreeMap>", |b| {
b.iter(|| {
let _ = m.clone();
})
});
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_policy_from_file("tests/aci/framework.rego")
.unwrap();
engine.add_policy_from_file("tests/aci/api.rego").unwrap();
engine
.add_policy_from_file("tests/aci/policy.rego")
.unwrap();
engine
.add_data(Value::from_json_file("tests/aci/data.json").expect("failed to load data.json"))
.expect("failed to add data");
engine.set_input(
Value::from_json_file("tests/aci/input.json").expect("failed to load input.json"),
);
// An engine without preparation will not have processed fields populated.
c.bench_function("clone: engine with aci policies", |b| {
b.iter(|| {
let _ = engine.clone();
})
});
// Trigger engine preparation.
let _ = engine.eval_query("data.framework.mount_overlay".to_string(), false);
// Prepared engine will have many more fields populated. But the fields are
// immutable after preparation and will be shared between clones.
c.bench_function("clone: prepared engine with aci policies", |b| {
b.iter(|| {
let _ = engine.clone();
})
});
}
fn aci_policy_eval(c: &mut Criterion) {
let mut group = c.benchmark_group("ACI Policy Eval");
let rules = ["data.policy.mount_overlay", "data.policy.mount_device"];
for rule in rules {
group.bench_with_input(BenchmarkId::new("rule", rule), &rule, |b, rule| {
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_policy_from_file("tests/aci/api.rego")
.expect("failed to add api.rego");
engine
.add_policy_from_file("tests/aci/framework.rego")
.expect("failed to add framework.rego");
engine
.add_policy_from_file("tests/aci/policy.rego")
.expect("failed to add policy.rego");
engine
.add_data(
Value::from_json_file("tests/aci/data.json").expect("failed to load data.json"),
)
.expect("failed to add data");
let input =
Value::from_json_file("tests/aci/input.json").expect("failed to load input.json");
engine.set_input(input.clone());
engine.eval_rule(rule.to_string()).unwrap();
b.iter(|| {
engine.eval_rule(rule.to_string()).unwrap();
})
});
}
group.finish();
}
criterion_group!(
benches,
allow_with_simple_equality,
allow_with_simple_membership,
clone,
aci_policy_eval
);
criterion_main!(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

@@ -0,0 +1,886 @@
use criterion::{criterion_group, criterion_main, Criterion};
use regorus::Value;
use regorus::{Schema, SchemaValidator};
use serde_json::json;
// Observed: validate_string - 3.19 ns/iter
fn bench_string_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "string",
"minLength": 3,
"maxLength": 10
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from("hello");
c.bench_function("validate_string", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_number - 146.5 ns/iter
fn bench_number_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "number",
"minimum": 0.0,
"maximum": 100.0
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(42.5);
c.bench_function("validate_number", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_array - 95.0 ns/iter
fn bench_array_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": { "type": "integer" },
"minItems": 2,
"maxItems": 5
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!([1, 2, 3]));
c.bench_function("validate_array", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_object - 126.9 ns/iter
fn bench_object_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["name", "age"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({"name": "Alice", "age": 30}));
c.bench_function("validate_object", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_complex_nested - 710.5 ns/iter
fn bench_complex_nested_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"id": { "type": "string" },
"profile": {
"type": "object",
"properties": {
"email": { "type": "string" },
"roles": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["email", "roles"]
}
},
"required": ["id", "profile"]
},
"active": { "type": "boolean" }
},
"required": ["user", "active"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"user": {
"id": "u123",
"profile": {
"email": "alice@example.com",
"roles": ["admin", "user"]
}
},
"active": true
}));
c.bench_function("validate_complex_nested", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_string_pattern - 29.99 µs/iter
fn bench_string_pattern_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "string",
"pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from("user@example.com");
c.bench_function("validate_string_pattern", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_enum - 7.26 ns/iter
fn bench_enum_validation(c: &mut Criterion) {
let schema_json = json!({
"enum": ["pending", "approved", "rejected", "cancelled"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from("approved");
c.bench_function("validate_enum", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_boolean - 3.22 ns/iter
fn bench_boolean_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "boolean"
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(true);
c.bench_function("validate_boolean", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_null - 3.22 ns/iter
fn bench_null_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "null"
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::Null;
c.bench_function("validate_null", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_large_array - 17.30 µs/iter
fn bench_large_array_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": { "type": "number" },
"minItems": 50,
"maxItems": 200
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let large_array: Vec<_> = (0..100).map(|i| json!(i as f64)).collect();
let value = Value::from(json!(large_array));
c.bench_function("validate_large_array", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_deeply_nested - 468.2 ns/iter
fn bench_deeply_nested_object(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"level1": {
"type": "object",
"properties": {
"level2": {
"type": "object",
"properties": {
"level3": {
"type": "object",
"properties": {
"level4": {
"type": "object",
"properties": {
"level5": {
"type": "string"
}
},
"required": ["level5"]
}
},
"required": ["level4"]
}
},
"required": ["level3"]
}
},
"required": ["level2"]
}
},
"required": ["level1"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"level1": {
"level2": {
"level3": {
"level4": {
"level5": "deep value"
}
}
}
}
}));
c.bench_function("validate_deeply_nested", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_mixed_type_array - 1.36 µs/iter
fn bench_mixed_type_array(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": {
"anyOf": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" }
]
}
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!(["hello", 42, true, "world", 99.5, false]));
c.bench_function("validate_mixed_type_array", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_additional_properties - 366.4 ns/iter
fn bench_additional_properties(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"additionalProperties": { "type": "string" },
"required": ["name"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"name": "Alice",
"age": 30,
"city": "New York",
"country": "USA",
"occupation": "Engineer"
}));
c.bench_function("validate_additional_properties", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_array_constraints - 146.2 ns/iter
fn bench_array_constraints(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": { "type": "string" },
"minItems": 2,
"maxItems": 10
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!(["apple", "banana", "cherry", "date", "elderberry"]));
c.bench_function("validate_array_constraints", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_multi_level - 915.8 ns/iter
fn bench_multi_level_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"profile": {
"type": "object",
"properties": {
"settings": {
"type": "object",
"properties": {
"theme": {
"enum": ["light", "dark", "auto"]
},
"notifications": {
"type": "boolean"
}
},
"required": ["theme"],
"additionalProperties": { "type": "string" }
}
},
"required": ["settings"],
"additionalProperties": { "type": "any" }
}
},
"required": ["profile"],
"additionalProperties": { "type": "any" }
}
},
"required": ["user"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"user": {
"profile": {
"settings": {
"theme": "dark",
"notifications": true,
"language": "en"
},
"avatar": "default.png"
},
"lastLogin": "2024-01-01"
},
"metadata": "extra info"
}));
c.bench_function("validate_multi_level", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Azure Resource Validation Benchmarks
// Observed: validate_azure_vm_resource - 34.74 µs/iter
fn bench_azure_vm_resource_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"type": {
"const": "Microsoft.Compute/virtualMachines"
},
"apiVersion": {
"enum": ["2021-03-01", "2021-07-01", "2022-03-01"]
},
"name": {
"type": "string",
"pattern": "^[a-zA-Z0-9-._]{1,64}$"
},
"location": {
"type": "string",
"description": "Azure region where the VM will be deployed"
},
"properties": {
"type": "object",
"properties": {
"hardwareProfile": {
"type": "object",
"properties": {
"vmSize": {
"enum": ["Standard_B1s", "Standard_B2s", "Standard_D2s_v3", "Standard_D4s_v3"]
}
},
"required": ["vmSize"]
},
"osProfile": {
"type": "object",
"properties": {
"computerName": {
"type": "string"
},
"adminUsername": {
"type": "string"
}
},
"required": ["computerName", "adminUsername"]
}
},
"required": ["hardwareProfile", "osProfile"]
}
},
"required": ["type", "apiVersion", "name", "location", "properties"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2021-03-01",
"name": "my-vm-01",
"location": "eastus",
"properties": {
"hardwareProfile": {
"vmSize": "Standard_B2s"
},
"osProfile": {
"computerName": "my-computer",
"adminUsername": "azureuser"
}
}
}));
c.bench_function("validate_azure_vm_resource", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_storage_resource - 22.12 µs/iter
fn bench_azure_storage_resource_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"type": {
"const": "Microsoft.Storage/storageAccounts"
},
"apiVersion": {
"enum": ["2021-04-01", "2021-06-01", "2022-05-01"]
},
"name": {
"type": "string",
"pattern": "^[a-z0-9]{3,24}$"
},
"location": {
"type": "string"
},
"sku": {
"type": "object",
"properties": {
"name": {
"enum": ["Standard_LRS", "Standard_GRS", "Standard_RAGRS", "Premium_LRS"]
}
},
"required": ["name"]
},
"kind": {
"enum": ["Storage", "StorageV2", "BlobStorage", "FileStorage", "BlockBlobStorage"]
},
"properties": {
"type": "object",
"properties": {
"accessTier": {
"enum": ["Hot", "Cool", "Archive"]
},
"encryption": {
"type": "object",
"properties": {
"services": {
"type": "object"
}
}
}
},
"additionalProperties": { "type": "any" }
}
},
"required": ["type", "apiVersion", "name", "location", "sku", "kind"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "mystorageaccount001",
"location": "westus2",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"accessTier": "Hot",
"encryption": {
"services": {}
}
}
}));
c.bench_function("validate_azure_storage_resource", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_arm_template - 1.99 µs/iter
fn bench_azure_arm_template_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"$schema": {
"type": "string"
},
"contentVersion": {
"type": "string"
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"variables": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"resources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"apiVersion": {
"type": "string"
},
"name": {
"type": "string"
},
"location": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"tags": {
"type": "object",
"additionalProperties": { "type": "string" }
}
},
"required": ["type", "apiVersion", "name"],
"additionalProperties": { "type": "any" }
}
},
"outputs": {
"type": "object",
"additionalProperties": { "type": "any" }
}
},
"required": ["resources"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmName": {
"type": "string",
"defaultValue": "myVM"
}
},
"variables": {
"storageAccountName": "[concat('storage', uniqueString(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2021-03-01",
"name": "[parameters('vmName')]",
"location": "[resourceGroup().location]",
"properties": {
"hardwareProfile": {
"vmSize": "Standard_B1s"
}
},
"tags": {
"environment": "dev",
"project": "test"
}
}
],
"outputs": {
"vmId": {
"type": "string",
"value": "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]"
}
}
}));
c.bench_function("validate_azure_arm_template", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Azure Policy Effect Validation Benchmarks
// Observed: validate_azure_policy_deny_effect - 188.6 ns/iter
fn bench_azure_policy_deny_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"const": "deny"
},
"description": {
"type": "string"
}
},
"required": ["effect"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "deny",
"description": "Deny resources that don't meet security requirements"
}));
c.bench_function("validate_azure_policy_deny_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_policy_audit_effect - 516.7 ns/iter
fn bench_azure_policy_audit_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"const": "audit"
},
"description": {
"type": "string"
},
"auditDetails": {
"type": "object",
"properties": {
"category": {
"enum": ["security", "compliance", "cost", "operational"]
},
"severity": {
"enum": ["low", "medium", "high", "critical"]
}
},
"additionalProperties": { "type": "any" }
}
},
"required": ["effect"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "audit",
"description": "Audit non-compliant resources",
"auditDetails": {
"category": "security",
"severity": "high"
}
}));
c.bench_function("validate_azure_policy_audit_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_policy_modify_effect - 1.17 µs/iter
fn bench_azure_policy_modify_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"const": "modify"
},
"description": {
"type": "string"
},
"modifyDetails": {
"type": "object",
"properties": {
"roleDefinitionIds": {
"type": "array",
"items": { "type": "string" }
},
"operations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"operation": {
"enum": ["add", "replace", "remove"]
},
"field": {
"type": "string"
},
"value": {
"type": "any"
}
},
"required": ["operation", "field"],
"additionalProperties": { "type": "any" }
}
}
},
"required": ["roleDefinitionIds", "operations"],
"additionalProperties": { "type": "any" }
}
},
"required": ["effect", "modifyDetails"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "modify",
"description": "Modify resources to ensure compliance",
"modifyDetails": {
"roleDefinitionIds": [
"/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
],
"operations": [
{
"operation": "add",
"field": "tags.environment",
"value": "production"
}
]
}
}));
c.bench_function("validate_azure_policy_modify_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_policy_complex_effect - 1.40 µs/iter
fn bench_azure_policy_complex_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"enum": ["auditIfNotExists", "deployIfNotExists"]
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"existenceCondition": {
"type": "object",
"properties": {
"field": { "type": "string" },
"equals": { "type": "string" }
},
"required": ["field"],
"additionalProperties": { "type": "any" }
},
"deployment": {
"type": "object",
"properties": {
"properties": {
"type": "object",
"properties": {
"mode": {
"enum": ["incremental", "complete"]
},
"template": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "any" }
}
},
"required": ["mode", "template"],
"additionalProperties": { "type": "any" }
}
},
"required": ["properties"],
"additionalProperties": { "type": "any" }
}
},
"required": ["effect"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "deployIfNotExists",
"parameters": {},
"existenceCondition": {
"field": "Microsoft.Security/complianceResults/resourceStatus",
"equals": "OffByPolicy"
},
"deployment": {
"properties": {
"mode": "incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": []
},
"parameters": {}
}
}
}));
c.bench_function("validate_azure_policy_complex_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
criterion_group!(
schema_validation_benches,
bench_string_validation,
bench_number_validation,
bench_array_validation,
bench_object_validation,
bench_complex_nested_validation,
bench_string_pattern_validation,
bench_enum_validation,
bench_boolean_validation,
bench_null_validation,
bench_large_array_validation,
bench_deeply_nested_object,
bench_mixed_type_array,
bench_additional_properties,
bench_array_constraints,
bench_multi_level_validation,
bench_azure_vm_resource_validation,
bench_azure_storage_resource_validation,
bench_azure_arm_template_validation,
bench_azure_policy_deny_effect_validation,
bench_azure_policy_audit_effect_validation,
bench_azure_policy_modify_effect_validation,
bench_azure_policy_complex_effect_validation
);
criterion_main!(schema_validation_benches);

View File

@@ -0,0 +1,44 @@
# Copyright (c) Microsoft
# Licensed under the MIT License.
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
# Use a tag that has a fix for https://github.com/corrosion-rs/corrosion/issues/590
GIT_TAG 6be991bb34c348dfb8344be22f3606288ea5c7fd
)
FetchContent_MakeAvailable(Corrosion)
project("regorus-test")
corrosion_import_crate(
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
MANIFEST_PATH "../ffi/Cargo.toml"
# Always build regorus in Release mode.
PROFILE "release"
# Only build the "regorus-ffi" crate.
CRATES "regorus-ffi"
# Turn off std support in regorus-ffi.
NO_DEFAULT_FEATURES
NO_STD
# custom_allocator allows using a custom memory allocator.
# To use malloc/free remove custom_allocator below.
# Additionally, select specific features in regorus.
# See regorus/opa_no_std
FEATURES "custom_allocator,regorus/semver"
LOCKED
# Link statically
CRATE_TYPES staticlib FLAGS --crate-type=staticlib
)
add_executable(regorus_test main.c)
# Add path to <regorus-source-folder>/bindings/ffi
target_include_directories(regorus_test PRIVATE "../ffi")
target_link_libraries(regorus_test regorus_ffi)

132
bindings/c-nostd/main.c Normal file
View File

@@ -0,0 +1,132 @@
#include <stdio.h>
#include <stdlib.h>
#if defined(_WIN32)
#include <malloc.h>
#endif
#include "regorus.h"
// Regorus has been built for no_std and cannot access files.
char *file_to_string(const char *file)
{
char *buffer = 0;
long length;
FILE *f = fopen(file, "rb");
if (f)
{
fseek(f, 0, SEEK_END);
length = ftell(f);
fseek(f, 0, SEEK_SET);
buffer = malloc(length + 1);
buffer[length] = '\0';
if (buffer)
{
fread(buffer, 1, length, f);
}
fclose(f);
}
return buffer;
}
// If regorus is built with custom-allocator, then provide implementation.
uint8_t *regorus_aligned_alloc(size_t alignment, size_t size)
{
// Aligned allocations must respect platform quirks: Windows offers
// _aligned_malloc/_aligned_free, while macOS/Linux reject aligned_alloc
// calls when size is not a multiple of alignment, so we rely on
// posix_memalign for the no_std build.
#if defined(_WIN32)
return (uint8_t *)_aligned_malloc(size, alignment);
#else
void *ptr = NULL;
// posix_memalign requires alignment to be at least sizeof(void*)
// and a power of two; normalize here so small requests succeed.
if (alignment < sizeof(void *))
{
alignment = sizeof(void *);
}
if (posix_memalign(&ptr, alignment, size) != 0)
{
return NULL;
}
return (uint8_t *)ptr;
#endif
}
void regorus_free(uint8_t *ptr)
{
#if defined(_WIN32)
_aligned_free(ptr);
#else
free(ptr);
#endif
}
int main()
{
// Create engine.
RegorusEngine *engine = regorus_engine_new();
RegorusResult r;
char *buffer = NULL;
// Turn on rego v0 since policy uses v0.
r = regorus_engine_set_rego_v0(engine, true);
if (r.status != Ok)
goto error;
// Load policies.
r = regorus_engine_add_policy(engine, "framework.rego", (buffer = file_to_string("../../../tests/aci/framework.rego")));
free(buffer);
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy(engine, "api.rego", (buffer = file_to_string("../../../tests/aci/api.rego")));
free(buffer);
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy(engine, "policy.rego", (buffer = file_to_string("../../../tests/aci/policy.rego")));
free(buffer);
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
// Add data
r = regorus_engine_add_data_json(engine, (buffer = file_to_string("../../../tests/aci/data.json")));
free(buffer);
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Set input
r = regorus_engine_set_input_json(engine, (buffer = file_to_string("../../../tests/aci/input.json")));
free(buffer);
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Eval rule.
r = regorus_engine_eval_rule(engine, "data.framework.mount_overlay");
if (r.status != Ok)
goto error;
// Print output
printf("%s", r.output);
regorus_result_drop(r);
// Free the engine.
regorus_engine_drop(engine);
return 0;
error:
printf("%s", r.error_message);
return 1;
}

45
bindings/c/CMakeLists.txt Normal file
View File

@@ -0,0 +1,45 @@
# Copyright (c) Microsoft
# Licensed under the MIT License.
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
# Use a tag that has a fix for https://github.com/corrosion-rs/corrosion/issues/590
GIT_TAG 6be991bb34c348dfb8344be22f3606288ea5c7fd
)
FetchContent_MakeAvailable(Corrosion)
project("regorus-test")
enable_testing()
corrosion_import_crate(
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
MANIFEST_PATH "../ffi/Cargo.toml"
# Always build regorus in Release mode.
PROFILE "release"
# Only build the "regorus-ffi" crate.
CRATES "regorus-ffi"
# Select specific features in regorus.
FEATURES "regorus/semver"
LOCKED
# Link statically
CRATE_TYPES "cdylib"
)
add_executable(regorus_test main.c)
# Add path to <regorus-source-folder>/bindings/ffi
target_include_directories(regorus_test PRIVATE "../ffi")
target_link_libraries(regorus_test regorus_ffi)
add_executable(regorus_rvm_test rvm_tests.c)
target_include_directories(regorus_rvm_test PRIVATE "../ffi")
target_link_libraries(regorus_rvm_test regorus_ffi)
add_test(NAME regorus_c_engine COMMAND regorus_test)
add_test(NAME regorus_c_rvm COMMAND regorus_rvm_test)

116
bindings/c/main.c Normal file
View File

@@ -0,0 +1,116 @@
#include <stdio.h>
#include "regorus.h"
int main() {
// Create engine.
RegorusEngine* engine = regorus_engine_new();
RegorusResult r;
// Turn on rego v0 since policy uses v0.
r = regorus_engine_set_rego_v0(engine, true);
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)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/api.rego");
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/policy.rego");
if (r.status != Ok)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
// Add data
r = regorus_engine_add_data_from_json_file(engine, "../../../tests/aci/data.json");
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Set input
r = regorus_engine_set_input_from_json_file(engine, "../../../tests/aci/input.json");
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Eval rule.
r = regorus_engine_eval_query(engine, "data.framework.mount_overlay");
if (r.status != Ok)
goto error;
// Print output
printf("%s\n", r.output);
regorus_result_drop(r);
// Free the engine.
regorus_engine_drop(engine);
// Create another engine.
engine = regorus_engine_new();
r = regorus_engine_add_policy(
engine,
"test.rego",
"package test\n"
"x = 1\n"
"message = `Hello`"
);
// Evaluate rule.
if (r.status != Ok)
goto error;
r = regorus_engine_set_enable_coverage(engine, true);
regorus_result_drop(r);
r = regorus_engine_eval_query(engine, "data.test.message");
if (r.status != Ok)
goto error;
// Print output
printf("%s\n", r.output);
regorus_result_drop(r);
// Print pretty coverage report.
r = regorus_engine_get_coverage_report_pretty(engine);
if (r.status != Ok)
goto error;
printf("%s\n", r.output);
regorus_result_drop(r);
// Free the engine.
regorus_engine_drop(engine);
return 0;
error:
printf("%s", r.error_message);
regorus_result_drop(r);
regorus_engine_drop(engine);
return 1;
}

289
bindings/c/rvm_tests.c Normal file
View File

@@ -0,0 +1,289 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include <stdio.h>
#include <string.h>
#include "regorus.h"
static int assert_ok(RegorusResult r, const char* message) {
if (r.status != Ok) {
fprintf(stderr, "%s: %s\n", message, r.error_message ? r.error_message : "(no error)");
return 0;
}
return 1;
}
int main() {
RegorusResult result = {0};
bool result_valid = false;
RegorusProgram* program = NULL;
RegorusBuffer* buffer = NULL;
RegorusProgram* program2 = NULL;
RegorusRvm* vm = NULL;
RegorusProgram* host_program = NULL;
RegorusRvm* host_vm = NULL;
bool is_partial = false;
int exit_code = 1;
const char* data_json =
"{"
" \"roles\": {"
" \"alice\": [\"admin\", \"reader\"]"
" }"
"}";
const char* input_json =
"{"
" \"user\": \"alice\","
" \"actions\": [\"read\"]"
"}";
const char* module_text =
"package demo\n"
"default allow = false\n"
"allow if {\n"
" input.user == \"alice\"\n"
" some role in data.roles[input.user]\n"
" role == \"admin\"\n"
" count(input.actions) > 0\n"
"}\n";
const char* host_data_json = "{}";
const char* host_input_json = "{\"account\":{\"id\":\"acct-1\",\"active\":true}}";
const char* host_module_text =
"package demo\n"
"import rego.v1\n"
"default allow := false\n"
"allow if {\n"
" input.account.active == true\n"
" details := __builtin_host_await(input.account.id, \"account\")\n"
" details.tier == \"gold\"\n"
"}\n";
RegorusPolicyModule module;
module.id = "demo.rego";
module.content = module_text;
const char* entry_points[] = {"data.demo.allow"};
printf("Rego policy:\n%s\n", module_text);
printf("Compiling program from modules...\n");
result = regorus_program_compile_from_modules(
data_json,
&module,
1,
entry_points,
1
);
result_valid = true;
if (!assert_ok(result, "compile program")) {
goto Cleanup;
}
program = (RegorusProgram*)result.pointer_value;
regorus_result_drop(result);
result_valid = false;
printf("Generating assembly listing...\n");
result = regorus_program_generate_listing(program);
result_valid = true;
if (!assert_ok(result, "generate listing")) {
goto Cleanup;
}
printf("Assembly listing:\n%s\n", result.output ? result.output : "(null)");
regorus_result_drop(result);
result_valid = false;
printf("Serializing program...\n");
result = regorus_program_serialize_binary(program);
result_valid = true;
if (!assert_ok(result, "serialize program")) {
goto Cleanup;
}
buffer = (RegorusBuffer*)result.pointer_value;
regorus_result_drop(result);
result_valid = false;
printf("Deserializing program (%zu bytes)...\n", buffer->len);
result = regorus_program_deserialize_binary(
buffer->data,
buffer->len,
&is_partial
);
result_valid = true;
if (!assert_ok(result, "deserialize program")) {
goto Cleanup;
}
if (is_partial) {
fprintf(stderr, "deserialized program marked partial\n");
goto Cleanup;
}
program2 = (RegorusProgram*)result.pointer_value;
regorus_result_drop(result);
result_valid = false;
printf("Creating VM...\n");
vm = regorus_rvm_new();
if (!vm) {
fprintf(stderr, "failed to allocate VM\n");
goto Cleanup;
}
printf("Loading program into VM...\n");
result = regorus_rvm_load_program(vm, program2);
result_valid = true;
if (!assert_ok(result, "load program")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
printf("Setting data...\n");
result = regorus_rvm_set_data(vm, data_json);
result_valid = true;
if (!assert_ok(result, "set data")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
printf("Setting input...\n");
result = regorus_rvm_set_input(vm, input_json);
result_valid = true;
if (!assert_ok(result, "set input")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
printf("Executing entry point...\n");
result = regorus_rvm_execute(vm);
result_valid = true;
if (!assert_ok(result, "execute")) {
goto Cleanup;
}
printf("Execution result (data.demo.allow): %s\n",
result.output ? result.output : "(null)");
printf("Decision: user=alice action=read -> allow=%s\n",
result.output ? result.output : "(null)");
if (!result.output || strcmp(result.output, "true") != 0) {
fprintf(stderr, "unexpected result: %s\n", result.output);
goto Cleanup;
}
printf("\n--- HostAwait example (suspendable execution) ---\n");
RegorusPolicyModule host_module;
host_module.id = "host_await.rego";
host_module.content = host_module_text;
const char* host_entry_points[] = {"data.demo.allow"};
result = regorus_program_compile_from_modules(
host_data_json,
&host_module,
1,
host_entry_points,
1
);
result_valid = true;
if (!assert_ok(result, "compile host await program")) {
goto Cleanup;
}
host_program = (RegorusProgram*)result.pointer_value;
regorus_result_drop(result);
result_valid = false;
host_vm = regorus_rvm_new();
if (!host_vm) {
fprintf(stderr, "failed to allocate host await VM\n");
goto Cleanup;
}
result = regorus_rvm_set_execution_mode(host_vm, 1);
result_valid = true;
if (!assert_ok(result, "set execution mode")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_load_program(host_vm, host_program);
result_valid = true;
if (!assert_ok(result, "load host await program")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_set_data(host_vm, host_data_json);
result_valid = true;
if (!assert_ok(result, "set host data")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_set_input(host_vm, host_input_json);
result_valid = true;
if (!assert_ok(result, "set host input")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_execute(host_vm);
result_valid = true;
if (!assert_ok(result, "execute host await")) {
goto Cleanup;
}
printf("HostAwait initial result: %s\n", result.output ? result.output : "(null)");
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_get_execution_state(host_vm);
result_valid = true;
if (!assert_ok(result, "get execution state")) {
goto Cleanup;
}
printf("Execution state: %s\n", result.output ? result.output : "(null)");
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_resume(host_vm, "{\"tier\":\"gold\"}", true);
result_valid = true;
if (!assert_ok(result, "resume host await")) {
goto Cleanup;
}
printf("HostAwait resumed result: %s\n", result.output ? result.output : "(null)");
if (!result.output || strcmp(result.output, "true") != 0) {
fprintf(stderr, "unexpected host await result\n");
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
exit_code = 0;
Cleanup:
if (result_valid) {
regorus_result_drop(result);
}
if (host_vm) {
regorus_rvm_drop(host_vm);
}
if (host_program) {
regorus_program_drop(host_program);
}
if (vm) {
regorus_rvm_drop(vm);
}
if (program2) {
regorus_program_drop(program2);
}
if (buffer) {
regorus_buffer_drop(buffer);
}
if (program) {
regorus_program_drop(program);
}
return exit_code;
}

View File

@@ -0,0 +1,92 @@
# Copyright (c) Microsoft
# Licensed under the MIT License.
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
# Use a tag that has a fix for https://github.com/corrosion-rs/corrosion/issues/590
GIT_TAG 6be991bb34c348dfb8344be22f3606288ea5c7fd
)
FetchContent_MakeAvailable(Corrosion)
project("regorus-test")
set(CMAKE_CXX_STANDARD 17)
enable_testing()
# installable ffi target
corrosion_import_crate(
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
MANIFEST_PATH "../ffi/Cargo.toml"
# Always build regorus in Release mode.
PROFILE "release"
# Only build the "regorus-ffi" crate.
CRATES "regorus-ffi"
# Select specific features in regorus.
FEATURES "regorus/semver"
LOCKED
# Link statically
CRATE_TYPES "cdylib")
include(GNUInstallDirs)
set(regorus_ffi_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}/regorus_ffi)
set(regorus_ffi_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/regorus_ffi)
set(regorus_ffi_LIBDIR ${CMAKE_INSTALL_LIBDIR})
set(regorus_ffi_BINDIR ${CMAKE_INSTALL_BINDIR})
add_library(regorus_ffi::regorus_ffi ALIAS regorus_ffi)
corrosion_install(TARGETS regorus_ffi EXPORT regorus_ffi_targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
target_include_directories(regorus_ffi
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../ffi>
$<INSTALL_INTERFACE:${regorus_ffi_INCLUDEDIR}>
)
set(regorus_ffi_HEADER_FILES
regorus.hpp
../ffi/regorus.ffi.hpp
)
install(FILES ${regorus_ffi_HEADER_FILES}
DESTINATION ${regorus_ffi_INCLUDEDIR}
COMPONENT Devel
)
install(EXPORT regorus_ffi_targets
FILE regorus_ffi_targets.cmake
NAMESPACE regorus_ffi::
DESTINATION ${regorus_ffi_CONFIGDIR}
)
include(CMakePackageConfigHelpers)
configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/regorus_ffiConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/regorus_ffiConfig.cmake
INSTALL_DESTINATION ${regorus_ffi_CONFIGDIR}
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/regorus_ffiConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/corrosion/regorus_ffi_targetsCorrosion.cmake
DESTINATION ${regorus_ffi_CONFIGDIR}
)
# test binary
add_executable(regorus_test main.cpp)
target_link_libraries(regorus_test regorus_ffi::regorus_ffi)
add_executable(regorus_rvm_test rvm_tests.cpp)
target_link_libraries(regorus_rvm_test regorus_ffi::regorus_ffi)
add_test(NAME regorus_cpp_engine COMMAND regorus_test)
add_test(NAME regorus_cpp_rvm COMMAND regorus_rvm_test)

142
bindings/cpp/main.cpp Normal file
View File

@@ -0,0 +1,142 @@
#include <iostream>
#include "regorus.hpp"
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
rect := {`width`: 2, "height": 4}
cube := {"width": 3, `height`: 4, "depth": 5}
a := 42
b := false
c := null
d := {"a": a, "x": [b, c]}
index := 1
shapes := [rect, cube]
names := ["prod", `smoke1`, "dev"]
sites := [{"name": "prod"}, {"name": names[index]}, {"name": "dev"}]
e := {
a: "foo",
"three": c,
names[2]: b,
"four": d,
}
f := e["dev"])");
// Add data.
engine.add_data_json(R"({
"one": {
"bar": "Foo",
"baz": 5,
"be": true,
"bop": 23.4
},
"two": {
"bar": "Bar",
"baz": 12.3,
"be": false,
"bop": 42
}
})");
engine.add_data_json(R"({
"three": {
"bar": "Baz",
"baz": 15,
"be": true,
"bop": 4.23
}
})");
// Set input.
engine.set_input_json(R"({
"a": 10,
"b": "20",
"c": 30.0,
"d": true
})");
// Eval query.
auto result = engine.eval_query("[data.one, input.b, data.objects.sites[1]] = x");
if (result) {
std::cout<<result.output()<<std::endl;
} else {
std::cerr<<result.error()<<std::endl;
}
// Print coverage report
auto result1 = engine.get_coverage_report_pretty();
if (result1) {
std::cout<<result1.output()<<std::endl;
} else {
std::cerr<<result1.error()<<std::endl;
}
}
int main() {
// Create engine.
regorus::Engine engine;
engine.set_rego_v0(true);
// Load policies.
const char* policies[] = {
"../../../tests/aci/framework.rego",
"../../../tests/aci/policy.rego",
"../../../tests/aci/api.rego",
};
// Add policies and data.
for (auto policy : policies) {
auto result = engine.add_policy_from_file(policy);
if (!result) {
std::cerr<<result.error()<<std::endl;
return -1;
}
std::cout<<"Loaded package "<<result.output()<< std::endl;
}
{
auto result = engine.add_data_from_json_file("../../../tests/aci/data.json");
if (!result) {
std::cerr<<result.error()<<std::endl;
return -1;
}
}
// Set input and eval rule.
{
auto result = engine.set_input_from_json_file("../../../tests/aci/input.json");
if (!result) {
std::cerr<<result.error()<<std::endl;
return -1;
}
}
auto result = engine.eval_rule("data.framework.mount_overlay");
if (!result) {
std::cerr<<result.error()<<std::endl;
return -1;
}
std::cout<<result.output()<<std::endl;
example();
}

411
bindings/cpp/regorus.hpp Normal file
View File

@@ -0,0 +1,411 @@
#ifndef REGORUS_WRAPPER_HPP
#define REGORUS_WRAPPER_HPP
#include <cstddef>
#include <cstdint>
#include <memory>
#include <variant>
#include "regorus.ffi.hpp"
namespace regorus {
class Buffer;
class Program;
class Result {
public:
operator bool() const { return result.status == RegorusStatus::Ok; }
bool operator !() const { return result.status != RegorusStatus::Ok; }
const char* output() const {
if (*this && result.output) {
return result.output;
} else {
return "";
}
}
const char* error() const {
if (!*this && result.error_message) {
return result.error_message;
} else {
return "";
}
}
void* pointer() const {
return result.pointer_value;
}
Program program() const;
Buffer buffer() const;
Result(RegorusResult r) : result(r) {}
Result(Result&& other) noexcept : result(other.result) {
other.result.output = nullptr;
other.result.error_message = nullptr;
other.result.pointer_value = nullptr;
}
Result& operator=(Result&& other) noexcept {
if (this != &other) {
regorus_result_drop(result);
result = other.result;
other.result.output = nullptr;
other.result.error_message = nullptr;
other.result.pointer_value = nullptr;
}
return *this;
}
~Result() {
regorus_result_drop(result);
}
private:
RegorusResult result;
private:
Result(const Result&) = delete;
Result& operator=(const Result&) = delete;
};
class Engine {
public:
Engine() : Engine(regorus_engine_new()) {}
std::unique_ptr<Engine> clone() const {
return std::unique_ptr<Engine>(new Engine(regorus_engine_clone(engine)));
}
Result set_rego_v0(bool enable) {
return Result(regorus_engine_set_rego_v0(engine, enable));
}
Result add_policy(const char* path, const char* policy) {
return Result(regorus_engine_add_policy(engine, path, policy));
}
Result add_policy_from_file(const char* path) {
return Result(regorus_engine_add_policy_from_file(engine, path));
}
Result add_data_json(const char* data) {
return Result(regorus_engine_add_data_json(engine, data));
}
Result add_data_from_json_file(const char* path) {
return Result(regorus_engine_add_data_from_json_file(engine, path));
}
Result set_input_json(const char* input) {
return Result(regorus_engine_set_input_json(engine, input));
}
Result set_input_from_json_file(const char* path) {
return Result(regorus_engine_set_input_from_json_file(engine, path));
}
Result eval_query(const char* query) {
return Result(regorus_engine_eval_query(engine, query));
}
Result eval_rule(const char* rule) {
return Result(regorus_engine_eval_rule(engine, rule));
}
Result set_enable_coverage(bool enable) {
return Result(regorus_engine_set_enable_coverage(engine, enable));
}
Result clear_coverage_data() {
return Result(regorus_engine_clear_coverage_data(engine));
}
Result get_coverage_report() {
return Result(regorus_engine_get_coverage_report(engine));
}
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);
}
RegorusEngine* raw() const {
return engine;
}
private:
RegorusEngine* engine;
private:
Engine(RegorusEngine* e) : engine(e) {}
Engine(const Engine&) = delete;
Engine(Engine&&) = delete;
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) {}
Result eval_with_input(const char* input_json) {
return Result(regorus_compiled_policy_eval_with_input(policy, input_json));
}
Result get_policy_info() {
return Result(regorus_compiled_policy_get_policy_info(policy));
}
RegorusCompiledPolicy* raw() const {
return policy;
}
~CompiledPolicy() {
if (policy) {
regorus_compiled_policy_drop(policy);
}
}
private:
RegorusCompiledPolicy* policy;
CompiledPolicy(const CompiledPolicy&) = delete;
CompiledPolicy(CompiledPolicy&&) = delete;
CompiledPolicy& operator=(const CompiledPolicy&) = delete;
};
class Buffer {
public:
Buffer() : buffer(nullptr) {}
explicit Buffer(RegorusBuffer* b) : buffer(b) {}
const std::uint8_t* data() const {
return buffer ? buffer->data : nullptr;
}
size_t size() const {
return buffer ? buffer->len : 0;
}
RegorusBuffer* raw() const {
return buffer;
}
~Buffer() {
if (buffer) {
regorus_buffer_drop(buffer);
}
}
private:
RegorusBuffer* buffer;
Buffer(const Buffer&) = delete;
Buffer(Buffer&&) = delete;
Buffer& operator=(const Buffer&) = delete;
};
class Program {
public:
Program() : program(regorus_program_new()) {}
explicit Program(RegorusProgram* p) : program(p) {}
static Result compile_from_policy(
RegorusCompiledPolicy* compiled_policy,
const char* const* entry_points,
size_t entry_points_len
) {
return Result(regorus_program_compile_from_policy(
compiled_policy,
entry_points,
entry_points_len
));
}
static Result compile_from_modules(
const char* data_json,
const RegorusPolicyModule* modules,
size_t modules_len,
const char* const* entry_points,
size_t entry_points_len
) {
return Result(regorus_program_compile_from_modules(
data_json,
modules,
modules_len,
entry_points,
entry_points_len
));
}
static Result compile_from_engine(
RegorusEngine* engine,
const char* const* entry_points,
size_t entry_points_len
) {
return Result(regorus_engine_compile_program_with_entrypoints(
engine,
entry_points,
entry_points_len
));
}
Result serialize_binary() const {
return Result(regorus_program_serialize_binary(program));
}
static Result deserialize_binary(
const std::uint8_t* data,
size_t len,
bool* is_partial
) {
return Result(regorus_program_deserialize_binary(data, len, is_partial));
}
Result generate_listing() const {
return Result(regorus_program_generate_listing(program));
}
Result generate_tabular_listing() const {
return Result(regorus_program_generate_tabular_listing(program));
}
RegorusProgram* raw() const {
return program;
}
~Program() {
if (program) {
regorus_program_drop(program);
}
}
private:
RegorusProgram* program;
Program(const Program&) = delete;
Program(Program&&) = delete;
Program& operator=(const Program&) = delete;
};
inline Program Result::program() const {
return Program(reinterpret_cast<RegorusProgram*>(result.pointer_value));
}
inline Buffer Result::buffer() const {
return Buffer(reinterpret_cast<RegorusBuffer*>(result.pointer_value));
}
class Rvm {
public:
Rvm() : vm(regorus_rvm_new()) {}
explicit Rvm(RegorusRvm* v) : vm(v) {}
static Result create_with_policy(RegorusCompiledPolicy* compiled_policy) {
return Result(regorus_rvm_new_with_policy(compiled_policy));
}
Result load_program(const Program& program) {
return Result(regorus_rvm_load_program(vm, program.raw()));
}
Result set_data(const char* data_json) {
return Result(regorus_rvm_set_data(vm, data_json));
}
Result set_input(const char* input_json) {
return Result(regorus_rvm_set_input(vm, input_json));
}
Result set_max_instructions(size_t max_instructions) {
return Result(regorus_rvm_set_max_instructions(vm, max_instructions));
}
Result set_strict_builtin_errors(bool strict) {
return Result(regorus_rvm_set_strict_builtin_errors(vm, strict));
}
Result set_execution_mode(std::uint8_t mode) {
return Result(regorus_rvm_set_execution_mode(vm, mode));
}
Result set_step_mode(bool enabled) {
return Result(regorus_rvm_set_step_mode(vm, enabled));
}
Result set_execution_timer_config(bool has_config, RegorusExecutionTimerConfig config) {
return Result(regorus_rvm_set_execution_timer_config(vm, has_config, config));
}
Result execute() {
return Result(regorus_rvm_execute(vm));
}
Result execute_entry_point_by_name(const char* entry_point) {
return Result(regorus_rvm_execute_entry_point_by_name(vm, entry_point));
}
Result execute_entry_point_by_index(size_t index) {
return Result(regorus_rvm_execute_entry_point_by_index(vm, index));
}
Result resume(const char* resume_value_json, bool has_value) {
return Result(regorus_rvm_resume(vm, resume_value_json, has_value));
}
Result get_execution_state() {
return Result(regorus_rvm_get_execution_state(vm));
}
RegorusRvm* raw() const {
return vm;
}
~Rvm() {
if (vm) {
regorus_rvm_drop(vm);
}
}
private:
RegorusRvm* vm;
Rvm(const Rvm&) = delete;
Rvm(Rvm&&) = delete;
Rvm& operator=(const Rvm&) = delete;
};
inline Result compile_policy_with_entrypoint(
const char* data_json,
const RegorusPolicyModule* modules,
size_t modules_len,
const char* entry_point
) {
return Result(regorus_compile_policy_with_entrypoint(
data_json,
modules,
modules_len,
entry_point
));
}
}
#endif // REGORUS_WRAPPER_HPP

View File

@@ -0,0 +1,3 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/regorus_ffi_targets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/regorus_ffi_targetsCorrosion.cmake")

261
bindings/cpp/rvm_tests.cpp Normal file
View File

@@ -0,0 +1,261 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include <iostream>
#include <string>
#include "regorus.hpp"
int main() {
const char* data_json =
"{"
" \"roles\": {"
" \"alice\": [\"admin\", \"reader\"]"
" }"
"}";
const char* input_json =
"{"
" \"user\": \"alice\","
" \"actions\": [\"read\"]"
"}";
const char* module_text =
"package demo\n"
"default allow = false\n"
"allow if {\n"
" input.user == \"alice\"\n"
" some role in data.roles[input.user]\n"
" role == \"admin\"\n"
" count(input.actions) > 0\n"
"}\n";
const char* host_data_json = "{}";
const char* host_input_json = "{\"account\":{\"id\":\"acct-1\",\"active\":true}}";
const char* host_module_text =
"package demo\n"
"import rego.v1\n"
"default allow := false\n"
"allow if {\n"
" input.account.active == true\n"
" details := __builtin_host_await(input.account.id, \"account\")\n"
" details.tier == \"gold\"\n"
"}\n";
RegorusPolicyModule module;
module.id = "demo.rego";
module.content = module_text;
const char* entry_points[] = {"data.demo.allow"};
std::cout << "Rego policy:\n" << module_text << std::endl;
std::cout << "Compiling program from modules..." << std::endl;
auto program_result = regorus::Program::compile_from_modules(
data_json,
&module,
1,
entry_points,
1
);
if (!program_result) {
std::cerr << "compile program (modules): " << program_result.error() << std::endl;
return 1;
}
regorus::Program program = program_result.program();
std::cout << "Generating assembly listing..." << std::endl;
auto listing_result = program.generate_listing();
if (!listing_result) {
std::cerr << "generate listing: " << listing_result.error() << std::endl;
return 1;
}
std::cout << "Assembly listing:\n" << listing_result.output() << std::endl;
std::cout << "Serializing program..." << std::endl;
auto serialize_result = program.serialize_binary();
if (!serialize_result) {
std::cerr << "serialize program: " << serialize_result.error() << std::endl;
return 1;
}
regorus::Buffer buffer(reinterpret_cast<RegorusBuffer*>(serialize_result.pointer()));
bool is_partial = false;
std::cout << "Deserializing program (" << buffer.size() << " bytes)..." << std::endl;
auto deserialize_result = regorus::Program::deserialize_binary(
buffer.data(),
buffer.size(),
&is_partial
);
if (!deserialize_result) {
std::cerr << "deserialize program: " << deserialize_result.error() << std::endl;
return 1;
}
if (is_partial) {
std::cerr << "deserialized program marked partial" << std::endl;
return 1;
}
regorus::Program program2 = deserialize_result.program();
{
std::cout << "Creating VM..." << std::endl;
regorus::Rvm vm;
auto load_result = vm.load_program(program2);
if (!load_result) {
std::cerr << "load program: " << load_result.error() << std::endl;
return 1;
}
std::cout << "Setting data..." << std::endl;
auto data_result = vm.set_data(data_json);
if (!data_result) {
std::cerr << "set data: " << data_result.error() << std::endl;
return 1;
}
std::cout << "Setting input..." << std::endl;
auto input_result = vm.set_input(input_json);
if (!input_result) {
std::cerr << "set input: " << input_result.error() << std::endl;
return 1;
}
std::cout << "Executing entry point..." << std::endl;
auto exec_result = vm.execute();
if (!exec_result) {
std::cerr << "execute: " << exec_result.error() << std::endl;
return 1;
}
std::cout << "Execution result (data.demo.allow): " << exec_result.output() << std::endl;
std::cout << "Decision: user=alice action=read -> allow=" << exec_result.output() << std::endl;
if (std::string(exec_result.output()) != "true") {
std::cerr << "unexpected result: " << exec_result.output() << std::endl;
return 1;
}
}
regorus::Engine engine;
std::cout << "Compiling program from engine..." << std::endl;
auto add_policy_result = engine.add_policy("demo.rego", module_text);
if (!add_policy_result) {
std::cerr << "engine add policy: " << add_policy_result.error() << std::endl;
return 1;
}
auto engine_program_result = regorus::Program::compile_from_engine(
engine.raw(),
entry_points,
1
);
if (!engine_program_result) {
std::cerr << "compile program (engine): " << engine_program_result.error() << std::endl;
return 1;
}
regorus::Program engine_program = engine_program_result.program();
regorus::Rvm engine_vm;
auto engine_load_result = engine_vm.load_program(engine_program);
if (!engine_load_result) {
std::cerr << "engine load program: " << engine_load_result.error() << std::endl;
return 1;
}
std::cout << "Setting engine data..." << std::endl;
auto engine_data_result = engine_vm.set_data(data_json);
if (!engine_data_result) {
std::cerr << "engine set data: " << engine_data_result.error() << std::endl;
return 1;
}
std::cout << "Setting engine input..." << std::endl;
auto engine_input_result = engine_vm.set_input(input_json);
if (!engine_input_result) {
std::cerr << "engine set input: " << engine_input_result.error() << std::endl;
return 1;
}
std::cout << "Executing engine entry point..." << std::endl;
auto engine_exec_result = engine_vm.execute();
if (!engine_exec_result) {
std::cerr << "engine execute: " << engine_exec_result.error() << std::endl;
return 1;
}
std::cout << "Engine execution result (data.demo.allow): " << engine_exec_result.output() << std::endl;
std::cout << "Decision: user=alice action=read -> allow=" << engine_exec_result.output() << std::endl;
if (std::string(engine_exec_result.output()) != "true") {
std::cerr << "unexpected engine result: " << engine_exec_result.output() << std::endl;
return 1;
}
std::cout << "\n--- HostAwait example (suspendable execution) ---" << std::endl;
RegorusPolicyModule host_module;
host_module.id = "host_await.rego";
host_module.content = host_module_text;
const char* host_entry_points[] = {"data.demo.allow"};
auto host_program_result = regorus::Program::compile_from_modules(
host_data_json,
&host_module,
1,
host_entry_points,
1
);
if (!host_program_result) {
std::cerr << "compile host await program: " << host_program_result.error() << std::endl;
return 1;
}
regorus::Program host_program = host_program_result.program();
regorus::Rvm host_vm;
auto host_mode_result = host_vm.set_execution_mode(1);
if (!host_mode_result) {
std::cerr << "set execution mode: " << host_mode_result.error() << std::endl;
return 1;
}
auto host_load_result = host_vm.load_program(host_program);
if (!host_load_result) {
std::cerr << "load host await program: " << host_load_result.error() << std::endl;
return 1;
}
auto host_data_result = host_vm.set_data(host_data_json);
if (!host_data_result) {
std::cerr << "set host data: " << host_data_result.error() << std::endl;
return 1;
}
auto host_input_result = host_vm.set_input(host_input_json);
if (!host_input_result) {
std::cerr << "set host input: " << host_input_result.error() << std::endl;
return 1;
}
auto host_exec_result = host_vm.execute();
if (!host_exec_result) {
std::cerr << "execute host await: " << host_exec_result.error() << std::endl;
return 1;
}
std::cout << "HostAwait initial result: " << host_exec_result.output() << std::endl;
auto host_state_result = host_vm.get_execution_state();
if (!host_state_result) {
std::cerr << "get execution state: " << host_state_result.error() << std::endl;
return 1;
}
std::cout << "Execution state: " << host_state_result.output() << std::endl;
auto host_resume_result = host_vm.resume("{\"tier\":\"gold\"}", true);
if (!host_resume_result) {
std::cerr << "resume host await: " << host_resume_result.error() << std::endl;
return 1;
}
std::cout << "HostAwait resumed result: " << host_resume_result.output() << std::endl;
if (std::string(host_resume_result.output()) != "true") {
std::cerr << "unexpected host await result: " << host_resume_result.output() << std::endl;
return 1;
}
return 0;
}

421
bindings/csharp/API.md Normal file
View File

@@ -0,0 +1,421 @@
# Regorus C# API Documentation
This document describes the C# API for Regorus, focusing on the compiled policy approach for high-performance policy evaluation.
## Overview
The Regorus C# bindings provide a modern, thread-safe API for compiling and evaluating Open Policy Agent (OPA) Rego policies. The API is designed around pre-compiled policies that can be evaluated efficiently multiple times with different inputs.
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ CompiledPolicy Workflow │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Policy Modules │ │ Target/Schema │ │ Static Data │
│ (.rego files) │ │ Registries │ │ (JSON) │
└─────────┬───────┘ └────────┬─────────┘ └─────────┬───────┘
│ │ │
└─────────────────────┼────────────────────────┘
┌─────────────────────────┐
│ Compile │
│ ┌─────────────────────┐│
│ │ Parse & Analyze ││
│ │ Infer Resource Types││
│ │ Build AST & Rules ││
│ │ Target Integration ││
│ └─────────────────────┘│
└─────────────┬───────────┘
┌─────────────────────────┐
│ CompiledPolicy │
│ ┌─────────────────────┐ │
│ │ AST & Rules │ │
│ │ Target Info │ │
│ │ Resource Types │ │
│ │ Function Table │ │
│ │ Compiled Modules │ │
│ └─────────────────────┘ │
└─────────────┬───────────┘
┌─────────────────────┐
│ Service Cache │
│ (Policy Framework, │
│ MS Graph, etc.) │
│ ┌─────────────────┐ │
│ │ CompiledPolicy │ │ ◄─── Same LOCK-FREE policy
│ │ (cached) │ │ instance shared across
│ └─────────────────┘ │ all threads
└─────────┬───────────┘
┌───────┼───────┬───────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Thread 1 │ │ Thread 2 │ │ Thread N │
│ │ │ │ │ │
│ input1 ────▶│ │ input2 ────▶│ │ inputN ────▶│
│ ◄─── result │ │ ◄─── result │ │ ◄─── result │
└─────────────┘ └─────────────┘ └─────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Key Benefits │
├─────────────────────────────────────────────────────────────────┤
│ ✓ Compile Once, Evaluate Many ✓ Lock-Free Concurrent Eval │
│ ✓ No Re-parsing Overhead ✓ Reference Counting Safety │
│ ✓ Reduced GC Pressure ✓ Proper Resource Management │
│ ✓ Cache-Friendly Design ✓ Target System Integration │
└─────────────────────────────────────────────────────────────────┘
```
## Key Features
- **Pre-compiled Policies**: Compile once, evaluate many times for optimal performance
- **Target System Support**: Built-in support for Azure Policy targets with resource type inference
- **Thread Safety**: All operations are thread-safe without external synchronization
- **Registry Management**: Centralized management of targets and schemas
- **Policy Introspection**: Rich metadata about compiled policies
## Core Classes
### CompiledPolicy
The `CompiledPolicy` class represents a pre-compiled Rego policy that can be evaluated efficiently.
```csharp
public sealed class CompiledPolicy : IDisposable
{
// Evaluate the policy with input data
public string? EvalWithInput(string inputJson);
// Get comprehensive policy metadata
public PolicyInfo GetPolicyInfo();
// Dispose of unmanaged resources
public void Dispose();
}
```
**Thread Safety**: All methods are thread-safe. Multiple threads can call `EvalWithInput()` concurrently, and `Dispose()` will safely wait for active evaluations to complete.
### Compiler
The `Compiler` class provides static methods for compiling policies.
```csharp
public static class Compiler
{
// Compile a policy with a specific entrypoint rule
public static CompiledPolicy CompilePolicyWithEntrypoint(
string dataJson,
IEnumerable<PolicyModule> modules,
string entryPointRule);
// Compile a target-aware policy (requires azure_policy feature)
public static CompiledPolicy CompilePolicyForTarget(
string dataJson,
IEnumerable<PolicyModule> modules);
}
```
### PolicyModule
Represents a single policy module to be compiled. Each PolicyModule corresponds to a Rego file (.rego), and each Rego file defines a Rego package using the `package` declaration at the top of the file.
```csharp
public struct PolicyModule
{
public string Id { get; set; }
public string Content { get; set; }
public PolicyModule(string id, string content);
}
```
**Properties:**
- `Id`: A unique identifier for the module, typically the filename (e.g., "policy.rego", "rules/storage.rego")
- `Content`: The complete Rego policy content, including the `package` declaration and all rules
**Example:**
```csharp
var module = new PolicyModule("storage-policy.rego", @"
package azure.storage
import rego.v1
default allow := false
allow if input.type == ""Microsoft.Storage/storageAccounts""
");
```
### PolicyInfo
Provides comprehensive metadata about a compiled policy.
```csharp
public class PolicyInfo
{
// List of module identifiers
public List<string> ModuleIds { get; set; }
// Target name (for target-aware policies)
public string? TargetName { get; set; }
// Resource types this policy can evaluate
public List<string> ApplicableResourceTypes { get; set; }
// Primary rule/entrypoint
public string EntrypointRule { get; set; }
// Effect rule (for target-aware policies)
public string? EffectRule { get; set; }
// Policy parameters
public List<PolicyParameters> Parameters { get; set; }
}
```
## Registry Classes
### TargetRegistry
Manages target definitions for Azure Policy-style evaluations.
```csharp
public static class TargetRegistry
{
// Register a target from JSON
public static void RegisterFromJson(string targetJson);
// Check if a target exists
public static bool Contains(string name);
// List all registered targets
public static string ListNames();
// Remove a target
public static bool Remove(string name);
// Clear all targets
public static void Clear();
// Get count of registered targets
public static int Count { get; }
// Check if registry is empty
public static bool IsEmpty { get; }
}
```
### SchemaRegistry
Manages schema definitions for validation.
```csharp
public static class SchemaRegistry
{
// Register resource schemas
public static void RegisterResourceSchema(string name, string schemaJson);
public static bool ContainsResourceSchema(string name);
public static string ListResourceSchemas();
// Register effect schemas
public static void RegisterEffectSchema(string name, string schemaJson);
public static bool ContainsEffectSchema(string name);
public static string ListEffectSchemas();
// Clear methods
public static void ClearResourceSchemas();
public static void ClearEffectSchemas();
}
```
## Usage Examples
### Basic Policy Compilation and Evaluation
```csharp
// Define policy modules
var modules = new List<PolicyModule>
{
new PolicyModule("policy.rego", @"
package example
import rego.v1
default allow := false
allow if input.user == ""admin""
")
};
// Compile the policy
using var policy = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.example.allow");
// Evaluate with different inputs
var result1 = policy.EvalWithInput(@"{""user"": ""admin""}"); // true
var result2 = policy.EvalWithInput(@"{""user"": ""guest""}"); // false
```
### Target-Aware Policy (Azure Policy Style)
```csharp
// Register target definition
TargetRegistry.RegisterFromJson(@"{
""name"": ""azure.storage"",
""resource_schema_selector"": ""type"",
""resource_types"": {
""Microsoft.Storage/storageAccounts"": {
""schema"": { /* JSON Schema */ }
}
}
}");
// Define policy with target
var modules = new List<PolicyModule>
{
new PolicyModule("policy.rego", @"
package policy
import rego.v1
__target__ := ""azure.storage""
default effect := ""deny""
effect := ""allow"" if {
input.type == ""Microsoft.Storage/storageAccounts""
input.properties.supportsHttpsTrafficOnly == true
}
")
};
// Compile for target
using var policy = Compiler.CompilePolicyForTarget("{}", modules);
// Evaluate Azure resource
var resource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": {
""supportsHttpsTrafficOnly"": true
}
}";
var result = policy.EvalWithInput(resource); // "allow"
```
### Policy Introspection
```csharp
// Get policy metadata
var info = policy.GetPolicyInfo();
Console.WriteLine($"Target: {info.TargetName}");
Console.WriteLine($"Effect Rule: {info.EffectRule}");
Console.WriteLine($"Modules: {string.Join(", ", info.ModuleIds)}");
Console.WriteLine($"Resource Types: {string.Join(", ", info.ApplicableResourceTypes)}");
// Access parameters
if (info.Parameters != null && info.Parameters.Count > 0)
{
foreach (var parameterSet in info.Parameters)
{
Console.WriteLine($"Module: {parameterSet.SourceFile}");
foreach (var param in parameterSet.Parameters)
{
Console.WriteLine($"Parameter: {param.Name} ({param.Type})");
if (param.Default != null)
Console.WriteLine($" Default: {param.Default}");
}
}
}
```
### Concurrent Evaluation
```csharp
// CompiledPolicy is thread-safe
var tasks = Enumerable.Range(0, 100).Select(i =>
Task.Run(() => policy.EvalWithInput($@"{{""id"": {i}}}"))
).ToArray();
var results = await Task.WhenAll(tasks);
```
## Performance Considerations
### Compilation Overhead
- Policy compilation has significant overhead due to parsing and analysis
- **Best Practice**: Compile once, reuse many times
- Consider caching compiled policies for repeated use
### Memory Management
- `CompiledPolicy` manages unmanaged resources
- **Always** dispose of compiled policies using `using` statements or explicit `Dispose()`
- Disposal is thread-safe and waits for active evaluations
### Thread Safety
- All classes are thread-safe for concurrent reads/evaluations
- Registry modifications should be done during initialization
- No external synchronization required
## Error Handling
All methods throw `Exception` on errors with descriptive messages:
```csharp
try
{
var policy = Compiler.CompilePolicyWithEntrypoint(data, modules, rule);
var result = policy.EvalWithInput(input);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
```
## Feature Flags
Some functionality requires specific Rust feature flags:
- **azure_policy**: Required for target-aware compilation and policy parameters
- Without this feature, target-related methods will not be available
## Version Compatibility
- Requires .NET Standard 2.0 or later
- Compatible with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+
- Uses System.Text.Json for JSON serialization (added as dependency)
## Best Practices
1. **Compile Once, Evaluate Many**: Pre-compile policies for repeated evaluation
2. **Use Disposable Pattern**: Always dispose of CompiledPolicy instances
3. **Thread-Safe Design**: Take advantage of built-in thread safety
4. **Registry Setup**: Configure targets and schemas during application startup
5. **Error Handling**: Wrap operations in try-catch blocks for robust error handling
6. **Performance Monitoring**: Monitor evaluation times for performance optimization
## Migration from Engine-Based API
If migrating from an engine-based approach:
```csharp
// Old approach (if it existed)
// var engine = new Engine();
// engine.AddPolicy("policy.rego", policyContent);
// engine.SetInputJson(inputJson);
// var result = engine.EvalRule("data.policy.allow");
// New compiled approach
var modules = new[] { new PolicyModule("policy.rego", policyContent) };
using var policy = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.policy.allow");
var result = policy.EvalWithInput(inputJson);
```
The compiled approach provides better performance for repeated evaluations and clearer resource management.

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>Enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<!-- If the environment variable is set (such as in a Github Action run), append the suffix to the version number -->
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
<PropertyGroup>
<!-- If the environment variable is set (such as in a Github Action run), append the suffix to the version number -->
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Regorus" />
</ItemGroup>
<ItemGroup>
<None Include="../../ffi/target/release/libregorus_ffi.dylib" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,312 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Regorus;
namespace Benchmarks
{
public class CompiledPolicyEvaluationBenchmark
{
private static readonly string TestDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"..", "..", "..",
"benches", "evaluation", "test_data"
);
private static readonly (string PolicyFile, string[] InputFiles)[] PolicyInputFiles = new[]
{
("rbac_policy.rego", new[] { "rbac_input.json", "rbac_input2.json", "rbac_input3.json" }),
("api_access_policy.rego", new[] { "api_access_input.json", "api_access_input2.json", "api_access_input3.json" }),
("data_sensitivity_policy.rego", new[] { "data_sensitivity_input.json", "data_sensitivity_input2.json", "data_sensitivity_input3.json" }),
("time_based_policy.rego", new[] { "time_based_input.json", "time_based_input2.json", "time_based_input3.json" }),
("data_processing_policy.rego", new[] { "data_processing_input.json", "data_processing_input2.json", "data_processing_input3.json" }),
("azure_vm_policy.rego", new[] { "azure_vm_input.json", "azure_vm_input2.json", "azure_vm_input3.json" }),
("azure_storage_policy.rego", new[] { "azure_storage_input.json", "azure_storage_input2.json", "azure_storage_input3.json" }),
("azure_keyvault_policy.rego", new[] { "azure_keyvault_input.json", "azure_keyvault_input2.json", "azure_keyvault_input3.json" }),
("azure_nsg_policy.rego", new[] { "azure_nsg_input.json", "azure_nsg_input2.json", "azure_nsg_input3.json" })
};
private static readonly string[] PolicyNames = new[]
{
"rbac_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
"azure_vm_policy",
"azure_storage_policy",
"azure_keyvault_policy",
"azure_nsg_policy"
};
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;
}
private static List<CompiledPolicy> PrepareSharedCompiledPolicies()
{
var policiesWithInputs = LoadPoliciesWithInputs();
var compiledPolicies = new List<CompiledPolicy>();
foreach (var (policy, _) in policiesWithInputs)
{
var modules = new[] { new PolicyModule("policy.rego", policy) };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
compiledPolicies.Add(compiled);
}
return compiledPolicies;
}
public static void RunCompiledPolicyEvaluationBenchmark()
{
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();
// Benchmark both shared policies and per-iteration compilation
var configurations = new[]
{
(true, "compiled_shared_policies"),
(false, "compiled_per_iteration")
};
foreach (var (useSharedPolicies, groupName) in configurations)
{
Console.WriteLine($"=== {groupName} ===");
foreach (var threads in threadCounts)
{
RunCompiledPolicyBenchmark(threads, useSharedPolicies, groupName);
}
Console.WriteLine();
}
}
public static void RunCompiledPolicyBenchmark(int threads, bool useSharedPolicies, string groupName)
{
const int warmupSeconds = 3;
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);
// Calculate throughput based on pure evaluation time (consistent with Rust benchmark)
var evalsPerSecond = totalEvaluations / evaluationTime.TotalSeconds;
var kelemsPerSecond = evalsPerSecond / 1000.0;
Console.WriteLine($"{groupName}/eval/{threads} threads");
Console.WriteLine($" time: [{evaluationTime.TotalMilliseconds:F2} ms]");
Console.WriteLine($" thrpt: [{kelemsPerSecond:F2} Kelem/s]");
if (totalEvaluations > 0)
{
var bytesPerEval = allocatedBytes / (double)totalEvaluations;
Console.WriteLine($" alloc: [{bytesPerEval:F2} B/op] (total {allocatedBytes} B)");
}
// Clean up compiled policies if we created them
if (compiledPolicies != null)
{
foreach (var policy in compiledPolicies)
{
DisposeCompiledPolicy(policy);
}
}
// Verify that all policies were evaluated
var allEvaluated = policyCounters.Values.All(count => count > 0);
if (allEvaluated)
{
Console.WriteLine("✓ All policies were evaluated successfully");
}
else
{
Console.WriteLine("ERROR: Some policies were never evaluated successfully!");
}
}
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters, long allocatedBytes) RunBenchmarkPhase(
int threads,
int durationSeconds,
List<(string Policy, string[] Inputs)> policiesWithInputs,
List<CompiledPolicy>? compiledPolicies,
bool useSharedPolicies,
bool isWarmup)
{
var barrier = new Barrier(threads);
var tasks = new Task[threads];
var policyCounters = new Dictionary<string, int>();
var evaluationTimes = new Dictionary<int, TimeSpan>();
var lockObject = new object();
var stopExecution = false;
long allocatedBytes = 0;
// Initialize counters
foreach (var policyName in PolicyNames)
{
policyCounters[policyName] = 0;
}
var stopwatch = Stopwatch.StartNew();
for (int threadId = 0; threadId < threads; threadId++)
{
int tid = threadId;
tasks[threadId] = Task.Run(() =>
{
long allocationStart = 0;
if (!isWarmup)
{
allocationStart = GC.GetAllocatedBytesForCurrentThread();
}
barrier.SignalAndWait();
int evaluationCount = 0;
var localEvaluationTime = TimeSpan.Zero;
while (!stopExecution)
{
// Use different policy for each iteration
int policyIdx = (tid + evaluationCount) % policiesWithInputs.Count;
var (policy, inputs) = policiesWithInputs[policyIdx];
// Use different input for the same policy based on iteration
int inputIdx = evaluationCount % inputs.Length;
var input = inputs[inputIdx];
try
{
// 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("policy.rego", policy) };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
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)
{
lock (lockObject)
{
policyCounters[PolicyNames[policyIdx]]++;
}
}
}
catch (Exception)
{
// Ignore evaluation errors for benchmarking purposes
}
evaluationCount++;
}
// Store the actual evaluation time for this thread
if (!isWarmup)
{
lock (lockObject)
{
if (!evaluationTimes.ContainsKey(tid))
evaluationTimes[tid] = TimeSpan.Zero;
evaluationTimes[tid] = localEvaluationTime;
}
var allocationEnd = GC.GetAllocatedBytesForCurrentThread();
System.Threading.Interlocked.Add(ref allocatedBytes, allocationEnd - allocationStart);
}
});
}
// Stop execution after the specified duration
Task.Delay(TimeSpan.FromSeconds(durationSeconds)).ContinueWith(_ => stopExecution = true);
Task.WaitAll(tasks);
stopwatch.Stop();
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

@@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Regorus;
namespace Benchmarks
{
public class EngineEvaluationBenchmark
{
private static readonly string TestDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"..", "..", "..",
"benches", "evaluation", "test_data"
);
private static readonly (string PolicyFile, string[] InputFiles)[] PolicyInputFiles = new[]
{
("rbac_policy.rego", new[] { "rbac_input.json", "rbac_input2.json", "rbac_input3.json" }),
("api_access_policy.rego", new[] { "api_access_input.json", "api_access_input2.json", "api_access_input3.json" }),
("data_sensitivity_policy.rego", new[] { "data_sensitivity_input.json", "data_sensitivity_input2.json", "data_sensitivity_input3.json" }),
("time_based_policy.rego", new[] { "time_based_input.json", "time_based_input2.json", "time_based_input3.json" }),
("data_processing_policy.rego", new[] { "data_processing_input.json", "data_processing_input2.json", "data_processing_input3.json" }),
("azure_vm_policy.rego", new[] { "azure_vm_input.json", "azure_vm_input2.json", "azure_vm_input3.json" }),
("azure_storage_policy.rego", new[] { "azure_storage_input.json", "azure_storage_input2.json", "azure_storage_input3.json" }),
("azure_keyvault_policy.rego", new[] { "azure_keyvault_input.json", "azure_keyvault_input2.json", "azure_keyvault_input3.json" }),
("azure_nsg_policy.rego", new[] { "azure_nsg_input.json", "azure_nsg_input2.json", "azure_nsg_input3.json" })
};
private static readonly string[] PolicyNames = new[]
{
"rbac_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
"azure_vm_policy",
"azure_storage_policy",
"azure_keyvault_policy",
"azure_nsg_policy"
};
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;
}
private static List<Engine> PrepareClonedEngines()
{
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("{}");
try
{
engine.EvalRule("data.bench.allow");
}
catch
{
// Ignore warmup errors
}
engines.Add(engine);
}
return engines;
}
public static void RunEngineEvaluationBenchmark()
{
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();
// Benchmark both cloned engines and fresh engines
var configurations = new[]
{
(true, "cloned_engines"),
(false, "fresh_engines")
};
foreach (var (useClonedEngines, groupName) in configurations)
{
Console.WriteLine($"=== {groupName} ===");
foreach (var threads in threadCounts)
{
RunEngineEvaluationBenchmark(threads, useClonedEngines, groupName);
}
Console.WriteLine();
}
}
public static void RunEngineEvaluationBenchmark(int threads, bool useClonedEngines, string groupName)
{
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);
// Calculate throughput based on pure evaluation time (consistent with Rust benchmark)
var evalsPerSecond = totalEvaluations / evaluationTime.TotalSeconds;
var kelemsPerSecond = evalsPerSecond / 1000.0;
Console.WriteLine($"{groupName}/eval/{threads} threads");
Console.WriteLine($" time: [{evaluationTime.TotalMilliseconds:F2} ms]");
Console.WriteLine($" thrpt: [{kelemsPerSecond:F2} Kelem/s]");
// Verify that all policies were evaluated
var allEvaluated = policyCounters.Values.All(count => count > 0);
if (allEvaluated)
{
Console.WriteLine("✓ All policies were evaluated successfully");
}
else
{
Console.WriteLine("ERROR: Some policies were never evaluated successfully!");
}
}
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters) RunBenchmarkPhase(
int threads,
int durationSeconds,
List<(string Policy, string[] Inputs)> policiesWithInputs,
bool useClonedEngines,
bool isWarmup)
{
var barrier = new Barrier(threads);
var tasks = new Task[threads];
var policyCounters = new Dictionary<string, int>();
var evaluationTimes = new Dictionary<int, TimeSpan>();
var lockObject = new object();
var stopExecution = false;
// Initialize counters
foreach (var policyName in PolicyNames)
{
policyCounters[policyName] = 0;
}
// Pre-create engines if using cloned engines
List<Engine>? clonedEngines = null;
if (useClonedEngines)
{
clonedEngines = PrepareClonedEngines();
}
var stopwatch = Stopwatch.StartNew();
for (int threadId = 0; threadId < threads; threadId++)
{
int tid = threadId;
tasks[threadId] = Task.Run(() =>
{
barrier.SignalAndWait();
int evaluationCount = 0;
var localEvaluationTime = TimeSpan.Zero;
while (!stopExecution)
{
// Use different policy for each iteration
int policyIdx = (tid + evaluationCount) % policiesWithInputs.Count;
var (policy, inputs) = policiesWithInputs[policyIdx];
// Use different input for the same policy based on iteration
int inputIdx = evaluationCount % inputs.Length;
var input = inputs[inputIdx];
try
{
// Measure only the engine operations
var evalStopwatch = Stopwatch.StartNew();
Engine engine;
if (useClonedEngines)
{
engine = clonedEngines![policyIdx].Clone();
}
else
{
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)
{
lock (lockObject)
{
policyCounters[PolicyNames[policyIdx]]++;
}
}
}
catch (Exception)
{
// Ignore evaluation errors for benchmarking purposes
}
evaluationCount++;
}
// Store the actual evaluation time for this thread
if (!isWarmup)
{
lock (lockObject)
{
if (!evaluationTimes.ContainsKey(tid))
evaluationTimes[tid] = TimeSpan.Zero;
evaluationTimes[tid] = localEvaluationTime;
}
}
});
}
// Stop execution after the specified duration
Task.Delay(TimeSpan.FromSeconds(durationSeconds)).ContinueWith(_ => stopExecution = true);
Task.WaitAll(tasks);
stopwatch.Stop();
// Clean up cloned engines if we created them
if (clonedEngines != null)
{
foreach (var engine in clonedEngines)
{
engine.Dispose();
}
}
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

@@ -0,0 +1,36 @@
using System;
namespace Benchmarks
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== Regorus C# Benchmarks ===\n");
try
{
Console.WriteLine("Running Engine Evaluation Benchmark...");
EngineEvaluationBenchmark.RunEngineEvaluationBenchmark();
}
catch (Exception ex)
{
Console.WriteLine($"Engine benchmark failed: {ex.Message}");
}
Console.WriteLine("\n" + new string('=', 80) + "\n");
try
{
Console.WriteLine("Running Compiled Policy Evaluation Benchmark...");
CompiledPolicyEvaluationBenchmark.RunCompiledPolicyEvaluationBenchmark();
}
catch (Exception ex)
{
Console.WriteLine($"Compiled policy benchmark failed: {ex.Message}");
}
Console.WriteLine("\n=== Benchmarks Complete ===");
}
}
}

View File

@@ -0,0 +1,140 @@
# Compiled Policy Evaluation Benchmark Results (C#/.NET)
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **.NET Version**: 8.0
- **Allocator**: mimalloc (default allocator for Rust FFI)
- **Benchmark Framework**: Custom time-based benchmarking
- **Test Data**: 20,000 inputs per evaluation (distributed across threads)
- **Policy**: Complex authorization policy with nested rules
- **Warmup Duration**: 3 seconds per configuration
- **Evaluation Duration**: 3 seconds per configuration
## Benchmark Overview
The C# compiled policy evaluation benchmark tests Regorus compiled policy performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of compiled policy compilation strategies.
## Configuration Combinations
1. **Compiled Shared Policies**: All threads share pre-compiled policy instances - optimal for performance
2. **Compiled Per Iteration**: Each thread compiles the policy for each evaluation iteration
*Note: The C# implementation uses a simpler configuration model compared to Rust, which also varies input data handling (cloned vs fresh inputs). The C# benchmarks focus on compilation strategies with consistent input handling.*
## Performance Results
### Compiled Shared Policies (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2905.41 | 273 |
| 2 | 5808.07 | 240 |
| 4 | 11631.23 | 227 |
| 6 | 17431.95 | 216 |
| 8 | 23183.42 | 126 |
| 10 | 28886.11 | 118 |
| 12 | 34659.87 | 108 |
| 14 | 40564.07 | 84 |
| 16 | 46446.38 | 72 |
| 18 | 52047.06 | 63 |
| 20 | 56983.45 | 58 |
| 22 | 404931.47 | 55 |
| 24 | 61673.71 | 55 |
| 26 | 64370.41 | 51 |
| 28 | 56897.04 | 59 |
| 30 | 406850.06 | 52 |
| 32 | 56786.24 | 58 |
### Compiled Per Iteration
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2978.06 | 49 |
| 2 | 5965.09 | 47 |
| 4 | 11928.23 | 46 |
| 6 | 17892.58 | 45 |
| 8 | 23773.82 | 43 |
| 10 | 29705.61 | 42 |
| 12 | 35631.97 | 40 |
| 14 | 41563.35 | 34 |
| 16 | 47452.93 | 31 |
| 18 | 53505.42 | 27 |
| 20 | 59393.86 | 25 |
| 22 | 436115.28 | 23 |
| 24 | 71088.08 | 21 |
| 26 | 76928.70 | 19 |
| 28 | 82759.27 | 18 |
| 30 | 560658.97 | 17 |
| 32 | 93949.39 | 16 |
## Analysis
The C# compiled policy benchmark demonstrates important performance characteristics with mimalloc as the default allocator:
1. **Compilation Strategy Impact**: Shared compiled policies significantly outperform per-iteration compilation (~5.6x at 1 thread)
2. **Scaling Patterns with mimalloc**:
- Best throughput achieved at 1 thread for shared policies
- Performance generally degrades with increased thread count, but mimalloc provides better allocation efficiency
3. **Performance Hierarchy**:
- Shared compiled policies: Best performance (optimal configuration)
- Per-iteration compilation: ~82% reduction from optimal
4. **Compilation Overhead**: Per-iteration compilation creates substantial overhead, similar to fresh engine creation
5. **Thread Contention**: Significant performance degradation beyond 8 threads for both configurations, though mimalloc helps mitigate some allocation-related issues
## Comparison with Rust Compiled Policy Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:-----------------|:-------------------|:--------------------|:--------------------|
| | C# / Rust | C# / Rust | C# / Rust |
| Shared Policies | 273 / 426 | 227 / 342 | 126 / 185 |
| Per-iteration | 49 / 55 | 46 / 50 | 43 / 50 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:-----------------|:----------------------|:--------------------------|:-----------------------|
| | Avg C# / Rust | Avg C# / Rust | Avg C# / Rust |
| Shared Policies | 249 / 384 | 150 / 203 | 58 / 123 |
| Per-iteration | 47 / 54 | 40 / 50 | 22 / 42 |
**Key Observations:**
- **Single-threaded performance**: C# achieves 64% of Rust performance for shared policies, 89% for per-iteration
- **Threading scaling**: Both platforms show similar degradation patterns, but Rust maintains better absolute performance
- **Contention resistance**: Per-iteration compilation shows more consistent relative performance across thread counts
- **Platform differences**: C# shows more pronounced performance drops at higher thread counts, particularly for shared policies
*Note: Rust benchmarks include additional input data variations (cloned vs fresh inputs) that are not present in the C# implementation.*
## Comparison with C# Engine Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:----------------|:-------------------|:--------------------|:--------------------|
| | CP / EE | CP / EE | CP / EE |
| Shared Policies | 273 / 279 | 227 / 217 | 126 / 114 |
| Per-iteration | 49 / 50 | 46 / 47 | 43 / 45 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:----------------|:----------------------|:--------------------------|:-----------------------|
| | Avg CP / EE | Avg CP / EE | Avg CP / EE |
| Shared Policies | 249 / 248 | 150 / 128 | 58 / 54 |
| Per-iteration | 47 / 48 | 40 / 39 | 22 / 27 |
**Key Observations:**
- **Single-threaded parity**: Both systems perform nearly identically at 1 thread
- **Threading behavior**: Compiled policies slightly outperform engine evaluation at higher thread counts for shared policies
- **Contention resistance**: Per-iteration configurations show very similar performance characteristics across all thread counts
- **Platform consistency**: Both C# implementations show similar scaling patterns and contention behavior
## Performance Insights
1. **C# vs Rust Performance**: C# compiled policies achieve 65% average performance of Rust for shared policies, 87% average for per-iteration across low contention scenarios
2. **Engine vs Compiled**: In C#, engine and compiled policy evaluation show very similar average performance (compiled policies achieve 100% of engine performance for shared policies, 98% for per-iteration)
3. **mimalloc Impact**: The use of mimalloc as the default allocator in the underlying Rust FFI provides better memory allocation efficiency and improved threading characteristics
4. **Threading Scaling**: Both C# configurations demonstrate similar contention patterns, with shared policies showing more pronounced degradation under high thread contention compared to per-iteration compilation

View File

@@ -0,0 +1,118 @@
# Engine Evaluation Benchmark Results (C#/.NET)
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **.NET Version**: 8.0
- **Allocator**: mimalloc (default allocator for Rust FFI)
- **Benchmark Framework**: Custom time-based benchmarking
- **Test Data**: 20,000 inputs per evaluation (distributed across threads)
- **Policy**: Complex authorization policy with nested rules
- **Warmup Duration**: 3 seconds per configuration
- **Evaluation Duration**: 3 seconds per configuration
## Benchmark Overview
The C# engine evaluation benchmark tests Regorus policy evaluation performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of engine reuse strategies.
## Configuration Combinations
1. **Cloned Engines**: Each thread uses its own cloned engine instance - optimal for performance
2. **Fresh Engines**: Each thread creates a new engine for each evaluation iteration
*Note: The C# implementation uses a simpler configuration model compared to Rust, which also varies input data handling (cloned vs fresh inputs). The C# benchmarks focus on engine reuse strategies with consistent input handling.*
## Performance Results
### Cloned Engines (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2903.43 | 279 |
| 2 | 5808.35 | 227 |
| 4 | 11645.08 | 217 |
| 6 | 17469.69 | 207 |
| 8 | 23268.07 | 114 |
| 10 | 28996.14 | 104 |
| 12 | 34808.60 | 98 |
| 14 | 40703.21 | 72 |
| 16 | 46488.23 | 63 |
| 18 | 52078.52 | 56 |
| 20 | 57014.31 | 51 |
| 22 | 60482.22 | 47 |
| 24 | 62445.67 | 46 |
| 26 | 65128.74 | 45 |
| 28 | 58001.92 | 50 |
| 30 | 66154.78 | 42 |
| 32 | 64999.03 | 45 |
### Fresh Engines
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2982.28 | 50 |
| 2 | 5962.62 | 48 |
| 4 | 11917.94 | 47 |
| 6 | 17874.77 | 46 |
| 8 | 23729.94 | 45 |
| 10 | 29635.17 | 42 |
| 12 | 35574.71 | 38 |
| 14 | 41482.61 | 34 |
| 16 | 47425.16 | 32 |
| 18 | 53248.87 | 29 |
| 20 | 58424.34 | 27 |
| 22 | 61302.24 | 26 |
| 24 | 67430.08 | 23 |
| 26 | 65226.79 | 24 |
| 28 | 73118.48 | 22 |
| 30 | 326472.94 | 23 |
| 32 | 63805.03 | 24 |
## Analysis
The C# benchmark results demonstrate important performance characteristics with mimalloc as the default allocator:
1. **Engine Reuse Impact**: Cloned engines significantly outperform fresh engines (~5.6x at 1 thread)
2. **Scaling Patterns with mimalloc**:
- Best throughput achieved at 1 thread for both configurations
- Performance degrades with increased thread count due to contention, but mimalloc provides better allocation efficiency
- Cloned engines show better relative scaling characteristics
3. **Performance Hierarchy**:
- Cloned engines: Best performance (optimal configuration)
- Fresh engines: ~82% reduction from optimal
4. **Thread Contention**: Significant performance drop beyond 8 threads, especially for fresh engines, though mimalloc helps mitigate some allocation-related issues
5. **C# vs Rust Performance**: C# shows ~66% of Rust performance for equivalent cloned engine configuration
## Comparison with Rust Engine Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:---------------|:-------------------|:--------------------|:--------------------|
| | C# / Rust | C# / Rust | C# / Rust |
| Cloned Engines | 279 / 423 | 217 / 406 | 114 / 341 |
| Fresh Engines | 50 / 56 | 47 / 54 | 45 / 53 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:---------------|:----------------------|:--------------------------|:-----------------------|
| | Avg C# / Rust | Avg C# / Rust | Avg C# / Rust |
| Cloned Engines | 253 / 414 | 128 / 329 | 54 / 250 |
| Fresh Engines | 48 / 55 | 39 / 52 | 27 / 42 |
**Key Observations:**
- **Single-threaded performance**: C# achieves 66% of Rust performance for cloned engines, 89% for fresh engines
- **Threading scaling**: Both platforms show similar degradation patterns, but Rust maintains better absolute performance
- **Contention resistance**: Fresh engines show more consistent relative performance across thread counts
- **Platform differences**: C# shows more pronounced performance drops at higher thread counts, particularly for cloned engines
*Note: Rust benchmarks include additional input data variations (cloned vs fresh inputs) that are not present in the C# implementation.*
## Performance Insights
1. **Engine Creation Overhead**: Fresh engine creation has significant performance impact in C# (~5.6x slower than cloned engines)
2. **Thread Scaling**: C# shows moderate thread contention with better characteristics when using mimalloc
3. **Memory Management**: .NET garbage collection patterns combined with mimalloc allocation efficiency
4. **Interop Performance**: C# bindings achieve 66% of Rust performance for cloned engines, demonstrating effective FFI implementation
5. **mimalloc Benefits**: The use of mimalloc as the default allocator in the underlying Rust FFI provides improved memory allocation efficiency and better threading characteristics

View File

@@ -0,0 +1,15 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<RegorusPackageVersion>0.9.1</RegorusPackageVersion>
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
<ItemGroup>
<!-- Centralize Regorus package version with optional CI suffix -->
<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>

152
bindings/csharp/README.md Normal file
View File

@@ -0,0 +1,152 @@
# Regorus CSharp
**Regorus** is
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
interpreter written in Rust.
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
See main [Regorus page](https://github.com/microsoft/regorus) for more details about the project.
# Building
## Github Actions
The simplest way to build a Nuget for Regorus' C# bindings is to use Github Actions. The action to do so is named `bindings/csharp` and is defined in `.github/workflows/test-csharp.yml`.
There are two ways to trigger a Nuget build.
1. Runs are triggered automatically whenever a push or pull request is made to the `main` branch.
2. A run can be triggered manually by navigating to the action in the Github UI and clicking `Run workflow`. This option allows you to generate a Nuget for any branch, which is useful when testing the integration of in-progress changes to Regorus with other projects. Nuget files that are generated via this flow will have a `manualtrigger` suffix appended to their version number, making it easy to distinguish them from Nugets generated using the `main` branch.
![Image displaying the run workflow button](docs/images/readme/manuallytriggeringrun.png)
Once the workflow run completes, the generated Nuget can be downloaded by following these steps:
1. Open the run.
2. Click on `Build Regorus nuget` on the left.
3. Expand the `Upload Regorus nuget` step.
4. Click the `Artifact download URL` link at the bottom.
5. Save and extract the downloaded zip file to find the `.nupkg` file.
![Image displaying the download URL link](docs/images/readme/downloadnuget.png)
## Local
The `cargo xtask` runner provides helpers for local builds:
1. `cargo xtask ffi` builds the `bindings/ffi` crate for the host platform in debug mode. Add `--target <triple>` (repeatable) to cross-compile, or `--release` to produce optimised artefacts. Results land under `bindings/ffi/target/<triple>/<profile>`.
2. `cargo xtask nuget` reuses those artefacts to pack the C# library. It defaults to debug builds for the host but accepts `--target`, `--release`, `--artifacts-dir <path>` to reuse existing binaries, and `--enforce-artifacts` to require every officially supported platform.
3. `cargo xtask test-csharp` ensures a NuGet is available (rebuilding when required or when `--force-nuget` is passed) and then runs `Regorus.Tests`, `TestApp`, and `TargetExampleApp` against it. The command accepts the same build flags as `cargo xtask nuget`.
## Memory Usage Safeguards
The C# bindings expose allocator-backed memory tracking utilities via the static `Regorus.MemoryLimits` helper. Typical usage:
```csharp
// Restrict total allocations to 128 MiB for the process
Regorus.MemoryLimits.SetGlobalMemoryLimit(128 * 1024 * 1024);
// Optional: tune how frequently each thread flushes its allocation counters
Regorus.MemoryLimits.SetThreadFlushThresholdOverride(256 * 1024);
// Engine operations throw InvalidOperationException with the allocator message if the budget is exceeded
using var engine = new Regorus.Engine();
var veryLargeJson = new string('x', 128 * 1024);
try
{
engine.SetInputJson(veryLargeJson);
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Allocator reported: {ex.Message}");
}
// Restore defaults once done
Regorus.MemoryLimits.SetGlobalMemoryLimit(null);
Regorus.MemoryLimits.SetThreadFlushThresholdOverride(null);
```
See bindings/csharp/Regorus.Tests/RegorusTests.cs for scenario coverage and bindings/csharp/TargetExampleApp/Program.cs for end-to-end usage.
## RVM Usage Example
The RVM API lets you compile a program from modules/entrypoints and execute it in a VM:
```csharp
using Regorus;
const string Policy = """
package demo
default allow = false
allow if {
input.user == "alice"
some role in data.roles[input.user]
role == "admin"
}
""";
const string Data = """
{ "roles": { "alice": ["admin"] } }
""";
const string Input = """
{ "user": "alice" }
""";
var modules = new[] { new PolicyModule("demo.rego", Policy) };
var entryPoints = new[] { "data.demo.allow" };
using var program = Program.CompileFromModules(Data, modules, entryPoints);
var listing = program.GenerateListing();
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetDataJson(Data);
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}");
```

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