Compare commits

...

122 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
209a534b7c Initial plan 2025-11-25 00:43:19 +00: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
491 changed files with 91002 additions and 4482 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[alias]
xtask = "run --package xtask --"

View File

@@ -4,11 +4,20 @@
"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": {},
"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": [
// {

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.89.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

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

@@ -0,0 +1,212 @@
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@v4
# Setup language-specific dependencies BEFORE CodeQL init for proper tracing setup
- name: Setup Rust
if: matrix.language == 'rust' || matrix.language == 'c-cpp'
uses: ./.github/actions/toolchains/rust
- name: Setup Python
if: matrix.language == 'python'
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Setup Java
if: matrix.language == 'java-kotlin'
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '8'
- name: Setup Go
if: matrix.language == 'go'
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Setup .NET
if: matrix.language == 'csharp'
uses: actions/setup-dotnet@v4
with:
global-json-file: ./bindings/csharp/global.json
- name: Setup Node.js
if: matrix.language == 'javascript-typescript'
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
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@v1
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++ FFI bindings
if: matrix.language == 'c-cpp'
working-directory: ${{ matrix.working-directory }}
run: |
# Build FFI library in no_std mode for embedded/constrained environments
cargo build --release --locked --features "ast,coverage,regorus/opa-no-std" --no-default-features
# Build the Rust FFI library that provides C-compatible interface
cargo build --release --locked
# Build C bindings using CMake
cd ../c
mkdir -p build
cd build
cmake ..
make
# Build C++ bindings using CMake
cd ../../cpp
mkdir -p build
cd build
cmake ..
make
- name: Build Java bindings
if: matrix.language == 'java-kotlin'
working-directory: ${{ matrix.working-directory }}
run: |
# Build the Rust JNI library that provides Java-compatible interface
cargo fetch
cargo build --release --locked
# Compile Java source and create JAR package with Maven
mvn package
- name: Build Go bindings
if: matrix.language == 'go'
working-directory: ${{ matrix.working-directory }}
run: |
# Build the FFI library that Go bindings depend on via CGO
cd ../ffi
cargo fetch
cargo build --release --locked
cd ../go
# Download Go dependencies
go mod tidy
# Set up environment for CGO linking to Rust FFI library
export CGO_ENABLED=1
export LD_LIBRARY_PATH="$(pwd)/../ffi/target/release:$LD_LIBRARY_PATH"
# Build Go packages with verbose output for CodeQL tracing
go build -v ./pkg/regorus
go build -v -o regorus_test .
- name: Build C# bindings
if: matrix.language == 'csharp'
working-directory: ${{ matrix.working-directory }}
run: |
# Build the FFI library that C# bindings access via P/Invoke
cd ../ffi
cargo fetch
cargo build --release --locked
cd ../csharp
# Restore NuGet packages and build .NET assemblies in release mode
# Build the main Regorus library project only (tests require packaged version)
dotnet restore Regorus/Regorus.csproj
dotnet build Regorus/Regorus.csproj --no-restore /p:Configuration=Release /p:IgnoreMissingArtifacts=true
- name: Build WASM bindings
if: matrix.language == 'javascript-typescript'
working-directory: ${{ matrix.working-directory }}
run: |
# Build WebAssembly module with wasm-pack for Node.js target
cargo fetch
wasm-pack build --target nodejs --release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

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

@@ -0,0 +1,38 @@
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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Build only std
run: cargo build -r --example regorus --no-default-features --features "std,rego-extensions"
- name: Doc Tests
run: cargo test -r --doc --features rego-extensions
- name: Run tests
run: cargo test -r --features rego-extensions
- name: Run example
run: cargo run --example regorus --features rego-extensions -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
- name: Run tests (ACI)
run: cargo test -r --test aci --features rego-extensions
- name: Run tests (KATA)
run: cargo test -r --test kata --features rego-extensions
- name: Run tests (OPA Conformance)
run: >-
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision,rego-extensions -- $(tr '\n' ' ' < tests/opa.passing)

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,25 +18,36 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Format Check
run: cargo fmt --check
- name: Fetch
run: cargo fetch
- name: Build (all features)
run: cargo build -r --all-features
run: cargo build -r --all-features --frozen
- name: Build
run: cargo build -r
run: cargo build -r --frozen
- name: Test no_std
run: cargo test -r --no-default-features
run: cargo test -r --no-default-features --frozen
- name: Build only std
run: cargo build -r --example regorus --no-default-features --features "std"
run: cargo build -r --example regorus --no-default-features --features "std" --frozen
- name: Doc Tests
run: cargo test -r --doc
run: cargo test -r --doc --frozen
- name: Run tests
run: cargo test -r
run: cargo test -r --frozen
- name: Run example
run: cargo run --example regorus --frozen -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
- name: Run tests (ACI)
run: cargo test -r --test aci
run: cargo test -r --test aci --frozen
- name: Run tests (KATA)
run: cargo test -r --test kata
run: cargo test -r --test kata --frozen
- name: Run tests (OPA Conformance)
run: >-
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
cargo test -r --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
- name: Run tests (Azure Policy)
run: >-
cargo test --frozen --features azure_policy
- name: Run tests (Azure RBAC)
run: cargo test -r --frozen --features azure-rbac

View File

@@ -32,26 +32,27 @@ jobs:
os: windows-latest
extension: dll
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: 8
distribution: "corretto"
- uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/toolchains/rust
with:
targets: ${{ matrix.target }}
- if: ${{ matrix.build_cmd == 'zigbuild' }}
uses: actions/setup-python@v5
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"
- if: ${{ matrix.build_cmd == 'zigbuild' }}
run: pip install cargo-zigbuild
- run: cargo ${{ matrix.build_cmd || 'build' }} --release --target ${{ matrix.target }}${{ matrix.glibc && format('.{0}', matrix.glibc) || '' }} --manifest-path ./bindings/java/Cargo.toml
- run: cargo fetch
- 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@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: native-libraries-${{ matrix.target }}
path: native/
@@ -61,24 +62,24 @@ jobs:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: 8
distribution: "corretto"
server-id: ossrh
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
pattern: native-libraries-*
merge-multiple: true
path: ./bindings/java/native/
- run: mvn package
working-directory: ./bindings/java
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: built-jars
path: ./bindings/java/target/regorus-java-*.jar

View File

@@ -18,21 +18,30 @@ jobs:
matrix:
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.10'
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
cargo fetch
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@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels
name: wheels-linux-${{ matrix.target }}
path: dist
windows:
@@ -41,21 +50,30 @@ jobs:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.10'
architecture: ${{ matrix.target }}
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
cargo fetch
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@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
args: --release --out dist --manifest-path bindings/python/Cargo.toml --frozen --strip
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels
name: wheels-windows-${{ matrix.target }}
path: dist
macos:
@@ -64,35 +82,29 @@ jobs:
matrix:
target: [x86_64, aarch64, universal2-apple-darwin]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.10'
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
cargo fetch
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@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: wheels
path: dist
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build sdist
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
command: sdist
args: --out dist --manifest-path bindings/python/Cargo.toml
- name: Upload sdist
uses: actions/upload-artifact@v3
with:
name: wheels
name: wheels-macos-${{ matrix.host.target }}
path: dist
release:
@@ -100,11 +112,13 @@ jobs:
runs-on: ubuntu-latest
# Commented out for initial release.
# if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos, sdist]
needs: [linux, windows, macos]
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: wheels
pattern: wheels-*
merge-multiple: true
path: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
env:

View File

@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'

View File

@@ -10,15 +10,17 @@ jobs:
release-plz:
name: Release-plz
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
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@98b2b45b090aadf18cb662caaf3de6222d98822a #v0.5.60
uses: MarcoIeni/release-plz-action@8724d33cd97b8295051102e2e19ca592962238f5 #v0.5.108
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View File

@@ -16,6 +16,9 @@ on:
# 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:
@@ -27,28 +30,28 @@ jobs:
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1
with:
profile: minimal
toolchain: stable
components: clippy
override: true
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Install required cargo
run: cargo install clippy-sarif sarif-fmt
- name: Fetch
run: cargo fetch
- name: Run rust-clippy
run:
cargo clippy
--all-features
--frozen
--message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
continue-on-error: true
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@c298edae2d512d807fe4bdc57c0ac5a036f61501 # v3.29.11
with:
sarif_file: rust-clippy-results.sarif
wait-for-processing: true

View File

@@ -5,22 +5,28 @@ on:
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@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Setup gcc, g++, cmake, ninja
run: sudo apt update && sudo apt install -y gcc g++ cmake ninja-build
- name: Workaround to ensure that regorus.h is generated
run: |
cargo build -r
cargo fetch
cargo build -r --frozen
working-directory: ./bindings/ffi
- name: Test c binding

View File

@@ -1,26 +1,178 @@
name: bindings/csharp
on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
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
# Disabled for now
#- os: macos-latest
# target: aarch64-apple-darwin
# libpath: |
# **/release/libregorus_ffi.dylib
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Fetch crates
run: cargo fetch
working-directory: ./bindings/ffi
- name: Check Regorus binding formatting
run: cargo fmt --check
working-directory: ./bindings/ffi
- name: Check Clippy linting for Regorus binding
run: cargo clippy --frozen -- -D warnings
working-directory: ./bindings/ffi
- name: Build Regorus binding
run: cargo build -r --target ${{ matrix.runtime.target }} --locked
working-directory: ./bindings/ffi
- name: Upload regorus ffi shared library
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
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-nuget:
name: 'Build Regorus nuget'
runs-on: ubuntu-latest
needs: build-ffi
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
with:
global-json-file: ./bindings/csharp/global.json
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
- name: Download regorus ffi shared libraries
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
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
# Note that we need to supply the target folder within the folder where artifacts are downloaded.
- name: Build Regorus binding
run: dotnet build /p:Configuration=Release /p:RegorusFFIArtifactsDir=./tmp/bindings/ffi/target
working-directory: ./bindings/csharp/Regorus
- name: Pack
run: dotnet pack /p:RegorusFFIArtifactsDir=./tmp/bindings/ffi/target
working-directory: ./bindings/csharp/Regorus
- name: Upload Regorus nuget
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: regorus-nuget
path: bindings/csharp/Regorus/bin/Release/Regorus*.nupkg
if-no-files-found: error
retention-days: 1
test-nuget:
name: 'Test Regorus Nuget: (${{ matrix.runtime.target }})'
needs: build-nuget
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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
with:
global-json-file: ./bindings/csharp/global.json
- name: Build
run: dotnet build
working-directory: ./bindings/csharp/net8.0
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
- name: Run
run: LD_LIBRARY_PATH=. dotnet run
working-directory: ./bindings/csharp/net8.0
- name: Download regorus nuget
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: regorus-nuget
path: ./bindings/csharp/regorus-nuget/
- name: Restore Regorus.Tests
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
working-directory: ./bindings/csharp/Regorus.Tests
- name: Run Regorus.Tests
run: dotnet test --no-restore
working-directory: ./bindings/csharp/Regorus.Tests
- name: Restore TestApp
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
working-directory: ./bindings/csharp/TestApp
- name: Build TestApp
run: dotnet build --no-restore
working-directory: ./bindings/csharp/TestApp
- name: Run TestApp
run: dotnet run --no-build --framework net8.0
working-directory: ./bindings/csharp/TestApp
- name: Restore TargetExampleApp
run: dotnet restore /p:RestoreAdditionalProjectSources=../regorus-nuget
working-directory: ./bindings/csharp/TargetExampleApp
- name: Build TargetExampleApp
run: dotnet build --no-restore
working-directory: ./bindings/csharp/TargetExampleApp
- name: Run TargetExampleApp
run: dotnet run --no-build --framework net8.0
working-directory: ./bindings/csharp/TargetExampleApp

View File

@@ -1,28 +0,0 @@
name: bindings/csharp40
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "5.0.x"
- name: Build
run: dotnet build
working-directory: ./bindings/csharp/net40
- name: Run
run: dotnet run
working-directory: ./bindings/csharp/net40

View File

@@ -5,18 +5,24 @@ on:
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@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Test FFI
run: |
cargo build -r
cargo fetch
cargo build -r --frozen
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo test --features contention_checks --frozen
working-directory: ./bindings/ffi

View File

@@ -5,20 +5,23 @@ on:
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@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- uses: actions/setup-go@v5
- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build ffi
@@ -29,5 +32,5 @@ jobs:
run: |
go mod tidy
go build
LD_LIBRARY_PATH=../../target/release ./regorus_test
LD_LIBRARY_PATH=../ffi/target/release ./regorus_test
working-directory: ./bindings/go

View File

@@ -5,26 +5,35 @@ on:
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@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
java-version: 8
distribution: "corretto"
- uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/toolchains/rust
- name: Building binding
run: |
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --manifest-path bindings/java/Cargo.toml
cargo build --release --manifest-path bindings/java/Cargo.toml --locked
- name: Capture binding version
run: |
version=$(cargo metadata --manifest-path bindings/java/Cargo.toml --format-version 1 \
| jq -r '.packages[] | select(.name == "regorus-java") | .version')
echo "REGORUS_JAVA_VERSION=$version" >> "$GITHUB_ENV"
- name: Build jar
run: mvn package
@@ -32,6 +41,7 @@ jobs:
- name: Test jar
run: |
javac -cp target/regorus-java-0.2.2.jar Test.java
java -Djava.library.path=../../target/release -cp target/regorus-java-0.2.2.jar:. Test
jar="regorus-java-${REGORUS_JAVA_VERSION}.jar"
javac -cp "target/${jar}" Test.java
java -Djava.library.path=target/release -cp "target/${jar}:." Test
working-directory: ./bindings/java

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,19 +18,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Add musl target
run: rustup target add x86_64-unknown-linux-musl
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: ./.github/actions/toolchains/rust
with:
targets: x86_64-unknown-linux-musl
- name: Install musl-gcc
run: sudo apt update && sudo apt install -y musl-tools
- name: Fetch
run: cargo fetch
- name: Build (MUSL)
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl --frozen
- name: Run tests (MUSL)
run: cargo test -r --verbose --target x86_64-unknown-linux-musl
run: cargo test -r --verbose --target x86_64-unknown-linux-musl --frozen
- name: Run tests (MUSL ACI)
run: cargo test -r --test aci --target x86_64-unknown-linux-musl
run: cargo test -r --test aci --target x86_64-unknown-linux-musl --frozen
- name: Run tests (KATA ACI)
run: cargo test -r --test kata --target x86_64-unknown-linux-musl
run: cargo test -r --test kata --target x86_64-unknown-linux-musl --frozen
- name: Run tests (MUSL OPA Conformance)
run: >-
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision --target x86_64-unknown-linux-musl -- $(tr '\n' ' ' < tests/opa.passing)
cargo test -r --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision --target x86_64-unknown-linux-musl -- $(tr '\n' ' ' < tests/opa.passing)

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,10 +18,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Add no_std target
run: rustup target add thumbv7m-none-eabi
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- uses: ./.github/actions/toolchains/rust
with:
targets: thumbv7m-none-eabi
- name: Fetch
run: cargo fetch
- name: Build
run: cargo build -r --target thumbv7m-none-eabi
run: cargo build -r --target thumbv7m-none-eabi --frozen
working-directory: ./tests/ensure_no_std

View File

@@ -5,34 +5,89 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
PYTHON_VERSION: "3.10"
jobs:
test:
runs-on: ubuntu-latest
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@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- uses: actions/setup-python@v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build wheels
- name: Build Python extension
run: |
cargo fetch
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --target ${{ matrix.host.target }} --frozen
working-directory: bindings/python
- name: Build Wheel
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: x86_64
args: --release --out dist --manifest-path bindings/python/Cargo.toml
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
sccache: 'true'
- name: Test wheel
- name: Upload Wheel
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: regorus-wheel-${{ matrix.host.name }}
path: dist/regorus-*.whl
test:
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
host:
- name: ubuntu-24.04
wheel: regorus-0.5.0-cp310-abi3-manylinux_2_34_x86_64.whl
- name: ubuntu-22.04
wheel: regorus-0.5.0-cp310-abi3-manylinux_2_34_x86_64.whl
- name: windows-latest
wheel: regorus-0.5.0-cp310-abi3-win_amd64.whl
needs: build
runs-on: ${{ matrix.host.name }}
steps:
- name: Checkout repository
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- name: Download Regorus wheel
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: wheels
pattern: regorus-wheel-*
merge-multiple: true
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: Test Wheel
run: |
pip3 install dist/regorus-*.whl
cd bindings/python
cargo clippy --all-targets --no-deps -- -Dwarnings
python3 test.py
pip3 install ../../wheels/${{ matrix.host.wheel }}
python3 test.py
working-directory: bindings/python

View File

@@ -8,17 +8,20 @@ on:
jobs:
test:
if: false # temporarily disabled
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- name: Setup Ruby and Rust
uses: oxidize-rb/actions/setup-ruby-and-rust@7ca44a16e287e5ff7dd72ab53f4bd41cbf34a571 #v1.26
with:
ruby-version: "3.3.1"
bundler: 2.6.5
rubygems: 3.6.5
ruby-version: "3.4.2"
rustup-toolchain: "stable"
bundler-cache: true
cargo-cache: true
@@ -27,5 +30,7 @@ jobs:
- name: Run ruby tests
run: |
cd bindings/ruby
gem install bundler
bundle install
cargo clippy --all-targets --no-deps -- -Dwarnings
bundle exec rake

View File

@@ -5,18 +5,24 @@ on:
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@v4
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with:
fetch-depth: 0
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 18
@@ -26,7 +32,10 @@ jobs:
- name: Test wasm binding
run: |
cd bindings/wasm
cargo fetch
cargo clippy --all-targets --no-deps -- -Dwarnings
wasm-pack build --target nodejs --release
wasm-pack test --release --node
# Enable when upstream issue is fixed.
# https://github.com/microsoft/regorus/issues/371
# wasm-pack test --release --node
node test.js

View File

@@ -5,6 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,23 +18,29 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Fetch
run: cargo fetch
- name: Build (all features)
run: cargo build --all-features
run: cargo build --all-features --frozen
- name: Build
run: cargo build
run: cargo build --frozen
- name: Test no_std
run: cargo test --no-default-features
run: cargo test --no-default-features --frozen
- name: Build only std
run: cargo build --example regorus --no-default-features --features "std"
run: cargo build --example regorus --no-default-features --features "std" --frozen
- name: Doc Tests
run: cargo test --doc
run: cargo test --doc --frozen
- name: Run tests
run: cargo test
run: cargo test --frozen
- name: Run tests (ACI)
run: cargo test --test aci
run: cargo test --test aci --frozen
- name: Run tests (KATA)
run: cargo test --test kata
run: cargo test --test kata --frozen
- name: Run tests (OPA Conformance)
run: >-
cargo test --test opa --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
cargo test --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
- name: Run tests (Azure RBAC)
run: cargo test --frozen --features azure-rbac

18
.gitignore vendored
View File

@@ -4,10 +4,6 @@
**/wheels/
**/__pycache__/
# 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
# These are backup files generated by rustfmt
**/*.rs.bk
@@ -25,4 +21,16 @@ worktrees/
# Generated C, C++ headers
bindings/ffi/regorus.h
bindings/ffi/regorus.ffi.hpp
bindings/ffi/regorus.ffi.hpp
bindings/*/target
# C# build folders
**bin
**obj
# Visual Studio folders
**/*.vs
# Visual Studio solution files
*.sln

View File

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

1817
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,16 @@
[workspace]
members = [
"bindings/ffi",
"bindings/python",
"bindings/wasm",
"bindings/java",
"bindings/ruby/ext/regorusrb",
"tests/ensure_no_std",
"xtask",
]
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.2.2"
version = "0.5.0"
edition = "2021"
license-file = "LICENSE"
license = "MIT"
repository = "https://github.com/microsoft/regorus"
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
@@ -24,26 +20,28 @@ keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
doctest = false
[features]
default = ["full-opa", "arc"]
default = ["full-opa", "arc", "rvm"]
arc = ["scientific/arc"]
ast = []
azure_policy = ["dep:jsonschema", "arc", "dashmap"]
azure-rbac = []
base64 = ["dep:data-encoding"]
base64url = ["dep:data-encoding"]
coverage = []
crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha1", "dep:sha2"]
deprecated = []
hex = ["dep:data-encoding"]
http = []
glob = ["dep:wax"]
glob = ["dep:globset"]
graph = []
jsonschema = ["dep:jsonschema"]
jwt = ["dep:jsonwebtoken", "dep:data-encoding", "dep:itertools"]
mimalloc = ["dep:mimalloc"]
net = ["dep:ipnet"]
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
rvm = ["dep:bincode", "dep:indexmap"]
semver = ["dep:semver"]
std = ["rand/std", "rand/std_rng", "serde_json/std"]
std = ["rand/std", "rand/std_rng", "serde_json/std", "msvc_spectre_libs" ]
time = ["dep:chrono", "dep:chrono-tz"]
uuid = ["dep:uuid"]
urlquery = ["dep:url"]
@@ -52,14 +50,13 @@ full-opa = [
"base64",
"base64url",
"coverage",
"crypto",
"deprecated",
"glob",
"graph",
"hex",
"http",
"jwt",
"jsonschema",
"mimalloc",
"net",
"opa-runtime",
"regex",
"semver",
@@ -67,7 +64,9 @@ full-opa = [
"time",
"uuid",
"urlquery",
"yaml"
"yaml",
#"rego-extensions"
]
# Features that can be used in no_std environments.
@@ -77,8 +76,6 @@ opa-no-std = [
"base64",
"base64url",
"coverage",
"crypto",
"deprecated",
"graph",
"hex",
"no_std",
@@ -89,55 +86,65 @@ opa-no-std = [
"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.45", default-features = false }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc"] }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
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, default-features = false }
hmac = {version = "0.12.1", optional = true, default-features = false}
sha2 = {version= "0.10.8", optional = true, default-features = false }
hex = {version = "0.4.3", optional = true, default-features = false, features = ["alloc"] }
sha1 = {version = "0.10.6", optional = true, default-features = false }
md-5 = {version = "0.10.6", optional = true, default-features = false }
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.3", default-features = false }
data-encoding = { version = "2.4.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.2" }
regex = {version = "1.10.2", optional = true, default-features = false }
semver = {version = "1.0.20", optional = true, default-features = false }
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
url = { version = "2.5.0", optional = true }
uuid = { version = "1.6.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.18.0", default-features = false, optional = true }
chrono = { version = "0.4.31", optional = true }
chrono-tz = { version = "0.8.5", optional = true }
jsonwebtoken = { version = "9.2.0", optional = true }
itertools = { version = "0.13.0", default-features = false, optional = true }
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
regex = {version = "1.11.1", optional = true, default-features = false }
semver = {version = "1.0.25", optional = true, default-features = false }
url = { version = "2.5.4", optional = true }
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.30.0", default-features = false, optional = true }
chrono = { version = "0.4.40", optional = true }
chrono-tz = { version = "0.10.1", optional = true }
ipnet = { version = "2.11.0", optional = true, default-features = false }
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
rand = { version = "0.8.5", default-features = false, optional = true }
# Specify thread_rng for in order to use random_range
rand = { version = "0.9.0", default-features = false, features = ["thread_rng"], optional = true }
# 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 }
mimalloc = { path = "mimalloc", optional = true }
# rvm related deps
indexmap = { version = "2.12.1", default-features = false, features = ["serde"], optional = true }
bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"], optional = true }
[dev-dependencies]
anyhow = "1.0.45"
cfg-if = "1.0.0"
clap = { version = "4.4.7", features = ["derive"] }
prettydiff = { version = "0.7.0", default-features = false }
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.7" }
num_cpus = "1.16"
[build-dependencies]
anyhow = "1.0"
[profile.release]
debug = true
lto = true
codegen-units = 1
[[test]]
name="opa"
@@ -155,6 +162,29 @@ 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
[[example]]
name="regorus"
harness=false

View File

@@ -9,8 +9,8 @@
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 supports `import rego.v1`.
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.67.0](https://github.com/open-policy-agent/opa/releases/tag/v0.67.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.
- *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.
@@ -32,7 +32,6 @@ fn main() -> anyhow::Result<()> {
let policy = String::from(
r#"
package example
import rego.v1
allow if {
## All actions are allowed for admins.
@@ -99,7 +98,7 @@ $ cargo build -r --example regorus --no-default-features; strip target/release/e
-rwxr-xr-x 1 anand staff 1.9M May 11 22:04 target/release/examples/regorus*
```
Regorus passes the [OPA v0.67.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
@@ -108,11 +107,11 @@ 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).
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).
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).
@@ -146,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
@@ -184,11 +184,11 @@ This produces the following output
}
```
Next, evaluate a sample [policy](https://github.com/microsoft/regorus/blob/main/examples/example.rego) and [input](https://github.com/microsoft/regorus/blob/main/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)
@@ -204,7 +204,7 @@ Regorus allows determining which lines of a policy have been executed using the
We can try it out using the `regorus` example program by passing in the `--coverage` flag.
```shell
$ regorus eval -d examples/example.rego -i examples/input.json data.example --coverage
$ 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.
@@ -276,7 +276,7 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
```
## OPA Conformance
Regorus has been verified to be compliant with [OPA v0.67.0](https://github.com/open-policy-agent/opa/releases/tag/v0.67.0)
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:
@@ -288,29 +288,24 @@ $ cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precisio
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`
- `jsonpatch`
- `jwtbuiltins`
- `jwtdecodeverify`
- `jwtencodesign`
- `jwtencodesignheadererrors`
- `jwtencodesignpayloaderrors`
- `jwtencodesignraw`
- `jwtverifyhs256`
- `jwtverifyhs384`
- `jwtverifyhs512`
- `jwtverifyrsa`
- `netcidrcontains`
- `netcidrcontainsmatches`
- `netcidrexpand`
- `netcidrintersects`
- `netcidrisvalid`
- `netcidrmerge`
- `netcidroverlap`
- `netlookupipaddr`
@@ -322,6 +317,7 @@ The following test suites don't pass fully due to mising builtins:
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,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);

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

@@ -7,7 +7,8 @@ include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.4 # Optionally specify a commit hash, version tag or branch here
# Use a tag that has a fix for https://github.com/corrosion-rs/corrosion/issues/590
GIT_TAG 6be991bb34c348dfb8344be22f3606288ea5c7fd
)
FetchContent_MakeAvailable(Corrosion)
@@ -31,6 +32,8 @@ corrosion_import_crate(
# See regorus/opa_no_std
FEATURES "custom_allocator,regorus/semver"
LOCKED
# Link statically
CRATE_TYPES staticlib FLAGS --crate-type=staticlib
)

View File

@@ -1,92 +1,126 @@
#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;
char *file_to_string(const char *file)
{
char *buffer = 0;
long length;
FILE * f = fopen (file, "rb");
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);
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) {
return aligned_alloc(alignment, size);
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) {
void regorus_free(uint8_t *ptr)
{
#if defined(_WIN32)
_aligned_free(ptr);
#else
free(ptr);
#endif
}
int main() {
int main()
{
// Create engine.
RegorusEngine* engine = regorus_engine_new();
RegorusEngine *engine = regorus_engine_new();
RegorusResult r;
char* buffer = NULL;
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 != RegorusStatusOk)
goto error;
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 != RegorusStatusOk)
goto error;
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 != RegorusStatusOk)
goto error;
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 != RegorusStatusOk)
goto error;
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 != RegorusStatusOk)
goto error;
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 != RegorusStatusOk)
goto error;
if (r.status != Ok)
goto error;
// Print output
printf("%s", r.output);
regorus_result_drop(r);
// Free the engine.
regorus_engine_drop(engine);

View File

@@ -7,7 +7,8 @@ include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.4 # Optionally specify a commit hash, version tag or branch here
# Use a tag that has a fix for https://github.com/corrosion-rs/corrosion/issues/590
GIT_TAG 6be991bb34c348dfb8344be22f3606288ea5c7fd
)
FetchContent_MakeAvailable(Corrosion)
@@ -24,6 +25,8 @@ corrosion_import_crate(
# Select specific features in regorus.
FEATURES "regorus/semver"
LOCKED
# Link statically
CRATE_TYPES "cdylib"
)

View File

@@ -6,40 +6,45 @@ int main() {
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;
// Load policies.
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
if (r.status != RegorusStatusOk)
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 != RegorusStatusOk)
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 != RegorusStatusOk)
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 != RegorusStatusOk)
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 != RegorusStatusOk)
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 != RegorusStatusOk)
if (r.status != Ok)
goto error;
// Print output
@@ -61,14 +66,14 @@ int main() {
);
// Evaluate rule.
if (r.status != RegorusStatusOk)
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 != RegorusStatusOk)
if (r.status != Ok)
goto error;
// Print output
@@ -77,7 +82,7 @@ int main() {
// Print pretty coverage report.
r = regorus_engine_get_coverage_report_pretty(engine);
if (r.status != RegorusStatusOk)
if (r.status != Ok)
goto error;
printf("%s\n", r.output);

View File

@@ -7,13 +7,16 @@ include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.4 # Optionally specify a commit hash, version tag or branch here
# 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)
# installable ffi target
corrosion_import_crate(
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
MANIFEST_PATH "../ffi/Cargo.toml"
@@ -25,10 +28,58 @@ corrosion_import_crate(
# 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)
# Add path to <regorus-source-folder>/bindings/ffi
target_include_directories(regorus_test PRIVATE "../ffi")
target_link_libraries(regorus_test regorus_ffi)
target_link_libraries(regorus_test regorus_ffi::regorus_ffi)

View File

@@ -6,6 +6,7 @@ void example()
// Create engine
regorus::Engine engine;
engine.set_rego_v0(true);
engine.set_enable_coverage(true);
// Add policies.
@@ -83,6 +84,7 @@ int main() {
// Create engine.
regorus::Engine engine;
engine.set_rego_v0(true);
// Load policies.

View File

@@ -11,8 +11,8 @@ namespace regorus {
class Result {
public:
operator bool() const { return result.status == RegorusStatus::RegorusStatusOk; }
bool operator !() const { return result.status != RegorusStatus::RegorusStatusOk; }
operator bool() const { return result.status == RegorusStatus::Ok; }
bool operator !() const { return result.status != RegorusStatus::Ok; }
const char* output() const {
if (*this && result.output) {
@@ -54,6 +54,9 @@ namespace regorus {
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));

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

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" Version="0.8.0$(RegorusPackageVersionSuffix)"/>
</ItemGroup>
<ItemGroup>
<None Include="../../ffi/target/release/libregorus_ffi.dylib" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,294 @@
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 { Id = "policy.rego", Content = 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)
{
policy.Dispose();
}
}
// 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 { Id = "policy.rego", Content = policy } };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
var result = compiled.EvalWithInput(input);
compiled.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;
}
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);
}
}
}

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

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

@@ -0,0 +1,32 @@
# 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
TODO

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>Enable</Nullable>
<TargetFramework>net8.0</TargetFramework>
<EnableMSTestRunner>true</EnableMSTestRunner>
<!-- More info about dotnet test integration https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-integration-dotnet-test -->
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
</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>
<None Include="../../../tests/**/*.*" Link="tests/%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MSTest" Version="3.8.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Regorus" Version="0.8.0$(RegorusPackageVersionSuffix)"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,256 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text.Json.Nodes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
namespace Regorus.Tests;
[TestClass]
public class RegorusTests
{
[TestMethod]
public void Basic_evaluation_succeeds()
{
using var engine = new Engine();
engine.AddPolicy(
"test.rego",
"package test\nx = 1\nmessage = `Hello`");
var result = engine.EvalRule("data.test.message");
Assert.AreEqual("\"Hello\"", result);
}
[TestMethod]
public void Evaluation_using_file_policies_succeeds()
{
using var engine = new Engine();
engine.SetRegoV0(true);
// Load policies and data.
engine.AddPolicyFromFile("tests/aci/framework.rego");
engine.AddPolicyFromFile("tests/aci/api.rego");
engine.AddPolicyFromFile("tests/aci/policy.rego");
engine.AddDataFromJsonFile("tests/aci/data.json");
// Set input and eval rule.
engine.SetInputFromJsonFile("tests/aci/input.json");
var result = engine.EvalRule("data.framework.mount_overlay");
var expected = """
{
"allowed": true,
"metadata": [
{
"action": "add",
"key": "container0",
"name": "matches",
"value": [
{
"allow_elevated": true,
"allow_stdio_access": false,
"capabilities": {
"ambient": [
"CAP_SYS_ADMIN"
],
"bounding": [
"CAP_SYS_ADMIN"
],
"effective": [
"CAP_SYS_ADMIN"
],
"inheritable": [
"CAP_SYS_ADMIN"
],
"permitted": [
"CAP_SYS_ADMIN"
]
},
"command": [
"rustc",
"--help"
],
"env_rules": [
{
"pattern": "PATH=/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"required": true,
"strategy": "string"
},
{
"pattern": "RUSTUP_HOME=/usr/local/rustup",
"required": true,
"strategy": "string"
},
{
"pattern": "CARGO_HOME=/usr/local/cargo",
"required": true,
"strategy": "string"
},
{
"pattern": "RUST_VERSION=1.52.1",
"required": true,
"strategy": "string"
},
{
"pattern": "TERM=xterm",
"required": false,
"strategy": "string"
},
{
"pattern": "PREFIX_.+=.+",
"required": false,
"strategy": "re2"
}
],
"exec_processes": [
{
"command": [
"top"
],
"signals": []
}
],
"layers": [
"fe84c9d5bfddd07a2624d00333cf13c1a9c941f3a261f13ead44fc6a93bc0e7a",
"4dedae42847c704da891a28c25d32201a1ae440bce2aecccfa8e6f03b97a6a6c",
"41d64cdeb347bf236b4c13b7403b633ff11f1cf94dbc7cf881a44d6da88c5156",
"eb36921e1f82af46dfe248ef8f1b3afb6a5230a64181d960d10237a08cd73c79",
"e769d7487cc314d3ee748a4440805317c19262c7acd2fdbdb0d47d2e4613a15c",
"1b80f120dbd88e4355d6241b519c3e25290215c469516b49dece9cf07175a766"
],
"mounts": [
{
"destination": "/container/path/one",
"options": [
"rbind",
"rshared",
"rw"
],
"source": "sandbox:///host/path/one",
"type": "bind"
},
{
"destination": "/container/path/two",
"options": [
"rbind",
"rshared",
"ro"
],
"source": "sandbox:///host/path/two",
"type": "bind"
}
],
"no_new_privileges": true,
"seccomp_profile_sha256": "",
"signals": [],
"user": {
"group_idnames": [
{
"pattern": "",
"strategy": "any"
}
],
"umask": "0022",
"user_idname": {
"pattern": "",
"strategy": "any"
}
},
"working_dir": "/home/user"
}
]
},
{
"action": "add",
"key": "/run/gcs/c/container0/rootfs",
"name": "overlayTargets",
"value": true
}
]
}
""";
Assert.IsTrue(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(result!)), $"Actual: {result}");
}
[TestMethod]
public void GetPolicyPackageNames_succeeds()
{
using var engine = new Engine();
engine.AddPolicy(
"test.rego",
"package test\nx = 1\nmessage = `Hello`");
engine.AddPolicy(
"test.rego",
"package test.nested.name\nx = 1\nmessage = `Hello`");
var result = engine.GetPolicyPackageNames();
var packageNames = JsonNode.Parse(result!);
Assert.AreEqual("test", packageNames![0]["package_name"].ToString());
Assert.AreEqual("test.nested.name", packageNames![1]["package_name"].ToString());
}
[TestMethod]
public void GetPolicyParameters_succeeds()
{
using var engine = new Engine();
engine.AddPolicy(
"test.rego",
"package test\n default parameters.a = 5\nparameters.b = 10\nx = 1\nmessage = `Hello`");
var result = engine.GetPolicyParameters();
var parameters = JsonNode.Parse(result!);
Assert.AreEqual(1, parameters![0]["parameters"].AsArray().Count);
Assert.AreEqual(1, parameters![0]["modifiers"].AsArray().Count);
Assert.AreEqual("a", parameters![0]["parameters"][0]["name"].ToString());
Assert.AreEqual("b", parameters![0]["modifiers"][0]["name"].ToString());
}
[TestMethod]
public void SetInputJson_has_negligible_allocations_after_warmup()
{
using var engine = new Engine();
const string payload = "{}";
// Warm up the engine and JIT to ensure subsequent measurements are representative.
for (int i = 0; i < 16; i++)
{
engine.SetInputJson(payload);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
const int iterations = 256;
var before = GC.GetAllocatedBytesForCurrentThread();
for (int i = 0; i < iterations; i++)
{
engine.SetInputJson(payload);
}
var after = GC.GetAllocatedBytesForCurrentThread();
var allocated = Math.Max(0, after - before);
var bytesPerOp = allocated / (double)iterations;
// Runtime bookkeeping (delegate caches, GC write barriers) differs across platforms, so
// we measure bytes per call rather than absolute totals and allow a small budget.
// CI will flag regressions where marshalling starts allocating per invocation.
// Allow a small budget for delegates and runtime bookkeeping while still flagging regressions.
Assert.IsTrue(
bytesPerOp <= 512,
$"Expected ≤512 B/op after warmup, but observed {bytesPerOp:F2} B/op (total {allocated} bytes)."
);
}
}

View File

@@ -0,0 +1,220 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text;
using System.Text.Json;
using System.Threading;
#nullable enable
namespace Regorus
{
/// <summary>
/// Represents a compiled Regorus policy that can be evaluated efficiently.
/// This class wraps a pre-compiled policy that can be evaluated multiple times
/// with different inputs without recompilation overhead.
///
/// This class manages unmanaged resources and should not be copied or cloned.
/// Each instance represents a unique native policy object.
///
/// Thread Safety: This class is thread-safe for all operations. Multiple threads
/// can safely call EvalWithInput() concurrently, and Dispose() will safely wait
/// for all active evaluations to complete before freeing resources. No external
/// synchronization is required.
/// </summary>
public unsafe sealed class CompiledPolicy : IDisposable
{
private RegorusCompiledPolicyHandle? _handle;
private readonly ManualResetEventSlim _idleEvent = new(initialState: true);
private int _isDisposed;
private int _activeEvaluations;
internal CompiledPolicy(RegorusCompiledPolicyHandle handle)
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
/// <summary>
/// Evaluates the compiled policy with the given input.
/// For target policies, evaluates the target's effect rule.
/// For regular policies, evaluates the originally compiled rule.
/// </summary>
/// <param name="inputJson">JSON encoded input data (resource) to validate against the policy</param>
/// <returns>The evaluation result as JSON string</returns>
/// <exception cref="Exception">Thrown when policy evaluation fails</exception>
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
public string? EvalWithInput(string inputJson)
{
// Increment active evaluations count
var active = System.Threading.Interlocked.Increment(ref _activeEvaluations);
if (active == 1)
{
_idleEvent.Reset();
}
try
{
ThrowIfDisposed();
return Internal.Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
{
return UseHandle(policyPtr =>
{
unsafe
{
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input((Internal.RegorusCompiledPolicy*)policyPtr, (byte*)inputPtr));
}
});
});
}
finally
{
// Decrement active evaluations count
var remaining = System.Threading.Interlocked.Decrement(ref _activeEvaluations);
if (remaining == 0)
{
_idleEvent.Set();
}
}
}
/// <summary>
/// Gets information about the compiled policy including metadata about modules,
/// target configuration, and resource types.
/// </summary>
/// <returns>Policy information containing module IDs, target name, applicable resource types, entry point rule, and parameters</returns>
/// <exception cref="Exception">Thrown when getting policy info fails</exception>
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
public PolicyInfo GetPolicyInfo()
{
ThrowIfDisposed();
var jsonResult = UseHandle(policyPtr =>
{
unsafe
{
return CheckAndDropResult(Internal.API.regorus_compiled_policy_get_policy_info((Internal.RegorusCompiledPolicy*)policyPtr));
}
});
if (string.IsNullOrEmpty(jsonResult))
{
throw new Exception("Failed to get policy info: empty response");
}
try
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<PolicyInfo>(jsonResult!, options)
?? throw new Exception("Failed to deserialize policy info");
}
catch (JsonException ex)
{
throw new Exception($"Failed to parse policy info JSON: {ex.Message}", ex);
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
var handle = _handle;
if (handle != null)
{
_idleEvent.Wait();
handle.Dispose();
_handle = null;
}
_idleEvent.Dispose();
}
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
throw new ObjectDisposedException(nameof(CompiledPolicy));
}
private string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
private string? CheckAndDropResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type switch
{
Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output),
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Internal.RegorusDataType.Integer => result.int_value.ToString(),
Internal.RegorusDataType.None => null,
_ => StringFromUTF8((IntPtr)result.output)
};
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private RegorusCompiledPolicyHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(CompiledPolicy));
}
return handle;
}
private T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
try
{
handle.DangerousAddRef(ref addedRef);
var pointer = handle.DangerousGetHandle();
if (pointer == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(CompiledPolicy));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
}
}

View File

@@ -0,0 +1,197 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Represents a policy module with an ID and content.
/// </summary>
public struct PolicyModule
{
/// <summary>
/// Gets or sets the unique identifier for this policy module.
/// </summary>
public string Id { get; set; }
/// <summary>
/// Gets or sets the Rego policy content.
/// </summary>
public string Content { get; set; }
/// <summary>
/// Initializes a new instance of the PolicyModule struct.
/// </summary>
/// <param name="id">The unique identifier for this policy module</param>
/// <param name="content">The Rego policy content</param>
public PolicyModule(string id, string content)
{
Id = id;
Content = content;
}
}
/// <summary>
/// Provides static methods for compiling policies into efficient compiled representations.
/// These are convenience methods that create an engine internally and perform compilation.
/// </summary>
public static unsafe class Compiler
{
/// <summary>
/// Compiles a policy from data and modules with a specific entry point rule.
/// This is a convenience function that sets up an Engine internally and calls the appropriate compilation method.
/// </summary>
/// <param name="dataJson">JSON string containing static data for policy evaluation</param>
/// <param name="modules">List of policy modules to compile</param>
/// <param name="entryPointRule">The specific rule path to evaluate (e.g., "data.policy.allow")</param>
/// <returns>A compiled policy that can be evaluated efficiently</returns>
/// <exception cref="Exception">Thrown when compilation fails</exception>
public static CompiledPolicy CompilePolicyWithEntrypoint(string dataJson, IEnumerable<PolicyModule> modules, string entryPointRule)
{
var modulesArray = modules.ToArray();
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
try
{
for (int i = 0; i < modulesArray.Length; i++)
{
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
pinnedStrings.Add(idPinned);
pinnedStrings.Add(contentPinned);
nativeModules[i] = new Internal.RegorusPolicyModule
{
id = idPinned.Pointer,
content = contentPinned.Pointer
};
}
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
Utf8Marshaller.WithUtf8(entryPointRule, entryPointPtr =>
{
unsafe
{
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
{
var result = Internal.API.regorus_compile_policy_with_entrypoint(
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length, (byte*)entryPointPtr);
var policy = GetCompiledPolicyResult(result);
return policy;
}
}
}));
}
finally
{
foreach (var pinned in pinnedStrings)
{
pinned.Dispose();
}
}
}
/// <summary>
/// Compiles a target-aware policy from data and modules.
/// This is a convenience function that sets up an Engine internally and calls target-aware compilation.
/// At least one module must contain a `__target__` declaration.
/// </summary>
/// <param name="dataJson">JSON string containing static data for policy evaluation</param>
/// <param name="modules">List of policy modules to compile</param>
/// <returns>A compiled policy that can be evaluated efficiently</returns>
/// <exception cref="Exception">Thrown when compilation fails</exception>
public static CompiledPolicy CompilePolicyForTarget(string dataJson, IEnumerable<PolicyModule> modules)
{
var modulesArray = modules.ToArray();
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
try
{
for (int i = 0; i < modulesArray.Length; i++)
{
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
pinnedStrings.Add(idPinned);
pinnedStrings.Add(contentPinned);
nativeModules[i] = new Internal.RegorusPolicyModule
{
id = idPinned.Pointer,
content = contentPinned.Pointer
};
}
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
unsafe
{
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
{
var result = Internal.API.regorus_compile_policy_for_target(
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length);
var policy = GetCompiledPolicyResult(result);
return policy;
}
}
});
}
finally
{
foreach (var pinned in pinnedStrings)
{
pinned.Dispose();
}
}
}
private static string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
private static CompiledPolicy GetCompiledPolicyResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown compilation error occurred");
}
if (result.data_type != Internal.RegorusDataType.Pointer || result.pointer_value == null)
{
throw new Exception("Expected compiled policy pointer but got different data type");
}
var handle = RegorusCompiledPolicyHandle.FromPointer((IntPtr)result.pointer_value);
return new CompiledPolicy(handle);
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
}
}

View File

@@ -0,0 +1,445 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
using System.Text;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// C# Wrapper for the Regorus engine.
/// This class is not thread-safe. For multithreaded use, prefer cloning after adding policies and data to an instance.
/// Cloning is cheap and involves only incrementing reference counts for shared immutable objects like parsed policies,
/// data etc. Mutable state is deep copied as needed.
/// </summary>
public unsafe sealed class Engine : IDisposable
{
private RegorusEngineHandle? _handle;
private int _isDisposed;
public Engine()
{
_handle = RegorusEngineHandle.Create();
}
public void Dispose()
{
Dispose(disposing: true);
// This object will be cleaned up by the Dispose method.
// Therefore, call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
void Dispose(bool disposing)
{
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
_handle?.Dispose();
_handle = null;
}
}
private Engine(RegorusEngineHandle handle)
{
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
}
public Engine Clone()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
var clonePtr = Regorus.Internal.API.regorus_engine_clone((Regorus.Internal.RegorusEngine*)enginePtr);
if (clonePtr is null)
{
throw new InvalidOperationException("Failed to clone Regorus engine.");
}
var handle = RegorusEngineHandle.FromPointer((IntPtr)clonePtr);
return new Engine(handle);
}
});
}
public void SetStrictBuiltinErrors(bool strict)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors((Regorus.Internal.RegorusEngine*)enginePtr, strict));
}
});
}
public string? AddPolicy(string path, string rego)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(path, pathPtr =>
Utf8Marshaller.WithUtf8(rego, regoPtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr, (byte*)regoPtr));
}
});
}
}));
}
public void SetRegoV0(bool enable)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0((Regorus.Internal.RegorusEngine*)enginePtr, enable));
}
});
}
public string? AddPolicyFromFile(string path)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(path, pathPtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
}
});
}
});
}
public void AddDataJson(string data)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(data, dataPtr =>
{
unsafe
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)dataPtr));
}
});
}
});
}
public void AddDataFromJsonFile(string path)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(path, pathPtr =>
{
unsafe
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
}
});
}
});
}
public void SetInputJson(string input)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(input, inputPtr =>
{
unsafe
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)inputPtr));
}
});
}
});
}
public void SetInputFromJsonFile(string path)
{
ThrowIfDisposed();
Utf8Marshaller.WithUtf8(path, pathPtr =>
{
unsafe
{
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
}
});
}
});
}
public string? EvalQuery(string query)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(query, queryPtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)queryPtr));
}
});
}
});
}
public string? EvalRule(string rule)
{
ThrowIfDisposed();
return Utf8Marshaller.WithUtf8(rule, rulePtr =>
{
unsafe
{
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)rulePtr));
}
});
}
});
}
public void SetEnableCoverage(bool enable)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage((Regorus.Internal.RegorusEngine*)enginePtr, enable));
}
});
}
public void ClearCoverageData()
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetCoverageReport()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetCoverageReportPretty()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public void SetGatherPrints(bool enable)
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints((Regorus.Internal.RegorusEngine*)enginePtr, enable));
}
});
}
public string? TakePrints()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetAstAsJson()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetPolicyPackageNames()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? GetPolicyParameters()
{
ThrowIfDisposed();
return UseHandle(enginePtr =>
{
unsafe
{
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
{
if (result.status != Regorus.Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
var ex = new Exception(message);
Regorus.Internal.API.regorus_result_drop(result);
throw ex;
}
var resultString = "";
if (result.output is not null)
{
resultString = StringFromUTF8((IntPtr)result.output);
}
Regorus.Internal.API.regorus_result_drop(result);
return resultString;
}
private void ThrowIfDisposed()
{
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
{
throw new ObjectDisposedException(nameof(Engine));
}
}
private RegorusEngineHandle GetHandleForUse()
{
var handle = _handle;
if (handle is null || handle.IsClosed || handle.IsInvalid)
{
throw new ObjectDisposedException(nameof(Engine));
}
return handle;
}
private void UseHandle(Action<IntPtr> action)
{
UseHandle<object?>(handlePtr =>
{
action(handlePtr);
return null;
});
}
private T UseHandle<T>(Func<IntPtr, T> func)
{
var handle = GetHandleForUse();
bool addedRef = false;
try
{
handle.DangerousAddRef(ref addedRef);
var pointer = handle.DangerousGetHandle();
if (pointer == IntPtr.Zero)
{
throw new ObjectDisposedException(nameof(Engine));
}
return func(pointer);
}
finally
{
if (addedRef)
{
handle.DangerousRelease();
}
}
}
}
}

View File

@@ -0,0 +1,546 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
#pragma warning disable CS8500
#pragma warning disable CS8981
namespace Regorus.Internal
{
/// <summary>
/// Native FFI method declarations for Regorus.
/// This file contains all P/Invoke declarations for the Regorus native library.
/// </summary>
internal static unsafe partial class API
{
private const string LibraryName = "regorus_ffi";
#region Common Methods
/// <summary>
/// Drop a RegorusResult.
/// output and error_message strings are not valid after drop.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_result_drop(RegorusResult result);
#endregion
#region Engine Methods
/// <summary>
/// Construct a new Engine.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusEngine* regorus_engine_new();
/// <summary>
/// Clone a RegorusEngine.
/// To avoid having to parse same policy again, the engine can be cloned
/// after policies and data have been added.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine);
/// <summary>
/// Drop a RegorusEngine.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_engine_drop(RegorusEngine* engine);
/// <summary>
/// Add a policy.
/// The policy is parsed into AST.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_add_policy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_policy(RegorusEngine* engine, byte* path, byte* rego);
/// <summary>
/// Add a policy from file.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_add_policy_from_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_policy_from_file(RegorusEngine* engine, byte* path);
/// <summary>
/// Add policy data.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_add_data_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_data_json(RegorusEngine* engine, byte* data);
/// <summary>
/// Get list of loaded Rego packages as JSON.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_packages", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_packages(RegorusEngine* engine);
/// <summary>
/// Get list of policies as JSON.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_policies", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_policies(RegorusEngine* engine);
/// <summary>
/// Add data from JSON file.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_add_data_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_add_data_from_json_file(RegorusEngine* engine, byte* path);
/// <summary>
/// Clear policy data.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_clear_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_clear_data(RegorusEngine* engine);
/// <summary>
/// Set input.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_input
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_input_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_input_json(RegorusEngine* engine, byte* input);
/// <summary>
/// Set input from JSON file.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_input_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_input_from_json_file(RegorusEngine* engine, byte* path);
/// <summary>
/// Evaluate query.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_query
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_eval_query", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_eval_query(RegorusEngine* engine, byte* query);
/// <summary>
/// Evaluate specified rule.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_eval_rule", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_eval_rule(RegorusEngine* engine, byte* rule);
/// <summary>
/// Enable/disable coverage.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_enable_coverage", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_enable_coverage(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable);
/// <summary>
/// Get coverage report.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_coverage_report", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_coverage_report(RegorusEngine* engine);
/// <summary>
/// Enable/disable strict builtin errors.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_strict_builtin_errors
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_strict_builtin_errors", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_strict_builtin_errors(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool strict);
/// <summary>
/// Get pretty printed coverage report.
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_coverage_report_pretty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_coverage_report_pretty(RegorusEngine* engine);
/// <summary>
/// Clear coverage data.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_clear_coverage_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_clear_coverage_data(RegorusEngine* engine);
/// <summary>
/// Whether to gather output of print statements.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_gather_prints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_gather_prints(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable);
/// <summary>
/// Take all the gathered print statements.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_take_prints", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_take_prints(RegorusEngine* engine);
/// <summary>
/// Get AST of policies.
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_ast_as_json
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_ast_as_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_ast_as_json(RegorusEngine* engine);
/// <summary>
/// Gets the package names defined in each policy added to the engine.
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_package_names
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_policy_package_names", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_policy_package_names(RegorusEngine* engine);
/// <summary>
/// Gets the parameters defined in each policy added to the engine.
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_policy_parameters
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_get_policy_parameters", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_get_policy_parameters(RegorusEngine* engine);
/// <summary>
/// Enable/disable rego v1.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_rego_v0
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_set_rego_v0", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_set_rego_v0(RegorusEngine* engine, [MarshalAs(UnmanagedType.U1)] bool enable);
/// <summary>
/// Compile a target-aware policy from the current engine state.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.compile_for_target
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_compile_for_target", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_compile_for_target(RegorusEngine* engine);
/// <summary>
/// Compile a policy with a specific entry point rule.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.compile_with_entrypoint
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_engine_compile_with_entrypoint", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_engine_compile_with_entrypoint(RegorusEngine* engine, byte* rule);
#endregion
#region Compilation Methods
/// <summary>
/// Compiles a policy from data and modules with a specific entry point rule.
/// This is a convenience function that wraps regorus::compile_policy_with_entrypoint.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compile_policy_with_entrypoint", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_policy_with_entrypoint(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len, byte* entry_point_rule);
/// <summary>
/// Compiles a target-aware policy from data and modules.
/// This is a convenience function that wraps regorus::compile_policy_for_target.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compile_policy_for_target", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compile_policy_for_target(byte* data_json, RegorusPolicyModule* modules, UIntPtr modules_len);
#endregion
#region Compiled Policy Methods
/// <summary>
/// Drop a RegorusCompiledPolicy.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compiled_policy_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void regorus_compiled_policy_drop(RegorusCompiledPolicy* compiled_policy);
/// <summary>
/// Evaluate the compiled policy with the given input.
/// For target policies, evaluates the target's effect rule.
/// For regular policies, evaluates the originally compiled rule.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compiled_policy_eval_with_input", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compiled_policy_eval_with_input(RegorusCompiledPolicy* compiled_policy, byte* input);
/// <summary>
/// Get information about the compiled policy including metadata about modules,
/// target configuration, and resource types.
/// Returns a JSON-encoded PolicyInfo struct containing comprehensive
/// information about the compiled policy such as module IDs, target name,
/// applicable resource types, entry point rule, and parameters.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_compiled_policy_get_policy_info", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_compiled_policy_get_policy_info(RegorusCompiledPolicy* compiled_policy);
#endregion
#region Target Registry Methods
/// <summary>
/// Register a target from JSON definition.
/// The target JSON should follow the target schema format.
/// Once registered, the target can be referenced in Rego policies using __target__ rules.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_register_target_from_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_register_target_from_json(byte* target_json);
/// <summary>
/// Check if a target is registered.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_contains", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_contains(byte* name);
/// <summary>
/// Get a list of all registered target names as JSON array.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_list_names", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_list_names();
/// <summary>
/// Remove a target from the registry by name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_remove", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_remove(byte* name);
/// <summary>
/// Clear all targets from the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_clear", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_clear();
/// <summary>
/// Get the number of registered targets.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_len();
/// <summary>
/// Check if the target registry is empty.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_target_registry_is_empty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_target_registry_is_empty();
#endregion
#region Resource Schema Registry Methods
/// <summary>
/// Register a resource schema from JSON with a given name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_register", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_register(byte* name, byte* schema_json);
/// <summary>
/// Check if a resource schema with the given name exists.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_contains", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_contains(byte* name);
/// <summary>
/// Get the number of registered resource schemas.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_len();
/// <summary>
/// Check if the resource schema registry is empty.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_is_empty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_is_empty();
/// <summary>
/// List all registered resource schema names as a JSON array.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_list_names", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_list_names();
/// <summary>
/// Remove a resource schema by name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_remove", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_remove(byte* name);
/// <summary>
/// Clear all resource schemas from the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_resource_schema_clear", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_resource_schema_clear();
#endregion
#region Effect Schema Registry Methods
/// <summary>
/// Register an effect schema from JSON with a given name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_register", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_register(byte* name, byte* schema_json);
/// <summary>
/// Check if an effect schema with the given name exists.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_contains", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_contains(byte* name);
/// <summary>
/// Get the number of registered effect schemas.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_len", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_len();
/// <summary>
/// Check if the effect schema registry is empty.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_is_empty", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_is_empty();
/// <summary>
/// List all registered effect schema names as a JSON array.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_list_names", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_list_names();
/// <summary>
/// Remove an effect schema by name.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_remove", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_remove(byte* name);
/// <summary>
/// Clear all effect schemas from the registry.
/// </summary>
[DllImport(LibraryName, EntryPoint = "regorus_effect_schema_clear", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern RegorusResult regorus_effect_schema_clear();
#endregion
}
#region Native Structures
/// <summary>
/// Type of data contained in RegorusResult.
/// </summary>
internal enum RegorusDataType : uint
{
/// <summary>
/// No data / void.
/// </summary>
None,
/// <summary>
/// String data (output field is valid).
/// </summary>
String,
/// <summary>
/// Boolean data (bool_value field is valid).
/// </summary>
Boolean,
/// <summary>
/// Integer data (int_value field is valid).
/// </summary>
Integer,
/// <summary>
/// Pointer data (pointer_value field is valid).
/// </summary>
Pointer,
}
/// <summary>
/// Status of a call on RegorusEngine.
/// </summary>
internal enum RegorusStatus : uint
{
/// <summary>
/// The operation was successful.
/// </summary>
Ok,
/// <summary>
/// The operation was unsuccessful.
/// </summary>
Error,
/// <summary>
/// Invalid data format provided.
/// </summary>
InvalidDataFormat,
/// <summary>
/// Invalid entrypoint rule specified.
/// </summary>
InvalidEntrypoint,
/// <summary>
/// Compilation failed.
/// </summary>
CompilationFailed,
/// <summary>
/// Invalid argument provided.
/// </summary>
InvalidArgument,
/// <summary>
/// Invalid module ID.
/// </summary>
InvalidModuleId,
/// <summary>
/// Invalid policy content.
/// </summary>
InvalidPolicy,
}
/// <summary>
/// Result of a call on RegorusEngine.
/// Must be freed using regorus_result_drop.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusResult
{
/// <summary>
/// Status.
/// </summary>
public RegorusStatus status;
/// <summary>
/// Type of data contained in this result.
/// </summary>
public RegorusDataType data_type;
/// <summary>
/// String output produced by the call.
/// Valid when data_type is String. Owned by Rust.
/// </summary>
public byte* output;
/// <summary>
/// Boolean value.
/// Valid when data_type is Boolean.
/// </summary>
public bool bool_value;
/// <summary>
/// Integer value.
/// Valid when data_type is Integer.
/// </summary>
public long int_value;
/// <summary>
/// Pointer value.
/// Valid when data_type is Pointer.
/// </summary>
public void* pointer_value;
/// <summary>
/// Errors produced by the call.
/// Owned by Rust.
/// </summary>
public byte* error_message;
}
/// <summary>
/// Wrapper for regorus::Engine.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusEngine
{
}
/// <summary>
/// Wrapper for regorus::CompiledPolicy.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusCompiledPolicy
{
}
/// <summary>
/// FFI wrapper for PolicyModule struct.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusPolicyModule
{
public byte* id;
public byte* content;
}
#endregion
}

View File

@@ -0,0 +1,141 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Text.Json.Serialization;
#nullable enable
namespace Regorus
{
/// <summary>
/// Information about a compiled policy, including metadata about modules,
/// target configuration, and resource types that the policy can evaluate.
/// </summary>
public class PolicyInfo
{
/// <summary>
/// List of module identifiers that were compiled into this policy.
/// Each module ID represents a unique policy module that contributes
/// rules, functions, or data to the compiled policy.
/// </summary>
[JsonPropertyName("module_ids")]
public List<string> ModuleIds { get; set; } = new List<string>();
/// <summary>
/// Name of the target configuration used during compilation, if any.
/// This indicates which target schema and validation rules were applied.
/// </summary>
[JsonPropertyName("target_name")]
public string? TargetName { get; set; }
/// <summary>
/// List of resource types that this policy can evaluate.
/// For target-aware policies, this contains the inferred or configured
/// resource types. For general policies, this may be empty.
/// </summary>
[JsonPropertyName("applicable_resource_types")]
public List<string> ApplicableResourceTypes { get; set; } = new List<string>();
/// <summary>
/// The primary rule or entrypoint that this policy evaluates.
/// This is the rule path that will be executed when the policy runs.
/// </summary>
[JsonPropertyName("entrypoint_rule")]
public string EntrypointRule { get; set; } = string.Empty;
/// <summary>
/// The effect rule name for target-aware policies, if applicable.
/// This is the specific effect rule (e.g., "effect", "allow", "deny")
/// that determines the policy decision for target evaluation.
/// </summary>
[JsonPropertyName("effect_rule")]
public string? EffectRule { get; set; }
/// <summary>
/// Parameters that can be configured for this policy.
/// Contains parameter names and their expected types or default values.
/// Used for parameterized policies that accept configuration at evaluation time.
/// Each element represents parameters from a different module.
/// </summary>
[JsonPropertyName("parameters")]
public List<PolicyParameters> Parameters { get; set; } = new List<PolicyParameters>();
}
/// <summary>
/// Parameters that can be configured for a policy.
/// </summary>
public class PolicyParameters
{
/// <summary>
/// Source file where the parameters are defined.
/// </summary>
[JsonPropertyName("source_file")]
public string SourceFile { get; set; } = string.Empty;
/// <summary>
/// List of parameter definitions.
/// </summary>
[JsonPropertyName("parameters")]
public List<PolicyParameter> Parameters { get; set; } = new List<PolicyParameter>();
/// <summary>
/// List of parameter modifiers.
/// </summary>
[JsonPropertyName("modifiers")]
public List<PolicyParameterModifier> Modifiers { get; set; } = new List<PolicyParameterModifier>();
}
/// <summary>
/// A single parameter definition.
/// </summary>
public class PolicyParameter
{
/// <summary>
/// Name of the parameter.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Type of the parameter.
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
/// <summary>
/// Default value of the parameter, if any.
/// </summary>
[JsonPropertyName("default")]
public object? Default { get; set; }
/// <summary>
/// Description of the parameter.
/// </summary>
[JsonPropertyName("description")]
public string? Description { get; set; }
/// <summary>
/// Allowed values for the parameter, if constrained.
/// </summary>
[JsonPropertyName("allowed_values")]
public List<object>? AllowedValues { get; set; }
}
/// <summary>
/// A parameter modifier that affects parameter behavior.
/// </summary>
public class PolicyParameterModifier
{
/// <summary>
/// Name of the modifier.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Value of the modifier.
/// </summary>
[JsonPropertyName("value")]
public object? Value { get; set; }
}
}

View File

@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<RootNamespace>Microsoft.Regorus</RootNamespace>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>10.0</LangVersion>
<!-- See https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-pack -->
<VersionPrefix>0.8.0</VersionPrefix>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
<!--
$(RegorusFFIArtifactsDir) is the location where regorus shared libraries have been
built for various platforms and copied to. RegorusFFIArtifactsDir is passed in
by the publishing pipeline.
For each target triple, `Pack` expects the regorus ffi shared library
to be found in $(RegorusFFIArtifactsDir)/<target-triple>/release.
If $(IgnoreMissingArtifacts) is not set, ensure that the binaries for officially supported platforms exists.
-->
<Target Name="ChecksRegorusFFIArtifactsDir" BeforeTargets="Pack" Condition="'$(IgnoreMissingArtifacts)' == ''">
<Error Text="RegorusFFIArtifactsDir must be supplied." Condition="$(RegorusFFIArtifactsDir) == ''" />
<!-- Ensure that the binaries for officially supported platforms exists. -->
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.dll missing."
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.dll')" />
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.pdb missing."
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/regorus_ffi.pdb')" />
<Error Text="$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/release/libregorus_ffi.so missing."
Condition="!Exists('$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/release/libregorus_ffi.so')" />
</Target>
<ItemGroup>
<None Include="docs/README.md" Pack="true" PackagePath="/" />
<!-- Copy each binary to expected location within the package -->
<None Include="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/*.dll" Pack="true" PackagePath="runtimes/win-x64/native/" />
<None Include="$(RegorusFFIArtifactsDir)/x86_64-pc-windows-msvc/release/*.pdb" Pack="true" PackagePath="runtimes/win-x64/native/" />
<None Include="$(RegorusFFIArtifactsDir)/aarch64-pc-windows-msvc/release/*.dll" Pack="true" PackagePath="runtimes/win-arm64/native/" />
<None Include="$(RegorusFFIArtifactsDir)/aarch64-pc-windows-msvc/release/*.pdb" Pack="true" PackagePath="runtimes/win-arm64/native/" />
<None Include="$(RegorusFFIArtifactsDir)/x86_64-unknown-linux-gnu/release/lib*.so" Pack="true" PackagePath="runtimes/linux-x64/native/" />
<None Include="$(RegorusFFIArtifactsDir)/aarch64-apple-darwin/release/lib*.dylib" Pack="true" PackagePath="runtimes/osx-arm64/native/" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
#nullable enable
namespace Regorus
{
internal sealed class RegorusEngineHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private RegorusEngineHandle() : base(ownsHandle: true)
{
}
internal static RegorusEngineHandle Create()
{
unsafe
{
var raw = Internal.API.regorus_engine_new();
if (raw is null)
{
throw new InvalidOperationException("Failed to create Regorus engine.");
}
var handle = new RegorusEngineHandle();
handle.SetHandle((IntPtr)raw);
return handle;
}
}
internal static RegorusEngineHandle FromPointer(IntPtr pointer)
{
if (pointer == IntPtr.Zero)
{
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
}
var handle = new RegorusEngineHandle();
handle.SetHandle(pointer);
return handle;
}
protected override bool ReleaseHandle()
{
if (!IsInvalid && !IsClosed)
{
unsafe
{
Internal.API.regorus_engine_drop((Internal.RegorusEngine*)handle);
}
SetHandle(IntPtr.Zero);
}
return true;
}
}
internal sealed class RegorusCompiledPolicyHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private RegorusCompiledPolicyHandle() : base(ownsHandle: true)
{
}
internal static RegorusCompiledPolicyHandle FromPointer(IntPtr pointer)
{
if (pointer == IntPtr.Zero)
{
throw new ArgumentException("Pointer cannot be zero.", nameof(pointer));
}
var handle = new RegorusCompiledPolicyHandle();
handle.SetHandle(pointer);
return handle;
}
protected override bool ReleaseHandle()
{
if (!IsInvalid && !IsClosed)
{
unsafe
{
Internal.API.regorus_compiled_policy_drop((Internal.RegorusCompiledPolicy*)handle);
}
SetHandle(IntPtr.Zero);
}
return true;
}
}
}

View File

@@ -0,0 +1,297 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Provides static methods for managing the global resource schema registry.
/// Resource schemas define the structure and validation rules for Azure Policy resources.
/// </summary>
public static unsafe class SchemaRegistry
{
/// <summary>
/// Register a resource schema from JSON with a given name.
/// </summary>
/// <param name="name">Name to register the schema under</param>
/// <param name="schemaJson">JSON string representing the schema</param>
/// <exception cref="Exception">Thrown when schema registration fails</exception>
public static void RegisterResource(string name, string schemaJson)
{
Utf8Marshaller.WithUtf8(name, namePtr =>
{
Utf8Marshaller.WithUtf8(schemaJson, schemaPtr =>
{
unsafe
{
CheckAndDropResult(Internal.API.regorus_resource_schema_register((byte*)namePtr, (byte*)schemaPtr));
}
});
});
}
/// <summary>
/// Check if a resource schema with the given name exists.
/// </summary>
/// <param name="name">Name of the schema to check</param>
/// <returns>True if the schema exists, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool ContainsResource(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_resource_schema_contains((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Get the number of registered resource schemas.
/// </summary>
/// <returns>The number of registered resource schemas</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static long ResourceCount
{
get
{
var result = Internal.API.regorus_resource_schema_len();
return GetIntResult(result);
}
}
/// <summary>
/// Check if the resource schema registry is empty.
/// </summary>
/// <returns>True if the registry is empty, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool IsResourceRegistryEmpty
{
get
{
var result = Internal.API.regorus_resource_schema_is_empty();
return GetBoolResult(result);
}
}
/// <summary>
/// List all registered resource schema names.
/// </summary>
/// <returns>JSON array of schema names</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static string ListResourceNames()
{
return CheckAndDropResult(Internal.API.regorus_resource_schema_list_names()) ?? "[]";
}
/// <summary>
/// Remove a resource schema by name.
/// </summary>
/// <param name="name">Name of the schema to remove</param>
/// <returns>True if the schema was removed, false if it wasn't found</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool RemoveResource(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_resource_schema_remove((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Clear all resource schemas from the registry.
/// </summary>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static void ClearResources()
{
CheckAndDropResult(Internal.API.regorus_resource_schema_clear());
}
/// <summary>
/// Register an effect schema from JSON with a given name.
/// </summary>
/// <param name="name">Name to register the schema under</param>
/// <param name="schemaJson">JSON string representing the schema</param>
/// <exception cref="Exception">Thrown when schema registration fails</exception>
public static void RegisterEffect(string name, string schemaJson)
{
Utf8Marshaller.WithUtf8(name, namePtr =>
{
Utf8Marshaller.WithUtf8(schemaJson, schemaPtr =>
{
unsafe
{
CheckAndDropResult(Internal.API.regorus_effect_schema_register((byte*)namePtr, (byte*)schemaPtr));
}
});
});
}
/// <summary>
/// Check if an effect schema with the given name exists.
/// </summary>
/// <param name="name">Name of the schema to check</param>
/// <returns>True if the schema exists, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool ContainsEffect(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_effect_schema_contains((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Get the number of registered effect schemas.
/// </summary>
/// <returns>The number of registered effect schemas</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static long EffectCount
{
get
{
var result = Internal.API.regorus_effect_schema_len();
return GetIntResult(result);
}
}
/// <summary>
/// Check if the effect schema registry is empty.
/// </summary>
/// <returns>True if the registry is empty, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool IsEffectRegistryEmpty
{
get
{
var result = Internal.API.regorus_effect_schema_is_empty();
return GetBoolResult(result);
}
}
/// <summary>
/// List all registered effect schema names.
/// </summary>
/// <returns>JSON array of schema names</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static string ListEffectNames()
{
return CheckAndDropResult(Internal.API.regorus_effect_schema_list_names()) ?? "[]";
}
/// <summary>
/// Remove an effect schema by name.
/// </summary>
/// <param name="name">Name of the schema to remove</param>
/// <returns>True if the schema was removed, false if it wasn't found</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool RemoveEffect(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_effect_schema_remove((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Clear all effect schemas from the registry.
/// </summary>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static void ClearEffects()
{
CheckAndDropResult(Internal.API.regorus_effect_schema_clear());
}
private static string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
private static string? CheckAndDropResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type switch
{
Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output),
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Internal.RegorusDataType.Integer => result.int_value.ToString(),
Internal.RegorusDataType.None => null,
_ => StringFromUTF8((IntPtr)result.output)
};
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static bool GetBoolResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static long GetIntResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
}
}

View File

@@ -0,0 +1,192 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Provides static methods for managing the global target registry.
/// Targets define resource types and their associated schemas for Azure Policy evaluation.
/// </summary>
public static unsafe class TargetRegistry
{
/// <summary>
/// Register a target from JSON definition.
/// The target JSON should follow the target schema format.
/// Once registered, the target can be referenced in Rego policies using `__target__` rules.
/// </summary>
/// <param name="targetJson">JSON encoded target definition</param>
/// <exception cref="Exception">Thrown when target registration fails</exception>
public static void RegisterFromJson(string targetJson)
{
Utf8Marshaller.WithUtf8(targetJson, targetPtr =>
{
unsafe
{
CheckAndDropResult(Internal.API.regorus_register_target_from_json((byte*)targetPtr));
}
});
}
/// <summary>
/// Check if a target is registered.
/// </summary>
/// <param name="name">Name of the target to check</param>
/// <returns>True if the target is registered, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool Contains(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_target_registry_contains((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Get a list of all registered target names.
/// </summary>
/// <returns>JSON array of target names</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static string ListNames()
{
return CheckAndDropResult(Internal.API.regorus_target_registry_list_names()) ?? "[]";
}
/// <summary>
/// Remove a target from the registry by name.
/// </summary>
/// <param name="name">The target name to remove</param>
/// <returns>True if the target was removed, false if it wasn't found</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool Remove(string name)
{
return Utf8Marshaller.WithUtf8(name, namePtr =>
{
unsafe
{
var result = Internal.API.regorus_target_registry_remove((byte*)namePtr);
return GetBoolResult(result);
}
});
}
/// <summary>
/// Clear all targets from the registry.
/// </summary>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static void Clear()
{
CheckAndDropResult(Internal.API.regorus_target_registry_clear());
}
/// <summary>
/// Get the number of registered targets.
/// </summary>
/// <returns>The number of registered targets</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static long Count
{
get
{
var result = Internal.API.regorus_target_registry_len();
return GetIntResult(result);
}
}
/// <summary>
/// Check if the target registry is empty.
/// </summary>
/// <returns>True if the registry is empty, false otherwise</returns>
/// <exception cref="Exception">Thrown when the operation fails</exception>
public static bool IsEmpty
{
get
{
var result = Internal.API.regorus_target_registry_is_empty();
return GetBoolResult(result);
}
}
private static string? StringFromUTF8(IntPtr ptr)
{
#if NETSTANDARD2_1
return System.Runtime.InteropServices.Marshal.PtrToStringUTF8(ptr);
#else
int len = 0;
while (System.Runtime.InteropServices.Marshal.ReadByte(ptr, len) != 0) { ++len; }
byte[] buffer = new byte[len];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
#endif
}
private static string? CheckAndDropResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type switch
{
Internal.RegorusDataType.String => StringFromUTF8((IntPtr)result.output),
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
Internal.RegorusDataType.Integer => result.int_value.ToString(),
Internal.RegorusDataType.None => null,
_ => StringFromUTF8((IntPtr)result.output)
};
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static bool GetBoolResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
private static long GetIntResult(Internal.RegorusResult result)
{
try
{
if (result.status != Internal.RegorusStatus.Ok)
{
var message = StringFromUTF8((IntPtr)result.error_message);
throw new Exception(message ?? "Unknown error occurred");
}
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
}
finally
{
Internal.API.regorus_result_drop(result);
}
}
}
}

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