Compare commits

...

42 Commits

Author SHA1 Message Date
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
81 changed files with 1486 additions and 747 deletions

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

@@ -0,0 +1,33 @@
name: tests/release-extensions
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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

@@ -30,6 +30,8 @@ jobs:
run: cargo test -r --doc
- name: Run tests
run: cargo test -r
- name: Run example
run: cargo run --example regorus -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
- name: Run tests (ACI)
run: cargo test -r --test aci
- name: Run tests (KATA)

View File

@@ -18,7 +18,6 @@ jobs:
- uses: actions/setup-go@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build ffi
@@ -29,5 +28,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

@@ -33,5 +33,5 @@ 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
java -Djava.library.path=target/release -cp target/regorus-java-0.2.2.jar:. Test
working-directory: ./bindings/java

View File

@@ -18,7 +18,9 @@ jobs:
- 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 +29,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

@@ -28,5 +28,7 @@ jobs:
cd bindings/wasm
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

4
.gitignore vendored
View File

@@ -25,4 +25,6 @@ worktrees/
# Generated C, C++ headers
bindings/ffi/regorus.h
bindings/ffi/regorus.ffi.hpp
bindings/ffi/regorus.ffi.hpp
bindings/*/target

View File

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

View File

@@ -1,18 +1,13 @@
[workspace]
members = [
"bindings/ffi",
"bindings/python",
"bindings/wasm",
"bindings/java",
"bindings/ruby/ext/regorusrb",
"tests/ensure_no_std",
]
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.2.4"
version = "0.4.0"
edition = "2021"
license-file = "LICENSE"
repository = "https://github.com/microsoft/regorus"
@@ -31,14 +26,13 @@ ast = []
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"]
crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha2"]
deprecated = []
hex = ["dep:data-encoding"]
http = []
glob = ["dep:wax"]
graph = []
jsonschema = ["dep:jsonschema"]
jwt = ["dep:jsonwebtoken", "dep:data-encoding", "dep:itertools"]
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
@@ -58,7 +52,6 @@ full-opa = [
"graph",
"hex",
"http",
"jwt",
"jsonschema",
"opa-runtime",
"regex",
@@ -67,7 +60,9 @@ full-opa = [
"time",
"uuid",
"urlquery",
"yaml"
"yaml",
#"rego-extensions"
]
# Features that can be used in no_std environments.
@@ -89,6 +84,9 @@ 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"]
@@ -100,35 +98,33 @@ serde_json = { version = "1.0.89", default-features = false, features = ["alloc"
lazy_static = { version = "1.4.0", default-features = false }
# Crypto
constant_time_eq = {version = "0.3.0", optional = true, default-features = false }
constant_time_eq = {version = "0.4.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.4.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.2" }
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.3" }
regex = {version = "1.10.2", optional = true, default-features = false }
semver = {version = "1.0.20", optional = true, default-features = false }
regex = {version = "1.11.1", optional = true, default-features = false }
semver = {version = "1.0.25", 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 }
url = { version = "2.5.4", optional = true }
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.29.0", default-features = false, optional = true }
chrono = { version = "0.4.40", optional = true }
chrono-tz = { version = "0.10.1", optional = true }
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 }
[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 }
prettydiff = { version = "0.8.0", default-features = false }
serde_yaml = "0.9.16"
test-generator = "0.3.1"
walkdir = "2.3.2"
@@ -138,6 +134,8 @@ anyhow = "1.0"
[profile.release]
debug = true
lto = true
codegen-units = 1
[[test]]
name="opa"

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.68.0](https://github.com/open-policy-agent/opa/releases/tag/v0.68.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.68.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
@@ -184,11 +183,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 +203,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 +275,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.68.0](https://github.com/open-policy-agent/opa/releases/tag/v0.68.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,7 +287,7 @@ $ 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:
The following test suites don't pass fully due to missing builtins:
- `cryptoparsersaprivatekeys`
- `cryptox509parseandverifycertificates`
- `cryptox509parsecertificaterequest`
@@ -299,8 +298,11 @@ The following test suites don't pass fully due to mising builtins:
- `graphql`
- `invalidkeyerror`
- `jsonpatch`
- `jwtbuiltins`
- `jwtdecodeverify`
- `jwtencodesign`
- `jwtencodesignheadererrors`
- `jwtencodesignpayloaderrors`
- `jwtencodesignraw`
- `jwtverifyhs256`
- `jwtverifyhs384`
@@ -322,6 +324,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).
Cryptographically insecure `sha1` related builtins are intentionally not supported to discourage their use.
### Grammar

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)

View File

@@ -27,7 +27,7 @@ char* file_to_string(const char* file) {
// 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);
return (uint8_t*) aligned_alloc(alignment, size);
}
void regorus_free(uint8_t* ptr) {
@@ -41,6 +41,11 @@ int main() {
RegorusResult r;
char* buffer = NULL;
// Turn on rego v0 since policy uses v0.
r = regorus_engine_set_rego_v0(engine, true);
if (r.status != RegorusStatusOk)
goto error;
// Load policies.
r = regorus_engine_add_policy(engine, "framework.rego", (buffer = file_to_string("../../../tests/aci/framework.rego")));
free(buffer);

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)

View File

@@ -6,6 +6,11 @@ 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 != RegorusStatusOk)
goto error;
// Load policies.
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
if (r.status != RegorusStatusOk)

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)

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

@@ -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

@@ -19,185 +19,202 @@ using System.Threading;
namespace Microsoft.WindowsAzure.Regorus.IaaS
{
public class RegorusPolicyEngine : ICloneable, IDisposable
{
unsafe private RegorusFFI.RegorusEngine* E;
public RegorusPolicyEngine()
{
unsafe
{
E = RegorusFFI.API.regorus_engine_new();
}
}
public class RegorusPolicyEngine : ICloneable, IDisposable
{
unsafe private RegorusFFI.RegorusEngine* E;
public RegorusPolicyEngine()
{
unsafe
{
E = RegorusFFI.API.regorus_engine_new();
}
}
public void Dispose()
{
unsafe
public void Dispose()
{
unsafe
{
if (E != null)
{
RegorusFFI.API.regorus_engine_drop(E);
// to avoid Dispose() being called multiple times by mistake.
E = null;
}
}
}
public object Clone()
{
var clone = (RegorusPolicyEngine)this.MemberwiseClone();
unsafe
{
clone.E = RegorusFFI.API.regorus_engine_clone(E);
}
return clone;
}
byte[] NullTerminatedUTF8Bytes(string s)
{
return Encoding.UTF8.GetBytes(s + char.MinValue);
}
public void SetRegoV0(bool enable)
{
if (E != null)
unsafe
{
RegorusFFI.API.regorus_engine_drop(E);
// to avoid Dispose() being called multiple times by mistake.
E = null;
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_rego_v0(E, enable));
}
}
}
public void AddPolicy(string path, string rego)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
var regoBytes = NullTerminatedUTF8Bytes(rego);
public object Clone()
{
var clone = (RegorusPolicyEngine)this.MemberwiseClone();
unsafe
{
clone.E = RegorusFFI.API.regorus_engine_clone(E);
}
return clone;
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
fixed (byte* regoPtr = regoBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
}
}
}
}
public void AddPolicyFromFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
public void AddPolicy(string path, string rego)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
var regoBytes = Encoding.UTF8.GetBytes(rego);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
fixed(byte* regoPtr = regoBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
}
}
}
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
}
}
}
public void AddPolicyFromFile(string path)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
}
}
}
public void AddPolicyFromPath(string path)
{
if (!Directory.Exists(path))
{
return;
}
public void AddPolicyFromPath(string path)
{
if (!Directory.Exists(path))
{
return;
}
string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories);
foreach (string file in regoFiles)
{
AddPolicyFromFile(file);
}
}
string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories);
foreach (string file in regoFiles)
{
AddPolicyFromFile(file);
}
}
public void AddDataJson(string data)
{
var dataBytes = Encoding.UTF8.GetBytes(data);
unsafe
{
fixed (byte* dataPtr = dataBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
public void AddDataJson(string data)
{
var dataBytes = NullTerminatedUTF8Bytes(data);
}
}
}
public void AddDataFromJsonFile(string path)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
unsafe
{
fixed (byte* dataPtr = dataBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
}
}
}
}
}
}
public void SetInputJson(string input)
{
var inputBytes = Encoding.UTF8.GetBytes(input);
unsafe
{
fixed (byte* inputPtr = inputBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
public void AddDataFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
}
}
}
public void SetInputFromJsonFile(string path)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
}
}
}
public string EvalQuery(string query)
{
var queryBytes = Encoding.UTF8.GetBytes(query);
}
}
}
var resultJson = "";
unsafe
{
fixed (byte* queryPtr = queryBytes)
{
var result = RegorusFFI.API.regorus_engine_eval_query(E, queryPtr);
if (result.status == RegorusFFI.RegorusStatus.RegorusStatusOk) {
if (result.output != null) {
resultJson = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.output);
}
RegorusFFI.API.regorus_result_drop(result);
} else {
CheckAndDropResult(result);
}
}
}
if (resultJson != null) {
return resultJson;
} else {
return "";
}
}
void CheckAndDropResult(RegorusFFI.RegorusResult result)
{
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
unsafe {
var message = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.error_message);
var ex = new Exception(message);
RegorusFFI.API.regorus_result_drop(result);
throw ex;
}
}
RegorusFFI.API.regorus_result_drop(result);
}
public void SetInputJson(string input)
{
var inputBytes = NullTerminatedUTF8Bytes(input);
unsafe
{
fixed (byte* inputPtr = inputBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
}
}
}
public void SetInputFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
}
}
}
public string EvalQuery(string query)
{
var queryBytes = NullTerminatedUTF8Bytes(query);
var resultJson = "";
unsafe
{
fixed (byte* queryPtr = queryBytes)
{
var result = RegorusFFI.API.regorus_engine_eval_query(E, queryPtr);
if (result.status == RegorusFFI.RegorusStatus.RegorusStatusOk)
{
if (result.output != null)
{
resultJson = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.output);
}
RegorusFFI.API.regorus_result_drop(result);
}
else
{
CheckAndDropResult(result);
}
}
}
if (resultJson != null)
{
return resultJson;
}
else
{
return "";
}
}
void CheckAndDropResult(RegorusFFI.RegorusResult result)
{
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk)
{
unsafe
{
var message = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.error_message);
var ex = new Exception(message);
RegorusFFI.API.regorus_result_drop(result);
throw ex;
}
}
RegorusFFI.API.regorus_result_drop(result);
}
}
}

View File

@@ -4,7 +4,7 @@
<Exec Command="cargo build -r --manifest-path ../../ffi/Cargo.toml" />
<Copy SourceFiles="../../ffi/RegorusFFI.g.cs" DestinationFolder="." />
<ItemGroup>
<RegorusDylib Include="..\..\..\target\release\*regorus_ffi*" />
<RegorusDylib Include="..\..\ffi\target\release\*regorus_ffi*" />
</ItemGroup>
<Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
</Target>

View File

@@ -23,6 +23,7 @@ var w = new Stopwatch();
w.Restart();
var engine = new Regorus.Engine();
engine.SetRegoV0(true);
w.Stop();
var newEngineTicks = w.ElapsedTicks;

View File

@@ -2,219 +2,235 @@ using System.Text;
namespace Regorus
{
public class Exception : System.Exception
{
public Exception(string? message) : base(message) {}
}
public class Engine : ICloneable
{
unsafe private RegorusFFI.RegorusEngine* E;
public Engine()
public class Exception : System.Exception
{
unsafe
{
E = RegorusFFI.API.regorus_engine_new();
}
public Exception(string? message) : base(message) { }
}
public object Clone()
public class Engine : ICloneable
{
var clone = (Engine)this.MemberwiseClone();
unsafe
{
clone.E = RegorusFFI.API.regorus_engine_clone(E);
}
return clone;
}
public string AddPolicy(string path, string rego)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
var regoBytes = Encoding.UTF8.GetBytes(rego);
unsafe
{
fixed (byte* pathPtr = pathBytes)
unsafe private RegorusFFI.RegorusEngine* E;
public Engine()
{
fixed(byte* regoPtr = regoBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
unsafe
{
E = RegorusFFI.API.regorus_engine_new();
}
}
}
}
public string AddPolicyFromFile(string path)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
public object Clone()
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
var clone = (Engine)this.MemberwiseClone();
unsafe
{
clone.E = RegorusFFI.API.regorus_engine_clone(E);
}
return clone;
}
}
}
public void AddDataJson(string data)
{
var dataBytes = Encoding.UTF8.GetBytes(data);
byte[] NullTerminatedUTF8Bytes(string s)
{
return Encoding.UTF8.GetBytes(s + char.MinValue);
}
unsafe
{
fixed (byte* dataPtr = dataBytes)
public string AddPolicy(string path, string rego)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
var pathBytes = NullTerminatedUTF8Bytes(path);
var regoBytes = NullTerminatedUTF8Bytes(rego);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
fixed (byte* regoPtr = regoBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
}
}
}
}
}
public void AddDataFromJsonFile(string path)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
public void SetRegoV0(bool enable)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
}
}
}
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_rego_v0(E, enable));
}
}
public void SetInputJson(string input)
{
var inputBytes = Encoding.UTF8.GetBytes(input);
unsafe
{
fixed (byte* inputPtr = inputBytes)
public string AddPolicyFromFile(string path)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
var pathBytes = NullTerminatedUTF8Bytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
}
}
}
}
}
public void SetInputFromJsonFile(string path)
{
var pathBytes = Encoding.UTF8.GetBytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
public void AddDataJson(string data)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
var dataBytes = NullTerminatedUTF8Bytes(data);
unsafe
{
fixed (byte* dataPtr = dataBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
}
}
}
}
}
public string EvalQuery(string query)
{
var queryBytes = Encoding.UTF8.GetBytes(query);
unsafe
{
fixed (byte* queryPtr = queryBytes)
public void AddDataFromJsonFile(string path)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
var pathBytes = NullTerminatedUTF8Bytes(path);
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
}
}
}
}
}
public string EvalRule(string rule)
{
var ruleBytes = Encoding.UTF8.GetBytes(rule);
unsafe
{
fixed (byte* rulePtr = ruleBytes)
public void SetInputJson(string input)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr));
var inputBytes = NullTerminatedUTF8Bytes(input);
unsafe
{
fixed (byte* inputPtr = inputBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
}
}
}
}
}
public void SetEnableCoverage(bool enable)
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_enable_coverage(E, enable));
}
}
public void ClearCoverageData()
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_clear_coverage_data(E));
}
}
public string GetCoverageReport()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report(E));
}
}
public string GetCoverageReportPretty()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report_pretty(E));
}
}
public void SetGatherPrints(bool enable)
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_gather_prints(E, enable));
}
}
public string TakePrints()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_take_prints(E));
}
}
~Engine()
{
unsafe
{
RegorusFFI.API.regorus_engine_drop(E);
}
}
public void SetInputFromJsonFile(string path)
{
var pathBytes = NullTerminatedUTF8Bytes(path);
string CheckAndDropResult(RegorusFFI.RegorusResult result)
{
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
unsafe {
var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message);
var ex = new Exception(message);
RegorusFFI.API.regorus_result_drop(result);
throw ex;
unsafe
{
fixed (byte* pathPtr = pathBytes)
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
}
}
}
}
var resultString = "";
unsafe
{
if (result.output is not null) {
resultString = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output);
public string EvalQuery(string query)
{
var queryBytes = NullTerminatedUTF8Bytes(query);
unsafe
{
fixed (byte* queryPtr = queryBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
}
}
}
RegorusFFI.API.regorus_result_drop(result);
}
return resultString;
}
}
public string EvalRule(string rule)
{
var ruleBytes = NullTerminatedUTF8Bytes(rule);
unsafe
{
fixed (byte* rulePtr = ruleBytes)
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr));
}
}
}
public void SetEnableCoverage(bool enable)
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_enable_coverage(E, enable));
}
}
public void ClearCoverageData()
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_clear_coverage_data(E));
}
}
public string GetCoverageReport()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report(E));
}
}
public string GetCoverageReportPretty()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report_pretty(E));
}
}
public void SetGatherPrints(bool enable)
{
unsafe
{
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_gather_prints(E, enable));
}
}
public string TakePrints()
{
unsafe
{
return CheckAndDropResult(RegorusFFI.API.regorus_engine_take_prints(E));
}
}
~Engine()
{
unsafe
{
RegorusFFI.API.regorus_engine_drop(E);
}
}
string CheckAndDropResult(RegorusFFI.RegorusResult result)
{
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk)
{
unsafe
{
var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message);
var ex = new Exception(message);
RegorusFFI.API.regorus_result_drop(result);
throw ex;
}
}
var resultString = "";
unsafe
{
if (result.output is not null)
{
resultString = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output);
}
RegorusFFI.API.regorus_result_drop(result);
}
return resultString;
}
}
}

View File

@@ -4,7 +4,7 @@
<Exec Command="cargo build -r --manifest-path ../../ffi/Cargo.toml" />
<Copy SourceFiles="../../ffi/RegorusFFI.g.cs" DestinationFolder="." />
<ItemGroup>
<RegorusDylib Include="..\..\..\target\release\*regorus_ffi*" />
<RegorusDylib Include="..\..\ffi\target\release\*regorus_ffi*" />
</ItemGroup>
<Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
</Target>

View File

@@ -1,3 +1,5 @@
[workspace]
[package]
name = "regorus-ffi"
version = "0.2.2"
@@ -10,7 +12,7 @@ crate-type = ["cdylib", "staticlib"]
[dependencies]
anyhow = "1.0"
regorus = { path = "../..", default-features = false }
serde_json = "1.0.113"
serde_json = "1.0.140"
[features]
default = ["ast", "std", "coverage", "regorus/arc", "regorus/full-opa"]
@@ -20,5 +22,5 @@ coverage = ["regorus/coverage"]
custom_allocator = []
[build-dependencies]
cbindgen = "0.27.0"
cbindgen = "0.28.0"
csbindgen = "=1.9.3"

View File

@@ -222,7 +222,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
/// Clear policy data.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data
#[no_mangle]
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
@@ -233,7 +233,7 @@ pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> Regor
/// Set input.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_input
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_input
/// * `input`: JSON encoded value to be used as input to query.
#[no_mangle]
pub extern "C" fn regorus_engine_set_input_json(
@@ -264,7 +264,7 @@ pub extern "C" fn regorus_engine_set_input_from_json_file(
/// Evaluate query.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_query
/// * `query`: Rego expression to be evaluate.
#[no_mangle]
pub extern "C" fn regorus_engine_eval_query(
@@ -289,7 +289,7 @@ pub extern "C" fn regorus_engine_eval_query(
/// Evaluate specified rule.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_rule
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule
/// * `rule`: Path to the rule.
#[no_mangle]
pub extern "C" fn regorus_engine_eval_rule(
@@ -314,7 +314,7 @@ pub extern "C" fn regorus_engine_eval_rule(
/// Enable/disable coverage.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_enable_coverage
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage
/// * `enable`: Whether to enable or disable coverage.
#[no_mangle]
#[cfg(feature = "coverage")]
@@ -330,7 +330,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
/// Get coverage report.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.get_coverage_report
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult {
@@ -375,7 +375,7 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
/// Clear coverage data.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_coverage_data
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult {
@@ -387,7 +387,7 @@ pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine)
/// Whether to gather output of print statements.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_gather_prints
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints
/// * `enable`: Whether to enable or disable gathering print statements.
#[no_mangle]
pub extern "C" fn regorus_engine_set_gather_prints(
@@ -402,7 +402,7 @@ pub extern "C" fn regorus_engine_set_gather_prints(
/// Take all the gathered print statements.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.take_prints
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints
#[no_mangle]
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
let output = || -> Result<String> {
@@ -437,6 +437,28 @@ pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) ->
}
}
/// Enable/disable rego v1.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_rego_v0
#[no_mangle]
pub extern "C" fn regorus_engine_set_rego_v0(
engine: *mut RegorusEngine,
enable: bool,
) -> RegorusResult {
let output = || -> Result<()> {
to_ref(&engine)?.engine.set_rego_v0(enable);
Ok(())
}();
match output {
Ok(()) => RegorusResult {
status: RegorusStatus::RegorusStatusOk,
output: std::ptr::null_mut(),
error_message: std::ptr::null_mut(),
},
Err(e) => to_regorus_result(Err(e)),
}
}
#[cfg(feature = "custom_allocator")]
extern "C" {
fn regorus_aligned_alloc(alignment: usize, size: usize) -> *mut u8;

View File

@@ -16,8 +16,11 @@ func main() {
// Create new engine
engine := regorus.NewEngine()
defer engine.Close()
elapsed1 := time.Since(t)
engine.SetRegoV0(true)
elapsed1 := time.Since(t)
t = time.Now()
// Add policies and data.
policies := []string{

View File

@@ -1,6 +1,6 @@
package regorus
// #cgo LDFLAGS: -L ../../../../target/release -lregorus_ffi
// #cgo LDFLAGS: -L ../../../ffi/target/release -lregorus_ffi
// #include "../../../ffi/regorus.h"
import "C"
import (
@@ -28,6 +28,17 @@ func (e *Engine) Clone() *Engine {
return c
}
func (e *Engine) SetRegoV0(enable bool) error {
result := C.regorus_engine_set_rego_v0(e.e, C.bool(enable))
defer C.regorus_result_drop(result)
if result.status != C.RegorusStatusOk {
return fmt.Errorf("%s", C.GoString(result.error_message))
}
return nil
}
func (e *Engine) AddPolicy(path string, rego string) (string, error) {
path_c := C.CString(path)
defer C.free(unsafe.Pointer(path_c))
@@ -73,7 +84,6 @@ func (e *Engine) GetPolicies() (string, error) {
return C.GoString(result.output), nil
}
func (e *Engine) AddDataJson(data string) error {
data_c := C.CString(data)
defer C.free(unsafe.Pointer(data_c))

View File

@@ -1,6 +1,8 @@
[workspace]
[package]
name = "regorus-java"
version = "0.2.2"
version = "0.3.0"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/java"
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"

View File

@@ -23,7 +23,7 @@ Afterwards, you can build native library for that target using:
$ cargo build --release --target aarch64-apple-darwin
```
You will then have a native library at `../../target/aarch64-apple-darwin/release/libregorus_java.dylib` depending on your target.
You will then have a native library at `target/aarch64-apple-darwin/release/libregorus_java.dylib` depending on your target.
You then need to build Java bindings using:
```bash

View File

@@ -4,39 +4,45 @@
import com.microsoft.regorus.Engine;
public class Test {
public static void main(String[] args) {
try (Engine engine = new Engine()) {
String pkg = engine.addPolicy(
"hello.rego",
"package test\nx=1\nmessage = concat(\", \", [input.message, data.message])"
"hello.rego",
"package test\nx=1\nmessage = concat(\", \", [input.message, data.message])"
);
System.out.println("Loaded package " + pkg);
System.out.println("Loaded package " + pkg);
engine.addDataJson("{\"message\":\"World!\"}");
engine.setInputJson("{\"message\":\"Hello\"}");
// Evaluate query.
String resJson = engine.evalQuery("data.test.message");
// Evaluate query.
String resJson = engine.evalQuery("data.test.message");
System.out.println(resJson);
// Enable coverage.
engine.setEnableCoverage(true);
// Enable coverage.
engine.setEnableCoverage(true);
// Evaluate rule.
String valueJson = engine.evalRule("data.test.message");
// Evaluate rule.
String valueJson = engine.evalRule("data.test.message");
System.out.println(valueJson);
String coverageJson = engine.getCoverageReport();
System.out.println(coverageJson);
String coverageJson = engine.getCoverageReport();
System.out.println(coverageJson);
System.out.println(engine.getCoverageReportPretty());
System.out.println(engine.getCoverageReportPretty());
String packagesJson = engine.getPackages();
System.out.println(packagesJson);
String packagesJson = engine.getPackages();
System.out.println(packagesJson);
String policiesJson = engine.getPolicies();
System.out.println(policiesJson);
String policiesJson = engine.getPolicies();
System.out.println(policiesJson);
engine.setRegoV0(true);
engine.addPolicy(
"world.rego",
"package world\nx { true }"
);
}
}
}

View File

@@ -28,6 +28,20 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
Box::into_raw(Box::new(c)) as jlong
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetRegoV0(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
enable: bool,
) {
let _ = throw_err(env, |_env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.set_rego_v0(enable);
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
env: JNIEnv,

View File

@@ -8,10 +8,8 @@ package com.microsoft.regorus;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.atomic.AtomicReference;
/**
* Regorus Engine.
@@ -23,6 +21,7 @@ public class Engine implements AutoCloseable, Cloneable {
// if you update the native API.
private static native long nativeNewEngine();
private static native long nativeClone(long enginePtr);
private static native void nativeSetRegoV0(long enginePtr, boolean enable);
private static native String nativeAddPolicy(long enginePtr, String path, String rego);
private static native String nativeAddPolicyFromFile(long enginePtr, String path);
private static native String nativeGetPackages(long enginePtr);
@@ -55,7 +54,7 @@ public class Engine implements AutoCloseable, Cloneable {
Engine(long ptr) {
enginePtr = ptr;
enginePtr = ptr;
}
/**
@@ -65,6 +64,16 @@ public class Engine implements AutoCloseable, Cloneable {
return new Engine(nativeClone(enginePtr));
}
/**
* Enable/disable Rego v0.
*
* @param enable Whether to enable v0 or not.
*
*/
public void setRegoV0(boolean enable) {
nativeSetRegoV0(enginePtr, enable);
}
/**
* Adds an inline Rego policy.
*

View File

@@ -1,6 +1,8 @@
[workspace]
[package]
name = "regoruspy"
version = "0.2.2"
version = "0.3.0"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/python"
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -18,8 +20,8 @@ coverage = ["regorus/coverage"]
[dependencies]
anyhow = "1.0"
ordered-float = "4.2.0"
pyo3 = {version = "0.22.0", features = ["anyhow", "extension-module"] }
ordered-float = "5.0.0"
pyo3 = {version = "0.24.0", features = ["anyhow", "extension-module"] }
regorus = { path = "../..", default-features = false, features = ["arc"] }
serde_json = "1.0.112"
serde_json = "1.0.140"

View File

@@ -4,6 +4,7 @@ use anyhow::{anyhow, Result};
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pyo3::types::*;
use pyo3::IntoPyObjectExt;
use std::collections::{BTreeMap, BTreeSet};
@@ -94,7 +95,7 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
let mut map = BTreeMap::new();
let keys = pmap.keys()?;
let values = pmap.values()?;
for i in 0..keys.len()? {
for i in 0..keys.len() {
let key = keys.get_item(i)?;
let value = values.get_item(i)?;
map.insert(from(&key)?, from(&value)?);
@@ -108,49 +109,53 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
}
fn to(mut v: Value, py: Python<'_>) -> Result<PyObject> {
Ok(match v {
Value::Null => None::<u64>.to_object(py),
let obj = match v {
Value::Null => None::<u64>.into_bound_py_any(py),
// TODO: Revisit this mapping
Value::Undefined => None::<u64>.to_object(py),
Value::Undefined => None::<u64>.into_bound_py_any(py),
Value::Bool(b) => b.to_object(py),
Value::String(s) => s.to_object(py),
Value::Bool(b) => b.into_bound_py_any(py),
Value::String(s) => s.into_bound_py_any(py),
Value::Number(_) => {
if let Ok(f) = v.as_f64() {
f.to_object(py)
f.into_bound_py_any(py)
} else if let Ok(u) = v.as_u64() {
u.to_object(py)
u.into_bound_py_any(py)
} else {
v.as_i64()?.to_object(py)
v.as_i64()?.into_bound_py_any(py)
}
}
Value::Array(_) => {
let list = PyList::empty_bound(py);
let list = PyList::empty(py);
for v in std::mem::take(v.as_array_mut()?) {
list.append(to(v, py)?)?;
}
list.into()
list.into_bound_py_any(py)
}
Value::Set(_) => {
let set = PySet::empty_bound(py)?;
let set = PySet::empty(py)?;
for v in std::mem::take(v.as_set_mut()?) {
set.add(to(v, py)?)?;
}
set.into()
set.into_bound_py_any(py)
}
Value::Object(_) => {
let dict = PyDict::new_bound(py);
let dict = PyDict::new(py);
for (k, v) in std::mem::take(v.as_object_mut()?) {
dict.set_item(to(k, py)?, to(v, py)?)?;
}
dict.into()
dict.into_bound_py_any(py)
}
})
};
match obj {
Ok(v) => Ok(v.into()),
Err(e) => Err(anyhow!("{e}")),
}
}
#[pymethods]
@@ -163,6 +168,15 @@ impl Engine {
}
}
/// Turn on rego v0.
///
/// Regorus now defaults to v1.
///
/// * `enable`: Whether to enable/disable v0.
pub fn set_rego_v0(&mut self, enable: bool) {
self.engine.set_rego_v0(enable)
}
/// Add a policy
///
/// The policy is parsed into AST.
@@ -261,30 +275,30 @@ impl Engine {
pub fn eval_query(&mut self, query: String, py: Python<'_>) -> Result<PyObject> {
let results = self.engine.eval_query(query, false)?;
let rlist = PyList::empty_bound(py);
let rlist = PyList::empty(py);
for result in results.result.into_iter() {
let rdict = PyDict::new_bound(py);
let rdict = PyDict::new(py);
let elist = PyList::empty_bound(py);
let elist = PyList::empty(py);
for expr in result.expressions.into_iter() {
let edict = PyDict::new_bound(py);
edict.set_item("value".to_object(py), to(expr.value, py)?)?;
edict.set_item("text".to_object(py), expr.text.as_ref().to_object(py))?;
let edict = PyDict::new(py);
edict.set_item("value", to(expr.value, py)?)?;
edict.set_item("text", expr.text.as_ref())?;
let ldict = PyDict::new_bound(py);
ldict.set_item("row".to_object(py), expr.location.row.to_object(py))?;
ldict.set_item("col".to_object(py), expr.location.col.to_object(py))?;
let ldict = PyDict::new(py);
ldict.set_item("row", expr.location.row)?;
ldict.set_item("col", expr.location.col)?;
edict.set_item("location".to_object(py), ldict)?;
edict.set_item("location", ldict)?;
elist.append(edict)?;
}
rdict.set_item("expressions".to_object(py), elist)?;
rdict.set_item("bindings".to_object(py), to(result.bindings, py)?)?;
rdict.set_item("expressions", elist)?;
rdict.set_item("bindings", to(result.bindings, py)?)?;
rlist.append(rdict)?;
}
let dict = PyDict::new_bound(py);
dict.set_item("result".to_object(py), rlist)?;
let dict = PyDict::new(py);
dict.set_item("result", rlist)?;
Ok(dict.into())
}

View File

@@ -6,6 +6,8 @@ import regorus
# Create engine
engine = regorus.Engine()
engine.set_rego_v0(True)
# Load policies
pkg = engine.add_policy_from_file('../../tests/aci/framework.rego')
print(' Loaded package %s' % pkg)

View File

@@ -3,7 +3,7 @@ require:
- rubocop-rake
AllCops:
TargetRubyVersion: 3.0
TargetRubyVersion: 3.4
NewCops: enable
Layout/LineLength:

View File

@@ -1 +1 @@
ruby 3.3.1
ruby 3.4.2

View File

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

View File

@@ -1,65 +1,68 @@
PATH
remote: .
specs:
regorusrb (0.2.1)
rb_sys (~> 0.9.97)
regorusrb (0.3.0)
rb_sys (~> 0.9.111)
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
json (2.7.2)
language_server-protocol (3.17.0.3)
minitest (5.24.1)
parallel (1.25.1)
parser (3.3.4.0)
json (2.10.2)
language_server-protocol (3.17.0.4)
lint_roller (1.1.0)
minitest (5.25.4)
parallel (1.26.3)
parser (3.3.7.1)
ast (~> 2.4.1)
racc
racc (1.8.0)
racc (1.8.1)
rainbow (3.1.1)
rake (13.2.1)
rake-compiler (1.2.7)
rake-compiler (1.2.9)
rake
rake-compiler-dock (1.5.1)
rb_sys (0.9.99)
regexp_parser (2.9.2)
rexml (3.3.6)
strscan
rubocop (1.65.0)
rake-compiler-dock (1.9.1)
rb_sys (0.9.111)
rake-compiler-dock (= 1.9.1)
regexp_parser (2.10.0)
rubocop (1.73.2)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.4, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.38.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.38.1)
parser (>= 3.3.1.0)
rubocop-minitest (0.35.1)
rubocop (>= 1.61, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rake (0.6.0)
rubocop (~> 1.0)
rubocop-minitest (0.37.1)
lint_roller (~> 1.1)
rubocop (>= 1.72.1, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-rake (0.7.1)
lint_roller (~> 1.1)
rubocop (>= 1.72.1)
ruby-progressbar (1.13.0)
strscan (3.1.0)
unicode-display_width (2.5.0)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
minitest (~> 5.23)
minitest (~> 5.25)
rake (~> 13.2)
rake-compiler
rake-compiler-dock
rake-compiler (~> 1.2)
rake-compiler-dock (~> 1.9)
regorusrb!
rubocop (~> 1.64)
rubocop-minitest
rubocop-rake
rubocop (~> 1.73)
rubocop-minitest (~> 0.37.1)
rubocop-rake (~> 0.7.1)
BUNDLED WITH
2.5.13
2.6.5

View File

@@ -43,6 +43,9 @@ require "regorus"
engine = Regorus::Engine.new
# Policy is old-style.
engine.set_rego_v0(true)
engine.add_policy_from_file('../../tests/aci/framework.rego')
engine.add_policy_from_file('../../tests/aci/api.rego')
engine.add_policy_from_file('../../tests/aci/policy.rego')

View File

@@ -1,7 +1,7 @@
[package]
name = "regorusrb"
version = "0.2.2"
edition = "2021"
version = "0.3.0"
edition = "2024"
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
publish = false
@@ -15,7 +15,7 @@ ast = ["regorus/ast"]
coverage = ["regorus/coverage"]
[dependencies]
magnus = { version = "0.6.4" }
magnus = { version = "0.7.1" }
regorus = { path = "../../../..", default-features = false, features = ["arc"] }
serde_json = "1.0.117"
serde_magnus = "0.8.1"
serde_json = "1.0.140"
serde_magnus = "0.9.0"

View File

@@ -1,4 +1,4 @@
use magnus::{exception::runtime_error, method, module, prelude::*, Error, Ruby};
use magnus::{Error, Ruby, exception::runtime_error, method, module, prelude::*};
use regorus::Engine as RegorusEngine;
use std::cell::RefCell;
use std::cmp::Ordering;
@@ -36,6 +36,11 @@ impl Engine {
}
}
fn set_rego_v0(&self, enable: bool) -> Result<(), Error> {
self.engine.borrow_mut().set_rego_v0(enable);
Ok(())
}
fn add_policy(&self, path: String, rego: String) -> Result<String, Error> {
self.engine
.borrow_mut()
@@ -297,6 +302,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
// defines <, <=, >, >=, and == based on <=>
engine_class.include_module(module::comparable())?;
// rego language configuration
engine_class.define_method("set_rego_v0", method!(Engine::set_rego_v0, 1))?;
// policy operations
engine_class.define_method("add_policy", method!(Engine::add_policy, 2))?;
engine_class.define_method(

View File

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

View File

@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
spec.summary = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
spec.homepage = "https://github.com/microsoft/regorus/blob/main/bindings/ruby"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.0.0"
spec.required_rubygems_version = ">= 3.3.11"
spec.required_ruby_version = ">= 3.4.2"
spec.required_rubygems_version = ">= 3.6.5"
spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
@@ -26,5 +26,5 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.extensions = ["ext/regorusrb/extconf.rb"]
spec.add_dependency "rb_sys", "~> 0.9.97"
spec.add_dependency "rb_sys", "~> 0.9.111"
end

View File

@@ -17,11 +17,11 @@ class TestRegorus < Minitest::Test
def example_policy
<<~REGO
package regorus_test
is_manager {
is_manager if {
input.name == data.managers[_]
}
is_employee {
is_employee if {
input.name == data.employees[_]
}
@@ -29,11 +29,11 @@ class TestRegorus < Minitest::Test
default is_manager_bool = false
default is_employee_bool = false
is_manager_bool {
is_manager_bool if {
is_manager
}
is_employee_bool {
is_employee_bool if {
is_employee
}
REGO

View File

@@ -0,0 +1,2 @@
[target.wasm32-unknown-unknown]
rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""]

View File

@@ -1,6 +1,8 @@
[workspace]
[package]
name = "regorusjs"
version = "0.2.2"
version = "0.3.0"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/wasm"
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -17,8 +19,17 @@ coverage = ["regorus/coverage"]
[dependencies]
regorus = { path = "../..", default-features = false, features = ["arc"] }
serde_json = "1.0.111"
wasm-bindgen = "0.2.90"
serde_json = "1.0.140"
wasm-bindgen = "0.2.100"
# Specify uuid as a mandatory dependency so as to enable `js` feature which is now required
# when targeting wasm32-unknown-unknown.
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng", "js"]}
# Enable wasm_js. See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
getrandom_for_jsonschema = { package = "getrandom", version = "0.2.15", features = ["std", "js"] }
getrandom = { version = "0.3.1", features = ["std", "wasm_js"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.40"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }

View File

@@ -45,6 +45,15 @@ impl Engine {
}
}
/// Turn on rego v0.
///
/// Regorus defaults to rego v1.
///
/// * `enable`: Whether to enable or disable rego v0.
pub fn setRegoV0(&mut self, enable: bool) {
self.engine.set_rego_v0(enable)
}
/// Add a policy
///
/// The policy is parsed into AST.
@@ -191,6 +200,7 @@ mod tests {
use wasm_bindgen_test::wasm_bindgen_test;
#[wasm_bindgen_test]
#[allow(dead_code)]
pub fn basic() -> Result<(), JsValue> {
let mut engine = crate::Engine::new();
engine.setEnableCoverage(true);

View File

@@ -61,9 +61,9 @@ in-expr ::= in-expr 'in' bool-expr
bool-expr ::= bool-expr bool-op or-expr
| or-expr
bool-op ::= '<' | '<=' | '==' | '>=' | '>' | '!='
or-expr ::= or-expr '|' and-expr
| and-expr
and-expr ::= and-expr '&' arith-expr
set-union-expr ::= set-union-expr '|' set-intersection-expr
| set-intersection-expr
set-intersection-expr ::= set-intersection-expr '&' arith-expr
| arith-expr
arith-expr ::= arith-expr ('+' | '-') mul-div-expr
| mul-div-expr

View File

@@ -42,7 +42,7 @@ fn rego_eval(
enable_tracing: bool,
non_strict: bool,
#[cfg(feature = "coverage")] coverage: bool,
v1: bool,
v0: bool,
) -> Result<()> {
// Create engine.
let mut engine = regorus::Engine::new();
@@ -52,7 +52,7 @@ fn rego_eval(
#[cfg(feature = "coverage")]
engine.set_enable_coverage(coverage);
engine.set_rego_v1(v1);
engine.set_rego_v0(v0);
// Load files from given bundles.
for dir in bundles.iter() {
@@ -238,9 +238,9 @@ enum RegorusCommand {
#[arg(long, short)]
coverage: bool,
/// Turn on rego.v1
/// Turn on Rego language v0.
#[arg(long)]
v1: bool,
v0: bool,
},
/// Tokenize a Rego policy.
@@ -282,7 +282,7 @@ fn main() -> Result<()> {
non_strict,
#[cfg(feature = "coverage")]
coverage,
v1,
v0,
} => rego_eval(
&bundles,
&data,
@@ -292,7 +292,7 @@ fn main() -> Result<()> {
non_strict,
#[cfg(feature = "coverage")]
coverage,
v1,
v0,
),
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
RegorusCommand::Parse { file } => rego_parse(file),

View File

@@ -2,22 +2,22 @@ package example
default allow := false # unless otherwise defined, allow is false
allow := true { # allow is true if...
allow := true if { # allow is true if...
count(violation) == 0 # there are zero violations.
}
violation[server.id] { # a server is in the violation set if...
violation[server.id] if { # a server is in the violation set if...
some server
public_server[server] # it exists in the 'public_server' set and...
server.protocols[_] == "http" # it contains the insecure "http" protocol.
}
violation[server.id] { # a server is in the violation set if...
violation[server.id] if { # a server is in the violation set if...
server := input.servers[_] # it exists in the input.servers collection and...
server.protocols[_] == "telnet" # it contains the "telnet" protocol.
}
public_server[server] { # a server exists in the public_server set if...
public_server[server]if { # a server exists in the public_server set if...
some i, j
server := input.servers[_] # it exists in the input.servers collection and...
server.ports[_] == input.ports[i].id # it references a port in the input.ports collection and...

View File

@@ -30,6 +30,11 @@ if [ -f Cargo.toml ]; then
cargo test -r --test aci
cargo test -r --test kata
# Ensure that all tests pass with extensions
cargo test -r --features rego-extensions
cargo test -r --test aci --features rego-extensions
cargo test -r --test kata --features rego-extensions
# Ensure that OPA conformance tests don't regress.
cargo test -r --features opa-testutil,serde_json/arbitrary_precision --test opa -- $(tr '\n' ' ' < tests/opa.passing)
cargo test -r --features opa-testutil,serde_json/arbitrary_precision,rego-extensions --test opa -- $(tr '\n' ' ' < tests/opa.passing)
fi

View File

@@ -10,8 +10,8 @@ use core::{cmp, fmt, ops::Deref};
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
pub enum BinOp {
And,
Or,
Intersection,
Union,
}
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -209,6 +209,13 @@ pub enum Expr {
value: Ref<Expr>,
collection: Ref<Expr>,
},
#[cfg(feature = "rego-extensions")]
OrExpr {
span: Span,
lhs: Ref<Expr>,
rhs: Ref<Expr>,
},
}
impl Expr {
@@ -232,6 +239,8 @@ impl Expr {
| ArithExpr { span, .. }
| AssignExpr { span, .. }
| Membership { span, .. } => span,
#[cfg(feature = "rego-extensions")]
OrExpr { span, .. } => span,
}
}
}

View File

@@ -11,18 +11,15 @@ use anyhow::{bail, Result};
use constant_time_eq::constant_time_eq;
use hmac::{Hmac, Mac};
use md5::{Digest, Md5};
use sha1::Sha1;
use sha2::{Sha256, Sha512};
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("crypto.hmac.equal", (hmac_equal_fixed_time, 2));
m.insert("crypto.hmac.md5", (hmac_md5, 2));
m.insert("crypto.hmac.sha1", (hmac_sha1, 2));
m.insert("crypto.hmac.sha256", (hmac_sha256, 2));
m.insert("crypto.hmac.sha512", (hmac_sha512, 2));
m.insert("crypto.md5", (crypto_md5, 1));
m.insert("crypto.sha1", (crypto_sha1, 1));
m.insert("crypto.sha256", (crypto_sha256, 1));
}
@@ -60,22 +57,6 @@ fn hmac_md5(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
Ok(Value::String(hex::encode(result.into_bytes()).into()))
}
fn hmac_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "crypto.hmac.sha1";
ensure_args_count(span, name, params, args, 2)?;
let x = ensure_string(name, &params[0], &args[0])?;
let key = ensure_string(name, &params[1], &args[1])?;
let mut hmac = Hmac::<Sha1>::new_from_slice(key.as_bytes())
.or_else(|_| bail!(span.error("failed to create hmac instance")))?;
hmac.update(x.as_bytes());
let result = hmac.finalize();
Ok(Value::String(hex::encode(result.into_bytes()).into()))
}
fn hmac_sha256(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "crypto.hmac.sha256";
ensure_args_count(span, name, params, args, 2)?;
@@ -122,20 +103,6 @@ fn crypto_md5(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
Ok(Value::String(hex::encode(result).into()))
}
fn crypto_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "crypto.sha1";
ensure_args_count(span, name, params, args, 1)?;
let x = ensure_string(name, &params[0], &args[0])?;
let mut h = Sha1::new();
h.update(x.as_bytes());
let result = h.finalize();
Ok(Value::String(hex::encode(result).into()))
}
fn crypto_sha256(
span: &Span,
params: &[Ref<Expr>],

View File

@@ -337,11 +337,11 @@ fn yaml_is_valid(
fn yaml_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "yaml.marshal";
ensure_args_count(span, name, params, args, 1)?;
Ok(Value::String(
serde_yaml::to_string(&args[0])
.with_context(|| span.error("could not serialize to yaml"))?
.into(),
))
let serialized = serde_yaml::to_string(&args[0])
.map_err(|err| span.error(&format!("could not serialize to yaml: {}", err)))?;
Ok(Value::String(serialized.into()))
}
#[cfg(feature = "yaml")]

View File

@@ -1,73 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_string};
use crate::*;
use crate::lexer::Span;
use crate::value::Value;
use itertools::Itertools;
use anyhow::{bail, Result};
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("io.jwt.decode", (jwt_decode, 1));
m.insert("io.jwt.decode_verify", (jwt_decode_verify, 2));
}
fn decode(span: &Span, jwt: String, strict: bool) -> Result<Value> {
let Some((Ok(header), Ok(payload), Ok(signature))) = jwt
.split('.')
.map(|p| data_encoding::BASE64URL_NOPAD.decode(p.as_bytes()))
.collect_tuple()
else {
if strict {
bail!(span.error("invalid jwt token"));
}
return Ok(Value::Undefined);
};
let header = String::from_utf8_lossy(&header).to_string();
let payload = String::from_utf8_lossy(&payload).to_string();
let signature = data_encoding::HEXLOWER_PERMISSIVE.encode(&signature);
let signature = Value::String(signature.into());
let header = Value::from_json_str(&header)?;
if header["enc"] != Value::Undefined {
bail!(span.error("JWT is a JWE object, which is not supported"));
}
if header["cty"] == "JWT".into() {
if payload.len() <= 2 || !payload.starts_with('"') || !payload.ends_with('"') {
bail!(span.error("invalid nested JWT"));
}
// Ignore ""
decode(span, payload[1..payload.len() - 1].to_string(), strict)
} else {
let payload = Value::from_json_str(&payload)?;
Ok(Value::from_array([header, payload, signature].into()))
}
}
fn jwt_decode(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "io.jwt.decode";
ensure_args_count(span, name, params, args, 1)?;
let jwt = ensure_string(name, &params[0], &args[0])?;
decode(span, jwt.to_string(), strict) //header, payload, signature, strict)
}
fn jwt_decode_verify(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "io.jwt.decode_verify";
ensure_args_count(span, name, params, args, 2)?;
Ok(Value::Undefined)
}

View File

@@ -18,8 +18,6 @@ mod glob;
mod graph;
#[cfg(feature = "http")]
mod http;
#[cfg(feature = "jwt")]
mod jwt;
pub mod numbers;
mod objects;
#[cfg(feature = "opa-runtime")]
@@ -83,8 +81,6 @@ lazy_static! {
//units::register(&mut m);
types::register(&mut m);
encoding::register(&mut m);
#[cfg(feature = "jwt")]
jwt::register(&mut m);
#[cfg(feature = "time")]
time::register(&mut m);

View File

@@ -12,7 +12,7 @@ use crate::*;
use anyhow::{bail, Result};
#[cfg(feature = "std")]
use rand::{thread_rng, Rng};
use rand::Rng;
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("abs", (abs, 1));
@@ -169,8 +169,7 @@ fn intn(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Res
Some(0) => Value::from(0u64),
Some(n) => {
// TODO: bounds checking; arbitrary precision
let mut rng = thread_rng();
let v = rng.gen_range(0..n);
let v = rand::rng().random_range(0..n);
Value::from(v)
}
_ => Value::Undefined,

View File

@@ -391,14 +391,14 @@ fn object_union_n(
}
#[cfg(feature = "jsonschema")]
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::JSONSchema> {
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::Validator> {
let schema_str = match arg {
Value::String(schema_str) => schema_str.as_ref().to_string(),
_ => arg.to_json_str()?,
};
if let Ok(schema) = serde_json::from_str(&schema_str) {
match jsonschema::JSONSchema::compile(&schema) {
match jsonschema::validator_for(&schema) {
Ok(schema) => return Ok(schema),
Err(e) => bail!(e.to_string()),
}
@@ -439,16 +439,14 @@ fn json_match_schema(
ensure_args_count(span, name, params, args, 2)?;
// The following is expected to succeed.
let document: serde_json::Value = serde_json::from_str(&args[0].to_json_str()?)?;
let document: serde_json::Value = serde_json::from_str(&args[0].to_json_str()?)
.map_err(|err| span.error(&format!("Failed to parse JSON: {}", err)))?;
Ok(Value::from_array(
match compile_json_schema(&params[1], &args[1]) {
Ok(schema) => match schema.validate(&document) {
Ok(_) => [Value::Bool(true), Value::Null],
Err(e) => [
Value::Bool(false),
Value::from_array(e.map(|e| Value::String(e.to_string().into())).collect()),
],
Err(e) => [Value::Bool(false), Value::from(e.to_string())],
},
Err(e) if strict => bail!(params[1]
.span()

View File

@@ -67,8 +67,6 @@ fn opa_runtime(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
"hex",
#[cfg(feature = "http")]
"http",
#[cfg(feature = "jwt")]
"jwt",
#[cfg(feature = "jsonschema")]
"jsonschema",
#[cfg(feature = "opa-runtime")]

View File

@@ -25,7 +25,11 @@ fn sleep(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
let dur = time::compat::parse_duration(val.as_ref())
.map_err(|e| params[0].span().error(&format!("{e}")))?;
thread::sleep(dur.to_std()?);
let std_dur = dur
.to_std()
.map_err(|err| anyhow::anyhow!("Failed to convert to std::time::Duration: {err}"))?;
thread::sleep(std_dur);
Ok(Value::Null)
}

View File

@@ -158,7 +158,8 @@ fn parse_ns(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) ->
let layout = ensure_string(name, &params[0], &args[0])?;
let value = ensure_string(name, &params[1], &args[1])?;
let datetime = compat::parse(layout_with_predefined_formats(&layout), &value)?;
let datetime = compat::parse(layout_with_predefined_formats(&layout), &value)
.map_err(|err| anyhow::anyhow!("Failed to parse datetime: {}", err))?;
safe_timestamp_nanos(span, strict, datetime.timestamp_nanos_opt())
}
@@ -173,7 +174,8 @@ fn parse_rfc3339_ns(
let value = ensure_string(name, &params[0], &args[0])?;
let datetime = DateTime::parse_from_rfc3339(&value)?;
let datetime = DateTime::parse_from_rfc3339(&value)
.map_err(|err| anyhow::anyhow!("Failed to parse datetime: {}", err))?;
safe_timestamp_nanos(span, strict, datetime.timestamp_nanos_opt())
}

View File

@@ -266,7 +266,7 @@ struct GoTimeFormatItems<'a> {
mode: GoTimeFormatItemsMode,
}
impl<'a> GoTimeFormatItems<'a> {
impl GoTimeFormatItems<'_> {
fn parse(reminder: &str) -> GoTimeFormatItems {
GoTimeFormatItems {
reminder,

View File

@@ -83,8 +83,8 @@ fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
bail!(span.error("spaces not allowed in resource strings"));
}
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) {
Some(p) => (&string[0..p], &string[p..]),
let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) {
Some(p) => (&string[0..p + 1], &string[p + 1..]),
_ => (string, ""),
};
@@ -161,8 +161,8 @@ fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool)
bail!(span.error("spaces not allowed in resource strings"));
}
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) {
Some(p) => (&string[0..p], &string[p..]),
let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) {
Some(p) => (&string[0..p + 1], &string[p + 1..]),
_ => (string, ""),
};

View File

@@ -132,16 +132,16 @@ fn timestamp(uuid: &Uuid) -> Option<Timestamp> {
const fn decode_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) {
let bytes = uuid.as_bytes();
let ticks: u64 = ((bytes[6] & 0x0F) as u64) << 56
| (bytes[7] as u64) << 48
| (bytes[4] as u64) << 40
| (bytes[5] as u64) << 32
| (bytes[0] as u64) << 24
| (bytes[1] as u64) << 16
| (bytes[2] as u64) << 8
let ticks: u64 = (((bytes[6] & 0x0F) as u64) << 56)
| ((bytes[7] as u64) << 48)
| ((bytes[4] as u64) << 40)
| ((bytes[5] as u64) << 32)
| ((bytes[0] as u64) << 24)
| ((bytes[1] as u64) << 16)
| ((bytes[2] as u64) << 8)
| (bytes[3] as u64);
let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16);
let counter: u16 = (((bytes[8] & 0x3F) as u16) << 8) | (bytes[9] as u16);
(ticks, counter)
}

View File

@@ -37,33 +37,37 @@ impl Engine {
modules: vec![],
interpreter: Interpreter::new(),
prepared: false,
rego_v1: false,
rego_v1: true,
}
}
/// Turn rego.v1 on/off for subsequently added policies.
///
/// Explicit import rego.v1 is not needed if set.
/// Enable rego v0.
///
/// Note that regorus now defaults to v1.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// engine.set_rego_v1(true);
/// // Enable v0 for old style policies.
/// engine.set_rego_v0(true);
///
/// engine.add_policy(
/// "test.rego".to_string(),
/// r#"
/// package test
/// allow if true # if keyword is automatically imported
///
/// allow { # v0 syntax does not require if keyword
/// 1 < 2
/// }
/// "#.to_string())?;
///
/// # Ok(())
/// # }
/// ```
///
pub fn set_rego_v1(&mut self, rego_v1: bool) {
self.rego_v1 = rego_v1;
pub fn set_rego_v0(&mut self, rego_v0: bool) {
self.rego_v1 = !rego_v0;
}
/// Add a policy.
@@ -114,6 +118,8 @@ impl Engine {
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
/// // framework.rego does not conform to v1.
/// engine.set_rego_v0(true);
///
/// let package = engine.add_policy_from_file("tests/aci/framework.rego")?;
///
@@ -139,6 +145,8 @@ impl Engine {
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
/// // framework.rego does not conform to v1.
/// engine.set_rego_v0(true);
///
/// let _ = engine.add_policy_from_file("tests/aci/framework.rego")?;
///
@@ -414,6 +422,7 @@ impl Engine {
/// let mut engine = Engine::new();
///
/// // Add policies
/// engine.set_rego_v0(true);
/// engine.add_policy_from_file("tests/aci/framework.rego")?;
/// engine.add_policy_from_file("tests/aci/api.rego")?;
/// engine.add_policy_from_file("tests/aci/policy.rego")?;
@@ -739,7 +748,7 @@ impl Engine {
/// engine.add_policy(
/// "policy.rego".to_string(),
/// r#"package invalid
/// x = y {
/// x = y if {
/// # y = do_magic(2)
/// do_magic(2, y) # y is supplied as an out parameter.
/// }
@@ -775,7 +784,7 @@ impl Engine {
/// r#"
/// package test # Line 2
///
/// x = y { # Line 4
/// x = y if { # Line 4
/// input.a > 2 # Line 5
/// y = 5 # Line 6
/// }

View File

@@ -484,6 +484,12 @@ impl Interpreter {
self.hoist_loops_impl(rhs, loops);
}
#[cfg(feature = "rego-extensions")]
OrExpr { lhs, rhs, .. } => {
self.hoist_loops_impl(lhs, loops);
self.hoist_loops_impl(rhs, loops);
}
Membership {
key,
value,
@@ -554,8 +560,8 @@ impl Interpreter {
}
match op {
BinOp::Or => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
BinOp::And => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
BinOp::Union => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
BinOp::Intersection => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
}
}
@@ -2831,6 +2837,15 @@ impl Interpreter {
..
} => self.eval_membership(key, value, collection),
#[cfg(feature = "rego-extensions")]
Expr::OrExpr { lhs, rhs, .. } => {
let lhs = self.eval_expr(lhs)?;
match lhs {
Value::Bool(false) | Value::Null | Value::Undefined => self.eval_expr(rhs),
_ => Ok(lhs),
}
}
// Creation expression
Expr::Array { items, .. } => self.eval_array(items),
Expr::Object { fields, .. } => self.eval_object(fields),
@@ -2955,10 +2970,7 @@ impl Interpreter {
_ => bail!("internal error: rule's context already popped"),
};
let result = match result {
Ok(r) => r,
Err(e) => return Err(e),
};
let result = result?;
assert_eq!(self.scopes.len(), n_scopes);
@@ -3126,6 +3138,8 @@ impl Interpreter {
ArithExpr { span, .. } => ("arithexpr", span),
AssignExpr { span, .. } => ("assignexpr", span),
Membership { span, .. } => ("membership", span),
#[cfg(feature = "rego-extensions")]
OrExpr { span, .. } => ("orexpr", span),
};
Err(span.error(format!("invalid `{kind}` in default value").as_str()))

View File

@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(unknown_lints)]
#![allow(clippy::doc_lazy_continuation)]
// Use README.md as crate documentation.
@@ -357,7 +358,7 @@ where
}
/// Implement clone for a boxed extension using [`Extension::clone_box`].
impl<'a> Clone for Box<dyn 'a + Extension> {
impl Clone for Box<dyn '_ + Extension> {
fn clone(&self) -> Self {
(**self).clone_box()
}
@@ -404,7 +405,6 @@ pub mod coverage {
/// Lines that are not covered are red.
///
/// <img src="https://github.com/microsoft/regorus/blob/main/docs/coverage.png?raw=true">
pub fn to_string_pretty(&self) -> anyhow::Result<String> {
let mut s = String::default();
s.push_str("COVERAGE REPORT:\n");

View File

@@ -19,7 +19,7 @@ pub struct Parser<'source> {
tok: Token,
line: u32,
end: u32,
future_keywords: BTreeMap<String, Span>,
future_keywords: BTreeMap<String, Option<Span>>,
rego_v1: bool,
}
@@ -41,13 +41,13 @@ impl<'source> Parser<'source> {
}
pub fn enable_rego_v1(&mut self) -> Result<()> {
self.turn_on_rego_v1(self.tok.1.clone())
self.turn_on_rego_v1(&None)
}
fn turn_on_rego_v1(&mut self, span: Span) -> Result<()> {
fn turn_on_rego_v1(&mut self, span: &Option<Span>) -> Result<()> {
self.rego_v1 = true;
for kw in FUTURE_KEYWORDS {
self.set_future_keyword(kw, &span)?;
self.set_future_keyword(kw, span)?;
}
Ok(())
}
@@ -97,9 +97,9 @@ impl<'source> Parser<'source> {
}
}
pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> {
match &self.future_keywords.get(kw) {
Some(s) if self.rego_v1 => Err(self.source.error(
pub fn set_future_keyword(&mut self, kw: &str, span: &Option<Span>) -> Result<()> {
match (span, self.future_keywords.get(kw)) {
(Some(span), Some(Some(s))) if self.rego_v1 => Err(self.source.error(
span.line,
span.col,
format!(
@@ -155,11 +155,11 @@ impl<'source> Parser<'source> {
fn handle_import_future_keywords(&mut self, comps: &[Span]) -> Result<bool> {
if comps.len() >= 2 && comps[0].text() == "future" && comps[1].text() == "keywords" {
match comps.len() - 2 {
1 => self.set_future_keyword(comps[2].text(), &comps[2])?,
1 => self.set_future_keyword(comps[2].text(), &Some(comps[2].clone()))?,
0 => {
let span = &comps[1];
for kw in FUTURE_KEYWORDS.iter() {
self.set_future_keyword(kw, span)?;
self.set_future_keyword(kw, &Some(span.clone()))?;
}
}
_ => {
@@ -499,7 +499,7 @@ impl<'source> Parser<'source> {
fn parse_parens_expr(&mut self) -> Result<Expr> {
self.next_token()?;
let expr = self.parse_membership_expr()?;
let expr = self.parse_expr()?;
self.expect(")", "while parsing parenthesized expression")?;
//TODO: if needed introduce a parens-expr node or adjust expr's span.
Ok(expr)
@@ -700,7 +700,7 @@ impl<'source> Parser<'source> {
}
}
fn parse_and_expr(&mut self) -> Result<Expr> {
fn parse_set_intersection_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_arith_expr()?;
@@ -712,7 +712,7 @@ impl<'source> Parser<'source> {
span.end = self.end;
expr = Expr::BinExpr {
span,
op: BinOp::And,
op: BinOp::Intersection,
lhs: Ref::new(expr),
rhs: Ref::new(right),
};
@@ -720,19 +720,19 @@ impl<'source> Parser<'source> {
Ok(expr)
}
fn parse_or_expr(&mut self) -> Result<Expr> {
fn parse_set_union_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_and_expr()?;
let mut expr = self.parse_set_intersection_expr()?;
while self.token_text() == "|" {
let mut span = self.tok.1.clone();
span.start = start;
self.next_token()?;
let right = self.parse_and_expr()?;
let right = self.parse_set_intersection_expr()?;
span.end = self.end;
expr = Expr::BinExpr {
span,
op: BinOp::Or,
op: BinOp::Union,
lhs: Ref::new(expr),
rhs: Ref::new(right),
};
@@ -742,7 +742,7 @@ impl<'source> Parser<'source> {
fn parse_bool_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_or_expr()?;
let mut expr = self.parse_set_union_expr()?;
loop {
let mut span = self.tok.1.clone();
span.start = start;
@@ -756,7 +756,7 @@ impl<'source> Parser<'source> {
_ => break,
};
self.next_token()?;
let right = self.parse_or_expr()?;
let right = self.parse_set_union_expr()?;
span.end = self.end;
expr = Expr::BoolExpr {
span,
@@ -811,6 +811,32 @@ impl<'source> Parser<'source> {
Ok(expr)
}
pub fn parse_expr(&mut self) -> Result<Expr> {
#[cfg(feature = "rego-extensions")]
return self.parse_or_expr();
#[cfg(not(feature = "rego-extensions"))]
return self.parse_membership_expr();
}
#[cfg(feature = "rego-extensions")]
pub fn parse_or_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_membership_expr()?;
while self.token_text() == "or" {
let mut span = self.tok.1.clone();
span.start = start;
self.next_token()?;
let rhs = self.parse_membership_expr()?;
expr = Expr::OrExpr {
span,
lhs: Ref::new(expr),
rhs: Ref::new(rhs),
};
}
Ok(expr)
}
pub fn parse_membership_expr(&mut self) -> Result<Expr> {
let start = self.tok.1.start;
let mut expr = self.parse_bool_expr()?;
@@ -851,12 +877,12 @@ impl<'source> Parser<'source> {
":=" => AssignOp::ColEq,
_ => {
*self = state;
return self.parse_membership_expr();
return self.parse_expr();
}
};
self.next_token()?;
let right = self.parse_membership_expr()?;
let right = self.parse_expr()?;
span.end = self.end;
Ok(Expr::AssignExpr {
span,
@@ -1048,7 +1074,13 @@ impl<'source> Parser<'source> {
// Treat { 1 | 1 } as a comprehension instead of a
// set of 1 element.
if let Literal::Expr { expr: e, .. } = &stmt.literal {
if matches!(e.as_ref(), Expr::BinExpr { op: BinOp::Or, .. }) {
if matches!(
e.as_ref(),
Expr::BinExpr {
op: BinOp::Union,
..
}
) {
*self = state;
bail!("try parse as comprehension");
}
@@ -1116,7 +1148,7 @@ impl<'source> Parser<'source> {
_ => return Ok(None),
};
let expr = Ref::new(self.parse_membership_expr()?);
let expr = Ref::new(self.parse_expr()?);
span.end = self.end;
Ok(Some(RuleAssign {
span,
@@ -1264,7 +1296,7 @@ impl<'source> Parser<'source> {
}
"[" => {
self.next_token()?;
let index = self.parse_membership_expr()?;
let index = self.parse_expr()?;
span.end = self.end;
self.expect("]", "while parsing bracketed reference")?;
term = Expr::RefBrack {
@@ -1312,7 +1344,7 @@ impl<'source> Parser<'source> {
}
"contains" => {
self.next_token()?;
let key = Ref::new(self.parse_membership_expr()?);
let key = Ref::new(self.parse_expr()?);
span.end = self.end;
Ok(RuleHead::Set {
span,
@@ -1677,7 +1709,7 @@ impl<'source> Parser<'source> {
let is_future_kw =
if comps.len() == 2 && comps[0].text() == "rego" && comps[1].text() == "v1" {
self.turn_on_rego_v1(span.clone())?;
self.turn_on_rego_v1(&Some(span.clone()))?;
true
} else {
self.handle_import_future_keywords(&comps)?

View File

@@ -258,6 +258,12 @@ pub fn traverse(expr: &Ref<Expr>, f: &mut dyn FnMut(&Ref<Expr>) -> Result<bool>)
traverse(rhs, f)?;
}
#[cfg(feature = "rego-extensions")]
OrExpr { lhs, rhs, .. } => {
traverse(lhs, f)?;
traverse(rhs, f)?;
}
Membership {
key,
value,

View File

@@ -141,6 +141,7 @@ pub fn eval_file(
strict: bool,
) -> Result<(Vec<Value>, Vec<String>)> {
let mut engine: Engine = Engine::new();
engine.set_rego_v0(true);
engine.set_strict_builtin_errors(strict);
engine.set_gather_prints(true);
@@ -385,6 +386,11 @@ fn yaml_test_impl(file: &str) -> Result<()> {
}
fn yaml_test(file: &str) -> Result<()> {
#[cfg(not(feature = "rego-extensions"))]
if file.contains("rego-extensions") {
return Ok(());
}
match yaml_test_impl(file) {
Ok(_) => Ok(()),
Err(e) => {

View File

@@ -78,10 +78,7 @@ pub fn get_extra_arg(
module: Option<&str>,
functions: &FunctionTable,
) -> Option<Ref<Expr>> {
match get_extra_arg_impl(expr, module, functions) {
Ok(a) => a,
_ => None,
}
get_extra_arg_impl(expr, module, functions).unwrap_or_default()
}
pub fn gather_functions(modules: &[Ref<Module>]) -> Result<FunctionTable> {

View File

@@ -322,7 +322,8 @@ impl Value {
/// // Convert the value back to json.
/// let json_str = value.to_json_str()?;
///
/// assert_eq!(json_str.trim(), std::fs::read_to_string("tests/aci/input.json")?.trim());
/// assert_eq!(json_str.trim(),
/// std::fs::read_to_string("tests/aci/input.json")?.trim().replace("\r\n", "\n"));
/// # Ok(())
/// # }
/// ```
@@ -345,7 +346,8 @@ impl Value {
/// // Convert the value back to json.
/// let json_str = value.to_json_str()?;
///
/// assert_eq!(json_str.trim(), std::fs::read_to_string("tests/aci/input.json")?.trim());
/// assert_eq!(json_str.trim(),
/// std::fs::read_to_string("tests/aci/input.json")?.trim().replace("\r\n", "\n"));
/// # Ok(())
/// # }
/// ```
@@ -403,7 +405,9 @@ impl Value {
#[cfg(feature = "yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn from_yaml_str(yaml: &str) -> Result<Value> {
Ok(serde_yaml::from_str(yaml)?)
let value = serde_yaml::from_str(yaml)
.map_err(|err| anyhow::anyhow!("Failed to parse YAML: {}", err))?;
Ok(value)
}
/// Deserialize a value from a file containing YAML.
@@ -902,6 +906,180 @@ impl Value {
}
}
/// Cast value to [`& u32`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a u32.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(10);
/// assert_eq!(v.as_u32()?, 10u32);
///
/// let v = Value::from(-10);
/// assert!(v.as_u32().is_err());
/// # Ok(())
/// # }
pub fn as_u32(&self) -> Result<u32> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_u64() {
if let Ok(v) = u32::try_from(n) {
return Ok(v);
}
}
bail!("not a u32");
}
_ => Err(anyhow!("not a u32")),
}
}
/// Cast value to [`& i32`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a i32.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(-10);
/// assert_eq!(v.as_i32()?, -10i32);
///
/// let v = Value::from(2_147_483_648i64);
/// assert!(v.as_i32().is_err());
/// # Ok(())
/// # }
pub fn as_i32(&self) -> Result<i32> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_i64() {
if let Ok(v) = i32::try_from(n) {
return Ok(v);
}
}
bail!("not an i32");
}
_ => Err(anyhow!("not an i32")),
}
}
/// Cast value to [`& u16`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a u16.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(10);
/// assert_eq!(v.as_u16()?, 10u16);
///
/// let v = Value::from(-10);
/// assert!(v.as_u16().is_err());
/// # Ok(())
/// # }
pub fn as_u16(&self) -> Result<u16> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_u64() {
if let Ok(v) = u16::try_from(n) {
return Ok(v);
}
}
bail!("not a u16");
}
_ => Err(anyhow!("not a u16")),
}
}
/// Cast value to [`& i16`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a i16.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(-10);
/// assert_eq!(v.as_i16()?, -10i16);
///
/// let v = Value::from(32768i64);
/// assert!(v.as_i16().is_err());
/// # Ok(())
/// # }
pub fn as_i16(&self) -> Result<i16> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_i64() {
if let Ok(v) = i16::try_from(n) {
return Ok(v);
}
}
bail!("not an i16");
}
_ => Err(anyhow!("not an i16")),
}
}
/// Cast value to [`& u8`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a u8.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(10);
/// assert_eq!(v.as_u8()?, 10u8);
///
/// let v = Value::from(-10);
/// assert!(v.as_u8().is_err());
/// # Ok(())
/// # }
pub fn as_u8(&self) -> Result<u8> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_u64() {
if let Ok(v) = u8::try_from(n) {
return Ok(v);
}
}
bail!("not a u8");
}
_ => Err(anyhow!("not a u8")),
}
}
/// Cast value to [`& i8`] if [`Value::Number`].
///
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a i8.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let v = Value::from(-10);
/// assert_eq!(v.as_i8()?, -10i8);
///
/// let v = Value::from(128);
/// assert!(v.as_i8().is_err());
/// # Ok(())
/// # }
pub fn as_i8(&self) -> Result<i8> {
match self {
Value::Number(b) => {
if let Some(n) = b.as_i64() {
if let Ok(v) = i8::try_from(n) {
return Ok(v);
}
}
bail!("not an i8");
}
_ => Err(anyhow!("not an i8")),
}
}
/// Cast value to [`& f64`] if [`Value::Number`].
/// Error is raised if the value is not a number or if the numeric value
/// does not fit in a i64.

View File

@@ -27,6 +27,7 @@ struct YamlTest {
fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> {
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine.add_data(case.data.clone())?;
engine.set_input(case.input.clone());
@@ -116,6 +117,7 @@ fn run_aci_tests(dir: &Path) -> Result<()> {
#[cfg(feature = "coverage")]
fn run_aci_tests_coverage(dir: &Path) -> Result<()> {
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine.set_enable_coverage(true);
let mut added = std::collections::BTreeSet::new();

View File

@@ -45,6 +45,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
let mut engine = Engine::new();
engine.set_enable_coverage(true);
engine.set_rego_v0(true);
for (idx, rego) in case.modules.iter().enumerate() {
engine.add_policy(format!("rego_{idx}"), rego.clone())?;

View File

@@ -0,0 +1,151 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: base
data: {}
modules:
- |
package test
v1 = indexof("Hello world", "llo") # valid substring
v2 = indexof("Hello world", "hel") # case sensitive
v3 = indexof("Hello world", "l") # single character
v4 = indexof("", ",") # empty string
v5 = indexof("", "") # empty substring and string
query: data.test
want_result:
v1: 2
v2: -1
v3: 2
v4: -1
v5: -1
- note: unicode-char
data: {}
modules:
- |
package test
v1 = indexof("μx", "x")
query: data.test
want_result:
v1: 1
- note: unicode-chars-not-found
data: {}
modules:
- |
package test
v1 = indexof("μ", "μμ")
query: data.test
want_result:
v1: -1
- note: unicode-string
data: {}
modules:
- |
package test
v1 = indexof("skön var våren", "vår")
query: data.test
want_result:
v1: 9
- note: undefined-string
data: {}
modules:
- |
package test
x { false }
y = indexof(x, "")
query: data.test
want_result: {}
- note: undefined-substring
data: {}
modules:
- |
package test
x { false }
y = indexof(",", x)
query: data.test
want_result: {}
- note: invalid-null-string
data: {}
modules: ["package test\nx=indexof(null, ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-bool-string
data: {}
modules: ["package test\nx=indexof(true, ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-number-string
data: {}
modules: ["package test\nx=indexof(1, ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-array-string
data: {}
modules: ["package test\nx=indexof([], ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-set-string
data: {}
modules: ["package test\nx=indexof(set(), ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-object-string
data: {}
modules: ["package test\nx=indexof({}, ``)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-null-substring
data: {}
modules: ["package test\nx=indexof(``, null)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-bool-substring
data: {}
modules: ["package test\nx=indexof(``, true)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-number-substring
data: {}
modules: ["package test\nx=indexof(``, 1)"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-array-substring
data: {}
modules: ["package test\nx=indexof(``, [])"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-set-substring
data: {}
modules: ["package test\nx=indexof(``, set())"]
query: data.test
error: "`indexof` expects string argument."
- note: invalid-object-substring
data: {}
modules: ["package test\nx=indexof(``, {})"]
query: data.test
error: "`indexof` expects string argument."

View File

@@ -0,0 +1,79 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: base
data: {}
modules:
- |
package test
v1 = lower("") # empty string
v2 = lower("a")
v3 = lower("A")
v4 = lower("AbCd")
v5 = lower("aBcD109")
v6 = lower("aabAB09_")
v7 = lower("longStrinGwitHmixofsmaLLandcAps")
query: data.test
want_result:
v1: ""
v2: "a"
v3: "a"
v4: "abcd"
v5: "abcd109"
v6: "aabab09_"
v7: "longstringwithmixofsmallandcaps"
- note: unicode string
data: {}
modules:
- |
package test
v1 = lower("Σ")
v2 = lower("ὈΔΥΣΣΕΎΣ")
v3 = lower("LONG\u2C6FSTRING\u2C6FWITH\u2C6FNONASCII\u2C6FCHARS")
query: data.test
want_result:
v1: "σ"
v2: "ὀδυσσεύς"
v3: "long\u0250string\u0250with\u0250nonascii\u0250chars"
- note: invalid-null-string
data: {}
modules: ["package test\nx=lower(null)"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-bool-string
data: {}
modules: ["package test\nx=lower(true)"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-number-string
data: {}
modules: ["package test\nx=lower(1)"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-array-string
data: {}
modules: ["package test\nx=lower([])"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-set-string
data: {}
modules: ["package test\nx=lower(set())"]
query: data.test
error: "`lower` expects string argument."
- note: invalid-object-string
data: {}
modules: ["package test\nx=lower({})"]
query: data.test
error: "`lower` expects string argument."

View File

@@ -0,0 +1,52 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: basic
modules:
- |
package test
import rego.v1
x := data.foo or 2 # undefined lhs
y := false or 3 # false rhs
z := null or 4
a := data.foo or false or null or 5
b := startswith("a", "b") or startswith("a", "a")
c := 5 in [1,2] or 6 in [6]
d := x if {
x := false or [1, 2][_]
x > 1
}
e if 1 > 2 or false
query: data.test
want_result:
x: 2
y: 3
z: 4
a: 5
b: true
c: true
d: 2
- note: Azure Policy
modules:
- |
package policy
effect := parameters.effect if {
resource.type == "Microsoft.Storage/storageaccounts"
resource.properties.networkAcls.defaultAction == "Deny"
or count(resource.properties.networkAcls.ipRules) >= 1
}
resource := input.resource
parameters := input.parameters
input:
resource:
type: "Microsoft.Storage/storageaccounts"
properties:
networksAcls:
ipRules: ["rule1", "rule2"]
parameters:
effect: "Deny"
query: data.policy.effect
want_result: "Deny"

View File

@@ -9,6 +9,16 @@ use anyhow::{bail, Result};
use clap::Parser;
use walkdir::WalkDir;
fn normalize_printed_paths(mut prints: Vec<String>) -> Vec<String> {
prints.iter_mut().for_each(|p| {
*p = p
.replace("\\", "/")
.replace("//", "/")
.replace("\r\n", "\n");
});
prints
}
fn run_kata_tests(
tests_dir: &Path,
name: &Option<String>,
@@ -43,6 +53,7 @@ fn run_kata_tests(
let prints_file = path.join("prints.json");
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine.add_policy_from_file(&policy_file)?;
engine.set_gather_prints(true);
engine.set_strict_builtin_errors(false);
@@ -110,14 +121,14 @@ fn run_kata_tests(
if generate {
results.push(r);
prints.push(engine.take_prints()?);
prints.push(normalize_printed_paths(engine.take_prints()?));
} else {
let expected = results.pop().unwrap();
assert_eq!(r, expected, "{lineno} failed in {}", inputs_file.display());
let p = engine.take_prints()?;
assert_eq!(p, new_engine.take_prints()?);
assert_eq!(p, prints.pop().unwrap());
let p = normalize_printed_paths(engine.take_prints()?);
assert_eq!(p, normalize_printed_paths(new_engine.take_prints()?));
assert_eq!(p, normalize_printed_paths(prints.pop().unwrap()));
}
num_queries += 2;
@@ -179,7 +190,6 @@ fn stateful_policy_test() -> Result<()> {
let policy = String::from(
r#"
package example
import rego.v1
default allow := false

View File

@@ -22,11 +22,9 @@ v0/comprehensions
v0/containskeyword
v0/cryptohmacequal
v0/cryptohmacmd5
v0/cryptohmacsha1
v0/cryptohmacsha256
v0/cryptohmacsha512
v0/cryptomd5
v0/cryptosha1
v0/cryptosha256
v0/dataderef
v0/defaultkeyword
@@ -54,7 +52,6 @@ v0/jsonfilteridempotent
v0/jsonremove
v0/jsonremoveidempotent
v0/jsonschema
v0/jwtbuiltins
v0/negation
v0/nestedreferences
v0/numbersrange
@@ -137,11 +134,9 @@ v1/comprehensions
v1/containskeyword
v1/cryptohmacequal
v1/cryptohmacmd5
v1/cryptohmacsha1
v1/cryptohmacsha256
v1/cryptohmacsha512
v1/cryptomd5
v1/cryptosha1
v1/cryptosha256
v1/dataderef
v1/defaultkeyword
@@ -169,7 +164,6 @@ v1/jsonfilteridempotent
v1/jsonremove
v1/jsonremoveidempotent
v1/jsonschema
v1/jwtbuiltins
v1/negation
v1/nestedreferences
v1/numbersrange

View File

@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
const OPA_REPO: &str = "https://github.com/open-policy-agent/opa";
const OPA_BRANCH: &str = "v0.68.0";
const OPA_BRANCH: &str = "v1.2.0";
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
@@ -51,13 +51,13 @@ struct YamlTest {
cases: Vec<TestCase>,
}
fn eval_test_case(case: &TestCase, is_rego_v1_test: bool) -> Result<Value> {
fn eval_test_case(case: &TestCase, is_rego_v0_test: bool) -> Result<Value> {
let mut engine = Engine::new();
#[cfg(feature = "coverage")]
engine.set_enable_coverage(true);
engine.set_rego_v1(is_rego_v1_test);
engine.set_rego_v0(is_rego_v0_test);
if let Some(data) = &case.data {
engine.add_data(data.clone())?;
@@ -174,7 +174,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
continue;
}
let is_rego_v1_test = path_dir_str.starts_with("v1/");
let is_rego_v0_test = path_dir_str.starts_with("v0/") || path_dir.starts_with("v0\\");
let entry = status.entry(path_dir_str).or_insert((0, 0, 0));
let yaml_str = std::fs::read_to_string(&path_str)?;
@@ -210,6 +210,10 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
if let Some(ref mut want_result) = &mut case.want_result {
want_result.as_array_mut()?.sort();
}
} else if case.note == "withkeyword/builtin: nested, multiple mocks" {
// Mocks non-existent jwt builtin.
println!("skipping mock test for io.jwt.decode_verify: {}", case.note);
continue;
}
// Normalize for comparison.
@@ -219,7 +223,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
print!("{:4}: {:90}", entry.2, case.note);
entry.2 += 1;
match (eval_test_case(&case, is_rego_v1_test), &case.want_result) {
match (eval_test_case(&case, is_rego_v0_test), &case.want_result) {
(Ok(actual), Some(expected))
if is_json_schema_test && json_schema_tests_check(&actual, &expected) =>
{
@@ -294,6 +298,10 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
}
}
if is_rego_v0_test {
cmd += " -v0";
}
std::fs::write(path.join(format!("query{n}.text")), case.query.as_bytes())?;
cmd += format!(" \"{}\"", &case.query).as_str();
@@ -394,7 +402,7 @@ fn main() -> Result<()> {
bail!("failed to clone OPA repository");
}
}
format!("{branch_dir}/test/cases/testdata")
format!("{branch_dir}/v1/test/cases/testdata")
}
};

View File

@@ -198,6 +198,13 @@ fn match_expr_impl(e: &Expr, v: &Value) -> Result<()> {
match_expr(value, &v["inexpr"]["value"])?;
match_expr(collection, &v["inexpr"]["collection"])
}
#[cfg(feature = "rego-extensions")]
Expr::OrExpr { span, lhs, rhs } => {
match_span_opt(span, &v["orexpr"]["span"])?;
match_expr(lhs, &v["orexpr"]["lhs"])?;
match_expr(rhs, &v["orexpr"]["rhs"])
}
}
}
@@ -324,8 +331,8 @@ fn match_expr_opt(s: &Span, e: &Option<Ref<Expr>>, v: &Value) -> Result<()> {
fn match_bin_op(s: &Span, op: &BinOp, v: &Value) -> Result<()> {
match (op, v) {
(BinOp::And, Value::String(s)) if s.as_ref() == "&" => Ok(()),
(BinOp::Or, Value::String(s)) if s.as_ref() == "|" => Ok(()),
(BinOp::Intersection, Value::String(s)) if s.as_ref() == "&" => Ok(()),
(BinOp::Union, Value::String(s)) if s.as_ref() == "|" => Ok(()),
_ => bail!(
"{}",
s.source.message(