Compare commits

...

35 Commits

Author SHA1 Message Date
Anand Krishnamoorthi
658f34753b chore: release (#260)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-30 06:35:12 -07:00
Anand Krishnamoorthi
5eda76fd00 Add release-plz config to publish only regorus package (#259)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-05-30 06:26:10 -07:00
Anand Krishnamoorthi
97914d5596 Revert "chore: release v0.2.0 (#257)" (#258)
This reverts commit ffb79f1b30.
2024-05-30 06:09:47 -07:00
Anand Krishnamoorthi
ffb79f1b30 chore: release v0.2.0 (#257)
v0.2.0

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

---------

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

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

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

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

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

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

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

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

closes #229

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


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

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

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

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

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

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

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

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

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

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

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

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

closes #215, closes #218

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

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

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

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

Such errors are not raised; consistent with OPA.

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

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

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

Signed-off-by: Dan Mihai <dmihai@microsoft.com>
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
Co-authored-by: Dan Mihai <dmihai@microsoft.com>
2024-04-20 06:47:18 -07:00
169 changed files with 57201 additions and 1247 deletions

View File

@@ -1,4 +1,4 @@
name: Rust
name: tests/release
on:
push:
@@ -10,35 +10,30 @@ env:
CARGO_TERM_COLOR: always
jobs:
build:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Add musl target
run: rustup target add x86_64-unknown-linux-musl
- name: Install musl-gcc
run: sudo apt update && sudo apt install -y musl-tools
- uses: actions/checkout@v4
- name: Format Check
run: cargo fmt --check
- name: Build (all features)
run: cargo build -r --all-features
- name: Build
run: cargo build -r --verbose
run: cargo build -r
- name: Test no_std
run: cargo test -r --no-default-features
- name: Build only std
run: cargo build -r --example regorus --no-default-features --features "std"
- name: Doc Tests
run: cargo test -r --doc
- name: Run tests
run: cargo test -r --verbose
run: cargo test -r
- name: Run tests (ACI)
run: cargo test -r --test aci
- name: Run tests (KATA)
run: cargo test -r --test kata
- name: Run tests (OPA Conformance)
run: >-
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)
- name: Build (MUSL)
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl
- name: Run tests (MUSL)
run: cargo test -r --verbose --target x86_64-unknown-linux-musl
- name: Run tests (MUSL ACI)
run: cargo test -r --test aci --target x86_64-unknown-linux-musl
- name: Run tests (MUSL OPA Conformance)
run: >-
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision --target x86_64-unknown-linux-musl -- $(tr '\n' ' ' < tests/opa.passing)

View File

@@ -23,7 +23,7 @@ jobs:
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
@@ -47,7 +47,7 @@ jobs:
python-version: '3.10'
architecture: ${{ matrix.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
@@ -69,7 +69,7 @@ jobs:
with:
python-version: '3.10'
- name: Build wheels
uses: PyO3/maturin-action@v1
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
@@ -85,7 +85,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Build sdist
uses: PyO3/maturin-action@v1
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
command: sdist
args: --out dist --manifest-path bindings/python/Cargo.toml
@@ -106,7 +106,7 @@ jobs:
with:
name: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:

View File

@@ -18,7 +18,7 @@ jobs:
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Run release-plz
uses: MarcoIeni/release-plz-action@v0.5
uses: MarcoIeni/release-plz-action@98b2b45b090aadf18cb662caaf3de6222d98822a #v0.5.60
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

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

@@ -0,0 +1,43 @@
name: bindings/c-cpp
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup gcc, g++, cmake, ninja
run: sudo apt update && sudo apt install -y gcc g++ cmake ninja-build
- name: Test c binding
run: |
mkdir bindings/c/build
cd bindings/c/build
cmake -G Ninja ..
ninja
./regorus_test
- name: Test c-nostd binding
run: |
mkdir bindings/c-nostd/build
cd bindings/c-nostd/build
cmake -G Ninja ..
ninja
./regorus_test
- name: Test cpp binding
run: |
mkdir bindings/cpp/build
cd bindings/cpp/build
cmake -G Ninja ..
ninja
./regorus_test

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

@@ -0,0 +1,26 @@
name: bindings/csharp
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v4
- name: Build
run: dotnet build
working-directory: ./bindings/csharp
- name: Run
run: LD_LIBRARY_PATH=. dotnet run
working-directory: ./bindings/csharp

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

@@ -0,0 +1,22 @@
name: bindings/c-cpp
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Test FFI
run: |
cargo build -r
cargo clippy --all-targets --no-deps -- -Dwarnings
working-directory: ./bindings/ffi

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

@@ -0,0 +1,33 @@
name: bindings/go
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build ffi
run: cargo build -r
working-directory: ./bindings/ffi
- name: Test go
run: |
go mod tidy
go build
LD_LIBRARY_PATH=../../target/release ./regorus_test
working-directory: ./bindings/go

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

@@ -0,0 +1,37 @@
name: bindings/java
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
with:
java-version: 8
distribution: "corretto"
- uses: dtolnay/rust-toolchain@stable
- name: Building binding
run: |
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --manifest-path bindings/java/Cargo.toml
- name: Build jar
run: mvn package
working-directory: ./bindings/java
- name: Test jar
run: |
javac -cp target/regorus-java-0.1.5.jar Test.java
java -Djava.library.path=../../target/release -cp target/regorus-java-0.1.5.jar:. Test
working-directory: ./bindings/java

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

@@ -0,0 +1,33 @@
name: musl
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Add musl target
run: rustup target add x86_64-unknown-linux-musl
- name: Install musl-gcc
run: sudo apt update && sudo apt install -y musl-tools
- name: Build (MUSL)
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl
- name: Run tests (MUSL)
run: cargo test -r --verbose --target x86_64-unknown-linux-musl
- name: Run tests (MUSL ACI)
run: cargo test -r --test aci --target x86_64-unknown-linux-musl
- name: Run tests (KATA ACI)
run: cargo test -r --test kata --target x86_64-unknown-linux-musl
- name: Run tests (MUSL OPA Conformance)
run: >-
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision --target x86_64-unknown-linux-musl -- $(tr '\n' ' ' < tests/opa.passing)

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

@@ -0,0 +1,24 @@
name: bindings/no-std
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Add no_std target
run: rustup target add thumbv7m-none-eabi
- name: Build
run: cargo build -r --target thumbv7m-none-eabi
working-directory: ./tests/ensure_no_std

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

@@ -0,0 +1,38 @@
name: bindings/python
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
PYTHON_VERSION: "3.10"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Build wheels
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: x86_64
args: --release --out dist --manifest-path bindings/python/Cargo.toml
sccache: 'true'
- name: Test wheel
run: |
pip3 install dist/regorus-*.whl
cd bindings/python
cargo clippy --all-targets --no-deps -- -Dwarnings
python3 test.py

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

@@ -0,0 +1,31 @@
name: bindings/ruby
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Ruby and Rust
uses: oxidize-rb/actions/setup-ruby-and-rust@7ca44a16e287e5ff7dd72ab53f4bd41cbf34a571 #v1.26
with:
ruby-version: "3.3.1"
rustup-toolchain: "stable"
bundler-cache: true
cargo-cache: true
working-directory: "bindings/ruby"
- name: Run ruby tests
run: |
cd bindings/ruby
cargo clippy --all-targets --no-deps -- -Dwarnings
bundle exec rake

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

@@ -0,0 +1,32 @@
name: bindings/wasm
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
- name: Install wasmlpack
run: cargo install wasm-pack
- name: Test wasm binding
run: |
cd bindings/wasm
cargo clippy --all-targets --no-deps -- -Dwarnings
wasm-pack build --target nodejs --release
wasm-pack test --release --node
node test.js

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

@@ -0,0 +1,37 @@
name: tests/debug
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 (all features)
run: cargo build --all-features
- name: Build
run: cargo build
- name: Test no_std
run: cargo test --no-default-features
- name: Build only std
run: cargo build --example regorus --no-default-features --features "std"
- name: Doc Tests
run: cargo test --doc
- name: Run tests
run: cargo test
- name: Run tests (ACI)
run: cargo test --test aci
- name: Run tests (KATA)
run: cargo test --test kata
- name: Run tests (OPA Conformance)
run: >-
cargo test --test opa --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)

12
.gitignore vendored
View File

@@ -13,4 +13,14 @@ Cargo.lock
.vscode/
# worktrees
worktrees/
worktrees/
# build folders
**/build
# Generated C# bindings
**/*.g.cs
# Generated C, C++ headers
bindings/ffi/regorus.h
bindings/ffi/regorus.ffi.hpp

View File

@@ -6,6 +6,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.2.0](https://github.com/microsoft/regorus/compare/regorus-v0.1.5...regorus-v0.2.0) - 2024-05-30
### Other
- Add release-plz config to publish only regorus package ([#259](https://github.com/microsoft/regorus/pull/259))
- Revert "chore: release v0.2.0 ([#257](https://github.com/microsoft/regorus/pull/257))" ([#258](https://github.com/microsoft/regorus/pull/258))
- release v0.2.0 ([#257](https://github.com/microsoft/regorus/pull/257))
- Fix release-plz hash ([#256](https://github.com/microsoft/regorus/pull/256))
- non collections should evaluate to false ([#253](https://github.com/microsoft/regorus/pull/253))
- Fix merge issue ([#252](https://github.com/microsoft/regorus/pull/252))
- Update bindings to include newer APIs ([#250](https://github.com/microsoft/regorus/pull/250))
- update ruby bindings version to 0.1.5, bump deps ([#251](https://github.com/microsoft/regorus/pull/251))
- Use correct docsrs feature annotation ([#248](https://github.com/microsoft/regorus/pull/248))
- Lockdown kata test prints as well as prints of various values ([#249](https://github.com/microsoft/regorus/pull/249))
- Fix bindings and add CI tests ([#247](https://github.com/microsoft/regorus/pull/247))
- Add test-ruby CI for github actions ([#244](https://github.com/microsoft/regorus/pull/244))
- Update `README.md` for Java bindings to mention we don't publish to ([#246](https://github.com/microsoft/regorus/pull/246))
- Update itertools requirement from 0.12.1 to 0.13.0 ([#245](https://github.com/microsoft/regorus/pull/245))
- Update ruby bindings for add_policy and add_policy_from_file to return package name ([#240](https://github.com/microsoft/regorus/pull/240))
- Provide a way to obtain package names of loaded policies ([#239](https://github.com/microsoft/regorus/pull/239))
- `c_no_std` binding to show use in C freestanding environments. ([#238](https://github.com/microsoft/regorus/pull/238))
- Bump rexml in /bindings/ruby in the bundler group across 1 directory ([#236](https://github.com/microsoft/regorus/pull/236))
- Update prettydiff requirement from 0.6.4 to 0.7.0 ([#234](https://github.com/microsoft/regorus/pull/234))
- Update jsonschema requirement from 0.17.1 to 0.18.0 ([#235](https://github.com/microsoft/regorus/pull/235))
- no_std support ([#232](https://github.com/microsoft/regorus/pull/232))
- add `std` feature ([#231](https://github.com/microsoft/regorus/pull/231))
- Tests from MSFT fork of kata-containers ([#230](https://github.com/microsoft/regorus/pull/230))
- Use alloc, core instead of std ([#225](https://github.com/microsoft/regorus/pull/225))
## [0.1.5](https://github.com/microsoft/regorus/compare/regorus-v0.1.4...regorus-v0.1.5) - 2024-05-07
### Added
- Support policy files greater than 64KB in size ([#217](https://github.com/microsoft/regorus/pull/217))
- Add tests for kata containers policies ([#221](https://github.com/microsoft/regorus/pull/221))
- Support for OPA v0.64.0 ([#219](https://github.com/microsoft/regorus/pull/219))
- New builtin `json.marshal_with_options`
### Changed
- Improve example in readme ([#224](https://github.com/microsoft/regorus/pull/224))
### Fixed
- OPA Conformance: Do not interpret # within regular string ([#216](https://github.com/microsoft/regorus/pull/216))
## [0.1.4](https://github.com/microsoft/regorus/compare/regorus-v0.1.3...regorus-v0.1.4) - 2024-04-22
### Other
- early return ([#189](https://github.com/microsoft/regorus/pull/189))
- Fix anyhow dependency issues ([#208](https://github.com/microsoft/regorus/pull/208))
- remove unused compact-rc dependency ([#207](https://github.com/microsoft/regorus/pull/207))
## [0.1.3](https://github.com/microsoft/regorus/compare/regorus-v0.1.2...regorus-v0.1.3) - 2024-04-11
### Other

View File

@@ -6,16 +6,17 @@ members = [
"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.1.3"
version = "0.2.0"
edition = "2021"
license-file = "LICENSE"
repository = "https://github.com/microsoft/regorus"
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -33,16 +34,18 @@ crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha1",
deprecated = []
hex = ["dep:data-encoding"]
http = []
jwt = ["dep:jsonwebtoken", "dep:data-encoding"]
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"]
semver = ["dep:semver"]
std = ["rand/std", "rand/std_rng", "serde_json/std"]
time = ["dep:chrono", "dep:chrono-tz"]
uuid = ["dep:uuid"]
urlquery = ["dep:url"]
time = ["dep:chrono", "dep:chrono-tz"]
yaml = ["serde_yaml"]
full-opa = [
"base64",
@@ -59,57 +62,78 @@ full-opa = [
"opa-runtime",
"regex",
"semver",
"std",
"time",
"uuid",
"urlquery",
"yaml"
]
# Features that can be used in no_std environments.
# Note that: the spin_no_std feature in lazy_static must be specified.
opa-no-std = [
"arc",
"base64",
"base64url",
"coverage",
"crypto",
"deprecated",
"graph",
"hex",
"no_std",
"opa-runtime",
"regex",
"semver",
# Configure lazy_static to use spinlocks.
"lazy_static/spin_no_std"
]
# This feature enables some testing utils for OPA tests.
opa-testutil = []
rand = ["dep:rand"]
[dependencies]
anyhow = {version = "1.0.66", features = ["backtrace"] }
serde = {version = "1.0.150", features = ["derive", "rc"] }
serde_json = "1.0.89"
serde_yaml = {version = "0.9.16", optional = true }
lazy_static = "1.4.0"
rand = "0.8.5"
num = "0.4.1"
anyhow = { version = "1.0.45", default-features = false }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc"] }
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}
hmac = {version = "0.12.1", optional = true}
sha2 = {version= "0.10.8", optional = true}
hex = {version = "0.4.3", optional = true}
sha1 = {version = "0.10.6", optional = true}
md-5 = {version = "0.10.6", optional = true}
constant_time_eq = {version = "0.3.0", optional = true, default-features = false }
hmac = {version = "0.12.1", optional = true, default-features = false}
sha2 = {version= "0.10.8", optional = true, default-features = false }
hex = {version = "0.4.3", optional = true, default-features = false, features = ["alloc"] }
sha1 = {version = "0.10.6", optional = true, default-features = false }
md-5 = {version = "0.10.6", optional = true, default-features = false }
data-encoding = { version = "2.4.0", optional = true }
data-encoding = { version = "2.4.0", optional = true, default-features=false, features = ["alloc"] }
scientific = { version = "0.5.2" }
regex = {version = "1.10.2", optional = true}
semver = {version = "1.0.20", optional = true}
regex = {version = "1.10.2", optional = true, default-features = false }
semver = {version = "1.0.20", optional = true, default-features = false }
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
url = { version = "2.5.0", optional = true }
uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.17.1", default-features = false, 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 }
compact-rc = "0.5.2"
jsonwebtoken = { version = "9.2.0", optional = true }
itertools = "0.12.1"
itertools = { version = "0.13.0", default-features = false, optional = true }
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
rand = { version = "0.8.5", default-features = false, optional = true }
[dev-dependencies]
anyhow = "1.0.45"
cfg-if = "1.0.0"
clap = { version = "4.4.7", features = ["derive"] }
colored-diff = "0.2.3"
prettydiff = { version = "0.7.0", default-features = false }
serde_yaml = "0.9.16"
test-generator = "0.3.1"
walkdir = "2.3.2"
[build-dependencies]
anyhow = "1.0.66"
anyhow = "1.0"
[profile.release]
debug = true
@@ -125,6 +149,17 @@ name="aci"
harness=false
test=false
[[test]]
name="kata"
harness=false
test=false
[[example]]
name="regorus"
harness=false
test=false
doctest=false
[package.metadata.docs.rs]
# To build locally:
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps

View File

@@ -8,8 +8,9 @@
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.63.0](https://github.com/open-policy-agent/opa/releases/tag/v0.63.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.
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.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.
@@ -24,31 +25,60 @@ Regorus is available as a library that can be easily integrated into your Rust p
Here is an example of evaluating a simple Rego policy:
```rust
use anyhow::Result;
use regorus::*;
use serde_json;
fn main() -> anyhow::Result<()> {
// Create an engine for evaluating Rego policies.
let mut engine = regorus::Engine::new();
fn main() -> Result<()> {
// Create an engine for evaluating Rego policies.
let mut engine = Engine::new();
let policy = String::from(
r#"
package example
import rego.v1
// Add policy to the engine.
engine.add_policy(
// Filename to be associated with the policy.
"hello.rego".to_string(),
allow if {
## All actions are allowed for admins.
input.principal == "admin"
} else if {
## Check if action is allowed for given user.
input.action in data.allowed_actions[input.principal]
}
"#,
);
// Rego policy that just sets a message.
r#"
package test
message = "Hello, World!"
"#.to_string()
)?;
// Add policy to the engine.
engine.add_policy(String::from("policy.rego"), policy)?;
// Evaluate the policy, fetch the message and print it.
let results = engine.eval_query("data.test.message".to_string(), false)?;
println!("{}", serde_json::to_string_pretty(&results)?);
// Add data to engine.
engine.add_data(regorus::Value::from_json_str(
r#"{
"allowed_actions": {
"user1" : ["read", "write"],
"user2" : ["read"]
}}"#,
)?)?;
Ok(())
// Set input and evaluate whether user1 can write.
engine.set_input(regorus::Value::from_json_str(
r#"{
"principal": "user1",
"action": "write"
}"#,
)?);
let r = engine.eval_rule(String::from("data.example.allow"))?;
assert_eq!(r, regorus::Value::from(true));
// Set input and evaluate whether user2 can write.
engine.set_input(regorus::Value::from_json_str(
r#"{
"principal": "user2",
"action": "write"
}"#,
)?);
let r = engine.eval_rule(String::from("data.example.allow"))?;
assert_eq!(r, regorus::Value::Undefined);
Ok(())
}
```
@@ -56,20 +86,20 @@ Regorus is designed with [Confidential Computing](https://confidentialcomputing.
it is important to be able to control exactly what is being run. Regorus allows enabling and disabling various components using cargo
features. By default all features are enabled.
The default build of regorus example program is 6.4M:
The default build of regorus example program is 6.3M:
```bash
$ cargo build -r --example regorus; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 6.4M Jan 19 11:23 target/release/examples/regorus*
-rwxr-xr-x 1 anand staff 6.3M May 11 22:03 target/release/examples/regorus*
```
When all features except for `yaml` are disabled, the binary size drops down to 2.9M.
When all default features are disabled, the binary size drops down to 1.9M.
```bash
$ cargo build -r --example regorus --features "yaml" --no-default-features; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 2.9M Jan 19 11:26 target/release/examples/regorus*
$ cargo build -r --example regorus --no-default-features; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 1.9M May 11 22:04 target/release/examples/regorus*
```
Regorus passes the [OPA v0.63.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
Regorus passes the [OPA v0.64.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
builtins. See [OPA Conformance](#opa-conformance) below.
## Bindings
@@ -79,6 +109,7 @@ Regorus can be used from a variety of languages:
- *C*: C binding is generated using [cbindgen](https://github.com/mozilla/cbindgen).
[corrosion-rs](https://github.com/corrosion-rs/corrosion) can be used to seamlessly use Regorous
in your CMake based projects. See [bindings/c](https://github.com/microsoft/regorus/tree/main/bindings/c).
- *C freestanding*: [bindings/c_no_std](https://github.com/microsoft/regorus/tree/main/bindings/c_no_std) shows how to use Regorus from C environments without a libc.
- *C++*: C++ binding is generated using [cbindgen](https://github.com/mozilla/cbindgen).
[corrosion-rs](https://github.com/corrosion-rs/corrosion) can be used to seamlessly use Regorous
in your CMake based projects. See [bindings/cpp](https://github.com/microsoft/regorus/tree/main/bindings/cpp).
@@ -245,7 +276,7 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
```
## OPA Conformance
Regorus has been verified to be compliant with [OPA v0.63.0](https://github.com/open-policy-agent/opa/releases/tag/v0.63.0)
Regorus has been verified to be compliant with [OPA v0.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.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:

View File

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

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

@@ -0,0 +1,98 @@
#include <stdio.h>
#include "regorus.h"
// Regorus has been built for no_std and cannot access files.
char* file_to_string(const char* file) {
char * buffer = 0;
long length;
FILE * f = fopen (file, "rb");
if (f)
{
fseek (f, 0, SEEK_END);
length = ftell (f);
fseek (f, 0, SEEK_SET);
buffer = malloc (length + 1);
buffer[length] = '\0';
if (buffer)
{
fread (buffer, 1, length, f);
}
fclose (f);
}
return buffer;
}
// If regorus is built with custom-allocator, then provide implementation.
uint8_t* regorus_aligned_alloc(size_t alignment, size_t size) {
return aligned_alloc(alignment, size);
}
void regorus_free(uint8_t* ptr) {
free(ptr);
}
int main() {
// Create engine.
RegorusEngine* engine = regorus_engine_new();
RegorusResult r;
char* buffer = NULL;
// Load policies.
r = regorus_engine_add_policy(engine, "framework.rego", (buffer = file_to_string("../../../tests/aci/framework.rego")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy(engine, "api.rego", (buffer = file_to_string("../../../tests/aci/api.rego")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy(engine, "policy.rego", (buffer = file_to_string("../../../tests/aci/policy.rego")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
// Add data
r = regorus_engine_add_data_json(engine, (buffer = file_to_string("../../../tests/aci/data.json")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
regorus_result_drop(r);
// Set input
r = regorus_engine_set_input_json(engine, (buffer = file_to_string("../../../tests/aci/input.json")));
free(buffer);
if (r.status != RegorusStatusOk)
goto error;
regorus_result_drop(r);
// Eval rule.
r = regorus_engine_eval_rule(engine, "data.framework.mount_overlay");
if (r.status != RegorusStatusOk)
goto error;
// Print output
printf("%s", r.output);
regorus_result_drop(r);
// Free the engine.
regorus_engine_drop(engine);
return 0;
error:
printf("%s", r.error_message);
return 1;
}

View File

@@ -18,8 +18,15 @@ corrosion_import_crate(
MANIFEST_PATH "../ffi/Cargo.toml"
# Always build regorus in Release mode.
PROFILE "release"
# Only build the "regorusc" crate.
CRATES "regorus-ffi")
# Only build the "regorus-ffi" crate.
CRATES "regorus-ffi"
# Select specific features in regorus.
FEATURES "regorus/semver"
# Link statically
CRATE_TYPES "cdylib"
)
add_executable(regorus_test main.c)
# Add path to <regorus-source-folder>/bindings/ffi

View File

@@ -10,16 +10,19 @@ int main() {
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/api.rego");
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/policy.rego");
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
// Add data
@@ -34,22 +37,61 @@ int main() {
goto error;
regorus_result_drop(r);
// Eval query
r = regorus_engine_eval_query(engine, "data.framework.mount_overlay=x");
// Eval rule.
r = regorus_engine_eval_query(engine, "data.framework.mount_overlay");
if (r.status != RegorusStatusOk)
goto error;
// Print output
printf("%s", r.output);
printf("%s\n", r.output);
regorus_result_drop(r);
// Free the engine.
regorus_engine_drop(engine);
// Create another engine.
engine = regorus_engine_new();
r = regorus_engine_add_policy(
engine,
"test.rego",
"package test\n"
"x = 1\n"
"message = `Hello`"
);
// Evaluate rule.
if (r.status != RegorusStatusOk)
goto error;
r = regorus_engine_set_enable_coverage(engine, true);
regorus_result_drop(r);
r = regorus_engine_eval_query(engine, "data.test.message");
if (r.status != RegorusStatusOk)
goto error;
// Print output
printf("%s\n", r.output);
regorus_result_drop(r);
// Print pretty coverage report.
r = regorus_engine_get_coverage_report_pretty(engine);
if (r.status != RegorusStatusOk)
goto error;
printf("%s\n", r.output);
regorus_result_drop(r);
// Free the engine.
regorus_engine_drop(engine);
return 0;
error:
printf("%s", r.error_message);
regorus_result_drop(r);
regorus_engine_drop(engine);
return 1;
}

View File

@@ -19,8 +19,14 @@ corrosion_import_crate(
MANIFEST_PATH "../ffi/Cargo.toml"
# Always build regorus in Release mode.
PROFILE "release"
# Only build the "regorusc" crate.
CRATES "regorus-ffi")
# Only build the "regorus-ffi" crate.
CRATES "regorus-ffi"
# Select specific features in regorus.
FEATURES "regorus/semver"
# Link statically
CRATE_TYPES "cdylib")
add_executable(regorus_test main.cpp)
# Add path to <regorus-source-folder>/bindings/ffi

View File

@@ -6,6 +6,8 @@ void example()
// Create engine
regorus::Engine engine;
engine.set_enable_coverage(true);
// Add policies.
engine.add_policy("objects.rego",R"(package objects
@@ -67,6 +69,14 @@ f := e["dev"])");
} else {
std::cerr<<result.error()<<std::endl;
}
// Print coverage report
auto result1 = engine.get_coverage_report_pretty();
if (result1) {
std::cout<<result1.output()<<std::endl;
} else {
std::cerr<<result1.error()<<std::endl;
}
}
int main() {
@@ -89,6 +99,7 @@ int main() {
std::cerr<<result.error()<<std::endl;
return -1;
}
std::cout<<"Loaded package "<<result.output()<< std::endl;
}
{
auto result = engine.add_data_from_json_file("../../../tests/aci/data.json");
@@ -98,7 +109,7 @@ int main() {
}
}
// Set input and eval query.
// Set input and eval rule.
{
auto result = engine.set_input_from_json_file("../../../tests/aci/input.json");
if (!result) {
@@ -106,13 +117,13 @@ int main() {
return -1;
}
}
auto result = engine.eval_query("data.framework.mount_overlay = x");
auto result = engine.eval_rule("data.framework.mount_overlay");
if (!result) {
std::cerr<<result.error()<<std::endl;
return -1;
}
std::cout<<result.output()<<std::endl;
example();
}

View File

@@ -83,6 +83,26 @@ namespace regorus {
return Result(regorus_engine_eval_query(engine, query));
}
Result eval_rule(const char* rule) {
return Result(regorus_engine_eval_rule(engine, rule));
}
Result set_enable_coverage(bool enable) {
return Result(regorus_engine_set_enable_coverage(engine, enable));
}
Result clear_coverage_data() {
return Result(regorus_engine_clear_coverage_data(engine));
}
Result get_coverage_report() {
return Result(regorus_engine_get_coverage_report(engine));
}
Result get_coverage_report_pretty() {
return Result(regorus_engine_get_coverage_report_pretty(engine));
}
~Engine() {
regorus_engine_drop(engine);
}

View File

@@ -33,19 +33,26 @@ var loadPoliciesTicks = w.ElapsedTicks;
w.Restart();
// Set input and eval query.
// Set input and eval rule.
engine.SetInputFromJsonFile("../../tests/aci/input.json");
var results = engine.EvalQuery("data.framework.mount_overlay = x");
var resultsDoc = System.Text.Json.JsonDocument.Parse(results);
var value = engine.EvalQuery("data.framework.mount_overlay");
var valueDoc = System.Text.Json.JsonDocument.Parse(value);
w.Stop();
var evalTicks = w.ElapsedTicks;
Console.WriteLine("{0}", results);
Console.WriteLine("{0}", valueDoc);
Console.WriteLine("Engine creation took {0} msecs", (newEngineTicks*nanosecPerTick)/(1000.0*1000.0));
Console.WriteLine("Load policies and data took {0} msecs", (loadPoliciesTicks*nanosecPerTick)/(1000.0*1000.0));
Console.WriteLine("EvalQuery took {0} msecs", (evalTicks*nanosecPerTick)/(1000.0*1000.0));
engine = new Regorus.Engine();
engine.AddPolicy(
"test.rego",
"package test\nx = 1\nmessage = `Hello`");
engine.SetEnableCoverage(true);
Console.WriteLine("{0}", engine.EvalRule("data.test.message"));
Console.WriteLine("{0}", engine.GetCoverageReportPretty());

View File

@@ -29,117 +29,159 @@ namespace Regorus
}
public void AddPolicy(string path, string rego)
public string 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));
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
}
}
}
}
}
public void AddPolicyFromFile(string path)
public string 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));
}
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
}
}
}
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 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));
}
}
}
}
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 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));
}
}
}
}
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 is not null) {
resultJson = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output);
}
RegorusFFI.API.regorus_result_drop(result);
} else {
CheckAndDropResult(result);
}
}
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
}
}
if (resultJson is not null) {
return resultJson;
} else {
return "";
}
public string EvalRule(string rule)
{
var ruleBytes = Encoding.UTF8.GetBytes(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));
}
}
@@ -151,8 +193,8 @@ namespace Regorus
}
}
void CheckAndDropResult(RegorusFFI.RegorusResult result)
string CheckAndDropResult(RegorusFFI.RegorusResult result)
{
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
unsafe {
@@ -162,7 +204,16 @@ namespace Regorus
throw ex;
}
}
RegorusFFI.API.regorus_result_drop(result);
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

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="BuildRegorusFFI">
<Target Name="BuildRegorusFFI">
<Exec Command="cargo build -r --manifest-path ../ffi/Cargo.toml" />

View File

@@ -1,17 +1,23 @@
[package]
name = "regorus-ffi"
version = "0.1.0"
version = "0.1.5"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
crate-type = ["cdylib", "staticlib"]
[dependencies]
anyhow = "1.0.79"
regorus = { path = "../.." }
anyhow = "1.0"
regorus = { path = "../..", default-features = false }
serde_json = "1.0.113"
[features]
default = ["std", "coverage", "regorus/arc", "regorus/full-opa"]
std = ["regorus/std"]
coverage = ["regorus/coverage"]
custom_allocator = []
[build-dependencies]
cbindgen = "0.26.0"
csbindgen = "1.9.0"

View File

@@ -1,88 +0,0 @@
// <auto-generated>
// This code is generated by csbindgen.
// DON'T CHANGE THIS DIRECTLY.
// </auto-generated>
#pragma warning disable CS8500
#pragma warning disable CS8981
using System;
using System.Runtime.InteropServices;
namespace RegorusFFI
{
internal static unsafe partial class API
{
const string __DllName = "regorusc";
/// <summary>Drop a `RegorusResult`. `output` and `error_message` strings are not valid after drop.</summary>
[DllImport(__DllName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void regorus_result_drop(RegorusResult r);
/// <summary>Construct a new Engine See https://docs.rs/regorus/latest/regorus/struct.Engine.html</summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusEngine* regorus_engine_new();
/// <summary>Clone a [`RegorusEngine`] To avoid having to parse same policy again, the engine can be cloned after policies and data have been added.</summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine);
[DllImport(__DllName, EntryPoint = "regorus_engine_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void regorus_engine_drop(RegorusEngine* engine);
/// <summary>Add a policy The policy is parsed into AST. See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy * `path`: A filename to be associated with the policy. * `rego`: Rego policy.</summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_add_policy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusResult regorus_engine_add_policy(RegorusEngine* engine, byte* path, byte* rego);
[DllImport(__DllName, EntryPoint = "regorus_engine_add_policy_from_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusResult regorus_engine_add_policy_from_file(RegorusEngine* engine, byte* path);
/// <summary>Add policy data. See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data * `data`: JSON encoded value to be used as policy data.</summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_add_data_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusResult regorus_engine_add_data_json(RegorusEngine* engine, byte* data);
[DllImport(__DllName, EntryPoint = "regorus_engine_add_data_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusResult regorus_engine_add_data_from_json_file(RegorusEngine* engine, byte* path);
/// <summary>Clear policy data. See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data</summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_clear_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusResult regorus_engine_clear_data(RegorusEngine* engine);
/// <summary>Set input. See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_input * `input`: JSON encoded value to be used as input to query.</summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_set_input_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusResult regorus_engine_set_input_json(RegorusEngine* engine, byte* input);
[DllImport(__DllName, EntryPoint = "regorus_engine_set_input_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusResult regorus_engine_set_input_from_json_file(RegorusEngine* engine, byte* path);
/// <summary>Evaluate query. See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query * `query`: Rego expression to be evaluate.</summary>
[DllImport(__DllName, EntryPoint = "regorus_engine_eval_query", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern RegorusResult regorus_engine_eval_query(RegorusEngine* engine, byte* query);
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusResult
{
public RegorusStatus status;
public byte* output;
public byte* error_message;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct RegorusEngine
{
}
internal enum RegorusStatus : uint
{
RegorusStatusOk,
RegorusStatusError,
}
}

View File

@@ -24,7 +24,7 @@ fn main() {
csbindgen::Builder::default()
.input_extern_file("src/lib.rs")
.csharp_dll_name("regorusc")
.csharp_dll_name("regorus_ffi")
.csharp_class_name("API")
.csharp_namespace("RegorusFFI")
.generate_csharp_file("./RegorusFFI.g.cs")

View File

@@ -137,9 +137,6 @@ bitflags = false
############## Options for How Your Rust library Should Be Parsed ##############
[parse]
@@ -154,5 +151,5 @@ extra_bindings = []
[parse.expand]
crates = []
all_features = false
default_features = true
default_features = false
features = []

View File

@@ -1,95 +0,0 @@
#ifndef REGORUS_FFI_HPP
#define REGORUS_FFI_HPP
#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>
/// Status of a call on `RegorusEngine`.
enum class RegorusStatus {
/// The operation was successful.
RegorusStatusOk,
/// The operation was unsuccessful.
RegorusStatusError,
};
/// Wrapper for `regorus::Engine`.
struct RegorusEngine;
/// Result of a call on `RegorusEngine`.
///
/// Must be freed using `regorus_result_drop`.
struct RegorusResult {
/// Status
RegorusStatus status;
/// Output produced by the call.
/// Owned by Rust.
char *output;
/// Errors produced by the call.
/// Owned by Rust.
char *error_message;
};
extern "C" {
/// Drop a `RegorusResult`.
///
/// `output` and `error_message` strings are not valid after drop.
void regorus_result_drop(RegorusResult r);
/// Construct a new Engine
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html
RegorusEngine *regorus_engine_new();
/// Clone a [`RegorusEngine`]
///
/// To avoid having to parse same policy again, the engine can be cloned
/// after policies and data have been added.
RegorusEngine *regorus_engine_clone(RegorusEngine *engine);
void regorus_engine_drop(RegorusEngine *engine);
/// Add a policy
///
/// The policy is parsed into AST.
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
///
/// * `path`: A filename to be associated with the policy.
/// * `rego`: Rego policy.
RegorusResult regorus_engine_add_policy(RegorusEngine *engine, const char *path, const char *rego);
RegorusResult regorus_engine_add_policy_from_file(RegorusEngine *engine, const char *path);
/// Add policy data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data
/// * `data`: JSON encoded value to be used as policy data.
RegorusResult regorus_engine_add_data_json(RegorusEngine *engine, const char *data);
RegorusResult regorus_engine_add_data_from_json_file(RegorusEngine *engine, const char *path);
/// Clear policy data.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data
RegorusResult regorus_engine_clear_data(RegorusEngine *engine);
/// Set input.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_input
/// * `input`: JSON encoded value to be used as input to query.
RegorusResult regorus_engine_set_input_json(RegorusEngine *engine, const char *input);
RegorusResult regorus_engine_set_input_from_json_file(RegorusEngine *engine, const char *path);
/// Evaluate query.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query
/// * `query`: Rego expression to be evaluate.
RegorusResult regorus_engine_eval_query(RegorusEngine *engine, const char *query);
} // extern "C"
#endif // REGORUS_FFI_HPP

View File

@@ -1,127 +0,0 @@
#ifndef REGORUS_H
#define REGORUS_H
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/**
* Status of a call on `RegorusEngine`.
*/
typedef enum RegorusStatus {
/**
* The operation was successful.
*/
RegorusStatusOk,
/**
* The operation was unsuccessful.
*/
RegorusStatusError,
} RegorusStatus;
/**
* Wrapper for `regorus::Engine`.
*/
typedef struct RegorusEngine RegorusEngine;
/**
* Result of a call on `RegorusEngine`.
*
* Must be freed using `regorus_result_drop`.
*/
typedef struct RegorusResult {
/**
* Status
*/
enum RegorusStatus status;
/**
* Output produced by the call.
* Owned by Rust.
*/
char *output;
/**
* Errors produced by the call.
* Owned by Rust.
*/
char *error_message;
} RegorusResult;
/**
* Drop a `RegorusResult`.
*
* `output` and `error_message` strings are not valid after drop.
*/
void regorus_result_drop(struct RegorusResult r);
/**
* Construct a new Engine
*
* See https://docs.rs/regorus/latest/regorus/struct.Engine.html
*/
struct RegorusEngine *regorus_engine_new(void);
/**
* Clone a [`RegorusEngine`]
*
* To avoid having to parse same policy again, the engine can be cloned
* after policies and data have been added.
*/
struct RegorusEngine *regorus_engine_clone(struct RegorusEngine *engine);
void regorus_engine_drop(struct RegorusEngine *engine);
/**
* Add a policy
*
* The policy is parsed into AST.
* See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy
*
* * `path`: A filename to be associated with the policy.
* * `rego`: Rego policy.
*/
struct RegorusResult regorus_engine_add_policy(struct RegorusEngine *engine,
const char *path,
const char *rego);
struct RegorusResult regorus_engine_add_policy_from_file(struct RegorusEngine *engine,
const char *path);
/**
* Add policy data.
*
* See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data
* * `data`: JSON encoded value to be used as policy data.
*/
struct RegorusResult regorus_engine_add_data_json(struct RegorusEngine *engine, const char *data);
struct RegorusResult regorus_engine_add_data_from_json_file(struct RegorusEngine *engine,
const char *path);
/**
* Clear policy data.
*
* See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data
*/
struct RegorusResult regorus_engine_clear_data(struct RegorusEngine *engine);
/**
* Set input.
*
* See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_input
* * `input`: JSON encoded value to be used as input to query.
*/
struct RegorusResult regorus_engine_set_input_json(struct RegorusEngine *engine, const char *input);
struct RegorusResult regorus_engine_set_input_from_json_file(struct RegorusEngine *engine,
const char *path);
/**
* Evaluate query.
*
* See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query
* * `query`: Rego expression to be evaluate.
*/
struct RegorusResult regorus_engine_eval_query(struct RegorusEngine *engine, const char *query);
#endif /* REGORUS_H */

View File

@@ -39,14 +39,14 @@ fn to_c_str(s: String) -> *mut c_char {
}
}
fn from_c_str(s: *const c_char) -> Result<String> {
fn from_c_str(name: &str, s: *const c_char) -> Result<String> {
if s.is_null() {
bail!("null pointer");
}
unsafe {
CStr::from_ptr(s)
.to_str()
.map_err(|_| anyhow!("`path`: invalid utf8"))
.map_err(|e| anyhow!("`{name}`: invalid utf8.\n{e}"))
.map(|s| s.to_string())
}
}
@@ -70,6 +70,21 @@ fn to_regorus_result(r: Result<()>) -> RegorusResult {
}
}
fn to_regorus_string_result(r: Result<String>) -> RegorusResult {
match r {
Ok(s) => RegorusResult {
status: RegorusStatus::RegorusStatusOk,
output: to_c_str(s),
error_message: std::ptr::null_mut(),
},
Err(e) => RegorusResult {
status: RegorusStatus::RegorusStatusError,
output: std::ptr::null_mut(),
error_message: to_c_str(format!("{e}")),
},
}
}
/// Wrapper for `regorus::Engine`.
#[derive(Clone)]
pub struct RegorusEngine {
@@ -81,10 +96,13 @@ pub struct RegorusEngine {
/// `output` and `error_message` strings are not valid after drop.
#[no_mangle]
pub extern "C" fn regorus_result_drop(r: RegorusResult) {
if !r.error_message.is_null() {
unsafe {
unsafe {
if !r.error_message.is_null() {
let _ = CString::from_raw(r.error_message);
}
if !r.output.is_null() {
let _ = CString::from_raw(r.output);
}
}
}
@@ -101,21 +119,20 @@ pub extern "C" fn regorus_engine_new() -> *mut RegorusEngine {
///
/// To avoid having to parse same policy again, the engine can be cloned
/// after policies and data have been added.
///
#[no_mangle]
pub extern "C" fn regorus_engine_clone(engine: *mut RegorusEngine) -> *mut RegorusEngine {
unsafe {
if engine.is_null() {
return std::ptr::null_mut();
}
Box::into_raw(Box::new((*engine).clone()))
match to_ref(&engine) {
Ok(e) => Box::into_raw(Box::new(e.clone())),
_ => std::ptr::null_mut(),
}
}
#[no_mangle]
pub extern "C" fn regorus_engine_drop(engine: *mut RegorusEngine) {
if !engine.is_null() {
if let Ok(e) = to_ref(&engine) {
unsafe {
let _ = Box::from_raw(engine);
let _ = Box::from_raw(std::ptr::from_mut(e));
}
}
}
@@ -127,29 +144,29 @@ pub extern "C" fn regorus_engine_drop(engine: *mut RegorusEngine) {
///
/// * `path`: A filename to be associated with the policy.
/// * `rego`: Rego policy.
#[no_mangle]
pub extern "C" fn regorus_engine_add_policy(
engine: *mut RegorusEngine,
path: *const c_char,
rego: *const c_char,
) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
to_regorus_string_result(|| -> Result<String> {
to_ref(&engine)?
.engine
.add_policy(from_c_str(path)?, from_c_str(rego)?)
.add_policy(from_c_str("path", path)?, from_c_str("rego", rego)?)
}())
}
#[cfg(feature = "std")]
#[no_mangle]
pub extern "C" fn regorus_engine_add_policy_from_file(
engine: *mut RegorusEngine,
path: *const c_char,
) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
to_regorus_string_result(|| -> Result<String> {
to_ref(&engine)?
.engine
.add_policy_from_file(from_c_str(path)?)
.add_policy_from_file(from_c_str("path", path)?)
}())
}
@@ -165,10 +182,23 @@ pub extern "C" fn regorus_engine_add_data_json(
to_regorus_result(|| -> Result<()> {
to_ref(&engine)?
.engine
.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
.add_data(regorus::Value::from_json_str(&from_c_str("data", data)?)?)
}())
}
/// Get list of loaded Rego packages as JSON.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
/// * `data`: JSON encoded value to be used as policy data.
#[no_mangle]
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
to_regorus_string_result(|| -> Result<String> {
serde_json::to_string_pretty(&to_ref(&engine)?.engine.get_packages()?)
.map_err(anyhow::Error::msg)
}())
}
#[cfg(feature = "std")]
#[no_mangle]
pub extern "C" fn regorus_engine_add_data_from_json_file(
engine: *mut RegorusEngine,
@@ -177,7 +207,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
to_regorus_result(|| -> Result<()> {
to_ref(&engine)?
.engine
.add_data(regorus::Value::from_json_file(&from_c_str(path)?)?)
.add_data(regorus::Value::from_json_file(from_c_str("path", path)?)?)
}())
}
@@ -204,11 +234,12 @@ pub extern "C" fn regorus_engine_set_input_json(
to_regorus_result(|| -> Result<()> {
to_ref(&engine)?
.engine
.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
.set_input(regorus::Value::from_json_str(&from_c_str("input", input)?)?);
Ok(())
}())
}
#[cfg(feature = "std")]
#[no_mangle]
pub extern "C" fn regorus_engine_set_input_from_json_file(
engine: *mut RegorusEngine,
@@ -217,7 +248,7 @@ pub extern "C" fn regorus_engine_set_input_from_json_file(
to_regorus_result(|| -> Result<()> {
to_ref(&engine)?
.engine
.set_input(regorus::Value::from_json_file(&from_c_str(path)?)?);
.set_input(regorus::Value::from_json_file(from_c_str("path", path)?)?);
Ok(())
}())
}
@@ -234,7 +265,7 @@ pub extern "C" fn regorus_engine_eval_query(
let output = || -> Result<String> {
let results = to_ref(&engine)?
.engine
.eval_query(from_c_str(query)?, false)?;
.eval_query(from_c_str("query", query)?, false)?;
Ok(serde_json::to_string_pretty(&results)?)
}();
match output {
@@ -246,3 +277,165 @@ pub extern "C" fn regorus_engine_eval_query(
Err(e) => to_regorus_result(Err(e)),
}
}
/// Evaluate specified rule.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_rule
/// * `rule`: Path to the rule.
#[no_mangle]
pub extern "C" fn regorus_engine_eval_rule(
engine: *mut RegorusEngine,
rule: *const c_char,
) -> RegorusResult {
let output = || -> Result<String> {
to_ref(&engine)?
.engine
.eval_rule(from_c_str("rule", rule)?)?
.to_json_str()
}();
match output {
Ok(out) => RegorusResult {
status: RegorusStatus::RegorusStatusOk,
output: to_c_str(out),
error_message: std::ptr::null_mut(),
},
Err(e) => to_regorus_result(Err(e)),
}
}
/// Enable/disable coverage.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_enable_coverage
/// * `enable`: Whether to enable or disable coverage.
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_set_enable_coverage(
engine: *mut RegorusEngine,
enable: bool,
) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
to_ref(&engine)?.engine.set_enable_coverage(enable);
Ok(())
}())
}
/// Get coverage report.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/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 {
let output = || -> Result<String> {
Ok(serde_json::to_string_pretty(
&to_ref(&engine)?.engine.get_coverage_report()?,
)?)
}();
match output {
Ok(out) => RegorusResult {
status: RegorusStatus::RegorusStatusOk,
output: to_c_str(out),
error_message: std::ptr::null_mut(),
},
Err(e) => to_regorus_result(Err(e)),
}
}
/// Get pretty printed coverage report.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_get_coverage_report_pretty(
engine: *mut RegorusEngine,
) -> RegorusResult {
let output = || -> Result<String> {
to_ref(&engine)?
.engine
.get_coverage_report()?
.to_string_pretty()
}();
match output {
Ok(out) => RegorusResult {
status: RegorusStatus::RegorusStatusOk,
output: to_c_str(out),
error_message: std::ptr::null_mut(),
},
Err(e) => to_regorus_result(Err(e)),
}
}
/// Clear coverage data.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/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 {
to_regorus_result(|| -> Result<()> {
to_ref(&engine)?.engine.clear_coverage_data();
Ok(())
}())
}
/// 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
/// * `enable`: Whether to enable or disable gathering print statements.
#[no_mangle]
pub extern "C" fn regorus_engine_set_gather_prints(
engine: *mut RegorusEngine,
enable: bool,
) -> RegorusResult {
to_regorus_result(|| -> Result<()> {
to_ref(&engine)?.engine.set_gather_prints(enable);
Ok(())
}())
}
/// Take all the gathered print statements.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/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> {
Ok(serde_json::to_string_pretty(
&to_ref(&engine)?.engine.take_prints()?,
)?)
}();
match output {
Ok(out) => RegorusResult {
status: RegorusStatus::RegorusStatusOk,
output: to_c_str(out),
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;
fn regorus_free(ptr: *mut u8);
}
#[cfg(feature = "custom_allocator")]
mod allocator {
use std::alloc::{GlobalAlloc, Layout};
struct RegorusAllocator {}
unsafe impl GlobalAlloc for RegorusAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size();
let align = layout.align();
crate::regorus_aligned_alloc(align, size)
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
crate::regorus_free(ptr)
}
}
#[global_allocator]
static ALLOCATOR: RegorusAllocator = RegorusAllocator {};
}

View File

@@ -1,3 +1,3 @@
module regorus-test
module regorus_test
go 1.21.5

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"os"
"regorus-test/pkg/regorus"
"regorus_test/pkg/regorus"
"time"
)
@@ -12,7 +12,7 @@ func main() {
var err error
t := time.Now();
// Create new engine
engine := regorus.NewEngine()
defer engine.Close()
@@ -26,10 +26,12 @@ func main() {
"../../tests/aci/policy.rego",
}
for _, policy := range policies {
if err := engine.AddPolicyFromFile(policy); err != nil {
var pkg string
if pkg, err = engine.AddPolicyFromFile(policy); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Loaded package %s\n", pkg);
}
if err = engine.AddDataFromJsonFile("../../tests/aci/data.json"); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
@@ -38,21 +40,48 @@ func main() {
elapsed2 := time.Since(t)
t = time.Now()
// Set input and eval query.
// Set input.
if err = engine.SetInputFromJsonFile("../../tests/aci/input.json"); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if output, err = engine.EvalQuery("data.framework.mount_overlay = x"); err != nil {
// Eval Rule
if output, err = engine.EvalRule("data.framework.mount_overlay"); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
elapsed3 := time.Since(t)
fmt.Println("{%s}", output)
fmt.Printf("%s\n", output)
fmt.Printf("NewEngine took %v\n", elapsed1)
fmt.Printf("Add policies and data took %v\n", elapsed2)
fmt.Printf("Set input and eval query took %v\n", elapsed3)
// Create new engine.
engine1 := regorus.NewEngine()
defer engine1.Close()
// Enable coverage
engine1.SetEnableCoverage(true)
var pkg string
pkg, err = engine1.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`")
fmt.Printf("Loaded package %s\n", pkg)
// Eval Rule
if output, err = engine1.EvalRule("data.test.message"); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("%s\n", output)
// Print pretty coverage report.
if output, err = engine1.GetCoverageReportPretty(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
fmt.Printf("%s\n", output)
}

View File

@@ -28,7 +28,7 @@ func (e *Engine) Clone() *Engine {
return c
}
func (e *Engine) AddPolicy(path string, rego string) error {
func (e *Engine) AddPolicy(path string, rego string) (string, error) {
path_c := C.CString(path)
defer C.free(unsafe.Pointer(path_c))
@@ -38,21 +38,21 @@ func (e *Engine) AddPolicy(path string, rego string) error {
result := C.regorus_engine_add_policy(e.e, path_c, rego_c)
defer C.regorus_result_drop(result)
if result.status != C.RegorusStatusOk {
return fmt.Errorf("%s", C.GoString(result.error_message))
return "", fmt.Errorf("%s", C.GoString(result.error_message))
}
return nil
return C.GoString(result.output), nil
}
func (e *Engine) AddPolicyFromFile(path string) error {
func (e *Engine) AddPolicyFromFile(path string) (string, error) {
path_c := C.CString(path)
defer C.free(unsafe.Pointer(path_c))
result := C.regorus_engine_add_policy_from_file(e.e, path_c)
defer C.regorus_result_drop(result)
if result.status != C.RegorusStatusOk {
return fmt.Errorf("%s", C.GoString(result.error_message))
return "", fmt.Errorf("%s", C.GoString(result.error_message))
}
return nil
return C.GoString(result.output), nil
}
func (e *Engine) AddDataJson(data string) error {
@@ -115,3 +115,73 @@ func (e *Engine) EvalQuery(query string) (string, error) {
return C.GoString(result.output), nil
}
func (e *Engine) EvalRule(rule string) (string, error) {
rule_c := C.CString(rule)
defer C.free(unsafe.Pointer(rule_c))
result := C.regorus_engine_eval_rule(e.e, rule_c)
defer C.regorus_result_drop(result)
if result.status != C.RegorusStatusOk {
return "", fmt.Errorf("%s", C.GoString(result.error_message))
}
return C.GoString(result.output), nil
}
func (e *Engine) SetEnableCoverage(enable bool) error {
result := C.regorus_engine_set_enable_coverage(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) ClearCoverageData() error {
result := C.regorus_engine_clear_coverage_data(e.e)
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) GetCoverageReport() (string, error) {
result := C.regorus_engine_get_coverage_report(e.e)
defer C.regorus_result_drop(result)
if result.status != C.RegorusStatusOk {
return "", fmt.Errorf("%s", C.GoString(result.error_message))
}
return C.GoString(result.output), nil
}
func (e *Engine) GetCoverageReportPretty() (string, error) {
result := C.regorus_engine_get_coverage_report_pretty(e.e)
defer C.regorus_result_drop(result)
if result.status != C.RegorusStatusOk {
return "", fmt.Errorf("%s", C.GoString(result.error_message))
}
return C.GoString(result.output), nil
}
func (e *Engine) SetGatherPrints(b bool) error {
result := C.regorus_engine_set_gather_prints(e.e, C.bool(b))
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) TakePrints() (string, error) {
result := C.regorus_engine_take_prints(e.e)
defer C.regorus_result_drop(result)
if result.status != C.RegorusStatusOk {
return "", fmt.Errorf("%s", C.GoString(result.error_message))
}
return C.GoString(result.output), nil
}

View File

@@ -1,6 +1,6 @@
[package]
name = "regorus-java"
version = "0.1.0"
version = "0.1.5"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/java"
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -10,8 +10,11 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
[lib]
crate-type = ["cdylib"]
[features]
default = ["regorus/std", "regorus/full-opa"]
[dependencies]
anyhow = "1.0.79"
anyhow = "1.0"
serde_json = "1.0.112"
jni = "0.21.1"
regorus = { path = "../.." }
regorus = { path = "../..", default-features = false, features = ["arc"] }

View File

@@ -8,38 +8,33 @@
See main [Regorus page](https://github.com/microsoft/regorus) for more details about the project.
## Building
Due to operational overhead we don't publish Java bindings to Maven Central
currently (see https://github.com/microsoft/regorus/issues/237) and you need to build from source to use it.
In order to build Regorus Java for a target platform, you need to install Rust target for that platform first:
```bash
$ rustup target add aarch64-apple-darwin
```
Afterwards, you can build native library for that target using:
```bash
$ 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 then need to build Java bindings using:
```bash
$ mvn package
```
And you will have a JAR at `./target/regorus-java-0.1.5.jar`.
## Usage
Regorus Java is published to Maven Central with native libraries for the following:
- 64-bit Linux (kernel 3.2+, glibc 2.17+)
- ARM64 Linux (kernel 4.1, glibc 2.17+)
- 64-bit macOS (10.12+, Sierra+)
- ARM64 macOS (11.0+, Big Sur+)
- 64-bit MSVC (Windows 7+)
If you need to run it in a different OS or an architecture you need to manually [build it](#Building).
If you're on one of the supported platforms, you can just pull prebuilt JAR from Maven Central by declaring a dependency on `com.microsoft.regorus:regorus-java`.
With [Maven](https://maven.apache.org/):
```xml
<dependencies>
<dependency>
<groupId>com.microsoft.regorus</groupId>
<artifactId>regorus-java</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
```
With [Gradle](https://gradle.org/):
```kotlin
// build.gradle.kts
implementation("com.microsoft.regorus:regorus-java:0.0.1")
```
Afterwards you can use it as follows:
You can use Regorus Java bindings as:
```java
import com.microsoft.regorus.Engine;
@@ -61,37 +56,14 @@ public class Test {
}
```
And you can see the following output once you run it:
```shell
{"result":[{"expressions":[{"value":"Hello, World!","text":"data.test.message","location":{"row":1,"col":1}}]}]}
```
You need to ensure artifacts built in [previous section](#building) are in Java's classpath.
## Building
In order to build Regorus Java for a target platform, you need to install Rust target
for that target platform first:
```bash
$ rustup target add aarch64-apple-darwin
```
Afterwards, you can build native library for that target using:
```bash
$ 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 can then build a JAR from source using:
```bash
$ mvn package
```
And you will have a JAR at `./target/regorus-java-0.0.1.jar`.
You need to make sure both of the artifacts in Java's classpath.
For example with `java` CLI:
```bash
$ java -Djava.library.path=../../target/aarch64-apple-darwin/release/ -cp target/regorus-java-0.0.1.jar Test.java
$ java -Djava.library.path=../../target/aarch64-apple-darwin/release/ -cp target/regorus-java-0.1.5.jar Test.java
```
should gave you the output:
```
{"result":[{"expressions":[{"value":"Hello, World!","text":"data.test.message","location":{"row":1,"col":1}}]}]}
```

36
bindings/java/Test.java Normal file
View File

@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
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])"
);
System.out.println("Loaded package " + pkg);
engine.addDataJson("{\"message\":\"World!\"}");
engine.setInputJson("{\"message\":\"Hello\"}");
// Evaluate query.
String resJson = engine.evalQuery("data.test.message");
System.out.println(resJson);
// Enable coverage.
engine.setEnableCoverage(true);
// Evaluate rule.
String valueJson = engine.evalRule("data.test.message");
System.out.println(valueJson);
String coverageJson = engine.getCoverageReport();
System.out.println(coverageJson);
System.out.println(engine.getCoverageReportPretty());
}
}
}

View File

@@ -17,18 +17,26 @@ JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeNewEngine
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeAddPolicy
* Signature: (JLjava/lang/String;Ljava/lang/String;)V
* Method: nativeClone
* Signature: (J)J
*/
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicy
JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeClone
(JNIEnv *, jclass, jlong);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeAddPolicy
* Signature: (JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicy
(JNIEnv *, jclass, jlong, jstring, jstring);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeAddPolicyFromFile
* Signature: (JLjava/lang/String;)V
* Signature: (JLjava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile
JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile
(JNIEnv *, jclass, jlong, jstring);
/*
@@ -79,6 +87,62 @@ JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromF
JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeEvalQuery
(JNIEnv *, jclass, jlong, jstring);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeEvalRule
* Signature: (JLjava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeEvalRule
(JNIEnv *, jclass, jlong, jstring);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeSetEnableCoverage
* Signature: (JZ)V
*/
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage
(JNIEnv *, jclass, jlong, jboolean);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeGetCoverageReport
* Signature: (J)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeGetCoverageReport
(JNIEnv *, jclass, jlong);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeGetCoverageReportAsColoredString
* Signature: (J)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeGetCoverageReportAsColoredString
(JNIEnv *, jclass, jlong);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeClearCoverageData
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeClearCoverageData
(JNIEnv *, jclass, jlong);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeSetGatherPrints
* Signature: (JZ)V
*/
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeSetGatherPrints
(JNIEnv *, jclass, jlong, jboolean);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeTakePrints
* Signature: (J)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeTakePrints
(JNIEnv *, jclass, jlong);
/*
* Class: com_microsoft_regorus_Engine
* Method: nativeDestroyEngine

View File

@@ -9,7 +9,7 @@
<groupId>com.microsoft.regorus</groupId>
<artifactId>regorus-java</artifactId>
<version>0.0.1</version>
<version>0.1.5</version>
<name>Regorus Java</name>
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>

View File

@@ -17,6 +17,17 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
Box::into_raw(Box::new(engine)) as jlong
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
_env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) -> jlong {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let c = engine.clone();
Box::into_raw(Box::new(c)) as jlong
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
env: JNIEnv,
@@ -24,14 +35,19 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
engine_ptr: jlong,
path: JString,
rego: JString,
) {
let _ = throw_err(env, |env| {
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = env.get_string(&path)?.into();
let rego: String = env.get_string(&rego)?.into();
engine.add_policy(path, rego)?;
Ok(())
let pkg = env.new_string(engine.add_policy(path, rego)?)?;
Ok(pkg.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
@@ -40,13 +56,37 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile
_class: JClass,
engine_ptr: jlong,
path: JString,
) {
let _ = throw_err(env, |env| {
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = env.get_string(&path)?.into();
engine.add_policy_from_file(path)?;
Ok(())
let pkg = env.new_string(engine.add_policy_from_file(path)?)?;
Ok(pkg.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPackages(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let packages = engine.get_packages()?;
let packages_json = env.new_string(serde_json::to_string_pretty(&packages)?)?;
Ok(packages_json.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
@@ -117,7 +157,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromF
let _ = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = env.get_string(&path)?.into();
engine.set_input(Value::from_json_file(&path)?);
engine.set_input(Value::from_json_file(path)?);
Ok(())
});
}
@@ -144,12 +184,133 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalQuery(
}
#[no_mangle]
pub unsafe extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalRule(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
rule: JString,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let rule: String = env.get_string(&rule)?.into();
let value = engine.eval_rule(rule)?;
let output = env.new_string(value.to_json_str()?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
enable: bool,
) {
let _ = throw_err(env, |_| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.set_enable_coverage(enable);
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let report = engine.get_coverage_report()?;
let output = env.new_string(serde_json::to_string_pretty(&report)?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReportPretty(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let report = engine.get_coverage_report()?.to_string_pretty()?;
let output = env.new_string(&report)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearCoverageData(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) {
let _ = throw_err(env, |_| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.clear_coverage_data();
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetGatherPrints(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
b: bool,
) {
let _ = throw_err(env, |_| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.set_gather_prints(b);
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeTakePrints(
env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let prints = engine.take_prints()?;
let output = env.new_string(serde_json::to_string_pretty(&prints)?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
_env: JNIEnv,
_class: JClass,
engine_ptr: jlong,
) {
let _engine = Box::from_raw(engine_ptr as *mut Engine);
unsafe {
let _engine = Box::from_raw(engine_ptr as *mut Engine);
}
}
fn throw_err<T>(mut env: JNIEnv, mut f: impl FnMut(&mut JNIEnv) -> Result<T>) -> Result<T> {

View File

@@ -16,20 +16,29 @@ import java.util.concurrent.atomic.AtomicReference;
/**
* Regorus Engine.
*/
public class Engine implements AutoCloseable {
public class Engine implements AutoCloseable, Cloneable {
// Methods exposed from Rust side, you can run
// `javac -h . src/main/java/com/microsoft/regorus/Engine.java` to update
// expected native header at `bindings/java/com_microsoft_regorus_Engine.h`
// if you update the native API.
private static native long nativeNewEngine();
private static native void nativeAddPolicy(long enginePtr, String path, String rego);
private static native void nativeAddPolicyFromFile(long enginePtr, String path);
private static native long nativeClone(long enginePtr);
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);
private static native void nativeClearData(long enginePtr);
private static native void nativeAddDataJson(long enginePtr, String data);
private static native void nativeAddDataJsonFromFile(long enginePtr, String path);
private static native void nativeSetInputJson(long enginePtr, String input);
private static native void nativeSetInputJsonFromFile(long enginePtr, String path);
private static native String nativeEvalQuery(long enginePtr, String query);
private static native String nativeEvalRule(long enginePtr, String qrule);
private static native void nativeSetEnableCoverage(long enginePtr, boolean enable);
private static native String nativeGetCoverageReport(long enginePtr);
private static native String nativeGetCoverageReportPretty(long enginePtr);
private static native void nativeClearCoverageData(long enginePtr);
private static native void nativeSetGatherPrints(long enginePtr, boolean b);
private static native String nativeTakePrints(long enginePtr);
private static native void nativeDestroyEngine(long enginePtr);
// Pointer to Engine allocated on Rust's heap, all native methods works on
@@ -43,25 +52,50 @@ public class Engine implements AutoCloseable {
enginePtr = nativeNewEngine();
}
Engine(long ptr) {
enginePtr = ptr;
}
/**
* Efficiently clones an Engine.
*/
public Engine Clone() {
return new Engine(nativeClone(enginePtr));
}
/**
* Adds an inline Rego policy.
*
* @param filename Filename of this Rego policy.
* @param rego Rego policy.
*
* @return Rego package defined in the policy.
*/
public void addPolicy(String filename, String rego) {
nativeAddPolicy(enginePtr, filename, rego);
public String addPolicy(String filename, String rego) {
return nativeAddPolicy(enginePtr, filename, rego);
}
/**
* Adds a Rego policy from given path.
*
* @param path Path of the Rego policy.
*
* @return Rego package defined in the policy.
*/
public void addPolicyFromFile(String path) {
nativeAddPolicyFromFile(enginePtr, path);
public String addPolicyFromFile(String path) {
return nativeAddPolicyFromFile(enginePtr, path);
}
/**
* Get list of loaded Rego packages.
*
* @return List of Rego packages as a JSON array of strings.
*/
public String getPackages() {
return nativeGetPackages(enginePtr);
}
/**
* Clears the data document.
*/
@@ -137,6 +171,70 @@ public class Engine implements AutoCloseable {
return nativeEvalQuery(enginePtr, query);
}
/**
* Evaluates given Rego rule and returns a JSON string as a result.
*
* @param rule Path of the Rego rule.
*
* @return Value of the rule as a JSON string.
*/
public String evalRule(String rule) {
return nativeEvalRule(enginePtr, rule);
}
/**
* Enable/disable coverage.
*
* @param enable Whether to enable coverage or not.
*
*/
public void setEnableCoverage(boolean enable) {
nativeSetEnableCoverage(enginePtr, enable);
}
/**
* Clear coverage data.
*
*/
public void clearCoverageData() {
nativeClearCoverageData(enginePtr);
}
/**
* Get coverage report as json string.
*
*/
public String getCoverageReport() {
return nativeGetCoverageReport(enginePtr);
}
/**
* Get coverage report as ANSI color coded string.
*
*/
public String getCoverageReportPretty() {
return nativeGetCoverageReportPretty(enginePtr);
}
/**
* Enable/disable gathering prints.
*
* @param b Whether to gather prints or not.
*
*/
public void setGatherPrints(boolean b) {
nativeSetGatherPrints(enginePtr, b);
}
/**
* Take gathered prints.
*
*/
public String takePrints() {
return nativeTakePrints(enginePtr);
}
@Override
public void close() {
nativeDestroyEngine(enginePtr);

View File

@@ -1,6 +1,6 @@
[package]
name = "regoruspy"
version = "0.1.0"
version = "0.1.5"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/python"
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -11,10 +11,13 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
[lib]
crate-type = ["cdylib"]
[features]
default = ["regorus/std", "regorus/full-opa"]
[dependencies]
anyhow = "1.0.79"
anyhow = "1.0"
ordered-float = "4.2.0"
pyo3 = {version = "0.21.0", features = ["anyhow", "extension-module"] }
regorus = { path = "../.." }
regorus = { path = "../..", default-features = false, features = ["arc"] }
serde_json = "1.0.112"

View File

@@ -10,7 +10,7 @@ Regorus can be used in Python via `regorus` package. (It is not yet available in
See [Repository](https://github.com/microsoft/regorus).
To build this binding, see [building](https://github.com/microsoft/regorus/bindings/python/building.md)
To build this binding, see [building](https://github.com/microsoft/regorus/blob/main/bindings/python/building.md)
## Usage
```Python
@@ -54,14 +54,11 @@ input = {
}
engine.set_input(input)
# Eval query
results = engine.eval_query('data.framework.mount_overlay=x')
# Eval rule
value = engine.eval_rule('data.framework.mount_overlay')
# Print results
print(results['result'][0])
# Print value
print(value)
# Eval query as json
results_json = engine.eval_query_as_json('data.framework.mount_overlay=x')
print(results_json)
```

View File

@@ -21,24 +21,12 @@ impl Default for Engine {
}
}
impl Clone for Engine {
/// Clone a [`Engine`]
///
/// To avoid having to parse same policy again, the engine can be cloned
/// after policies and data have been added.
fn clone(&self) -> Self {
Self {
engine: self.engine.clone(),
}
}
}
fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
// dicts
Ok(if let Ok(dict) = ob.downcast::<PyDict>() {
let mut map = BTreeMap::new();
for (k, v) in dict {
map.insert(from(k)?, from(v)?);
map.insert(from(&k)?, from(&v)?);
}
map.into()
}
@@ -46,7 +34,7 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
else if let Ok(pset) = ob.downcast::<PySet>() {
let mut set = BTreeSet::new();
for v in pset {
set.insert(from(v)?);
set.insert(from(&v)?);
}
set.into()
}
@@ -55,7 +43,7 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
//
let mut set = BTreeSet::new();
for v in pfset {
set.insert(from(v)?);
set.insert(from(&v)?);
}
set.into()
}
@@ -63,30 +51,30 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
else if let Ok(plist) = ob.downcast::<PyList>() {
let mut array = Vec::new();
for v in plist {
array.push(from(v)?);
array.push(from(&v)?);
}
array.into()
} else if let Ok(ptuple) = ob.downcast::<PyTuple>() {
let mut array = Vec::new();
for v in ptuple {
array.push(from(v)?);
array.push(from(&v)?);
}
array.into()
}
// String
else if let Ok(s) = String::extract(ob) {
else if let Ok(s) = ob.extract::<String>() {
s.into()
}
// Numeric
else if let Ok(v) = i64::extract(ob) {
else if let Ok(v) = ob.extract::<i64>() {
v.into()
} else if let Ok(v) = u64::extract(ob) {
} else if let Ok(v) = ob.extract::<u64>() {
v.into()
} else if let Ok(v) = f64::extract(ob) {
} else if let Ok(v) = ob.extract::<f64>() {
v.into()
}
// Boolean
else if let Ok(b) = bool::extract(ob) {
else if let Ok(b) = ob.extract::<bool>() {
b.into()
}
// None
@@ -97,7 +85,7 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
else if let Ok(pseq) = ob.downcast::<PySequence>() {
let mut array = Vec::new();
for i in 0..pseq.len()? {
array.push(from(pseq.get_item(i)?)?);
array.push(from(&pseq.get_item(i)?)?);
}
array.into()
}
@@ -109,7 +97,7 @@ fn from<'source>(ob: &'source PyAny) -> Result<Value, PyErr> {
for i in 0..keys.len()? {
let key = keys.get_item(i)?;
let value = values.get_item(i)?;
map.insert(from(key)?, from(value)?);
map.insert(from(&key)?, from(&value)?);
}
map.into()
} else {
@@ -140,24 +128,24 @@ fn to(mut v: Value, py: Python<'_>) -> Result<PyObject> {
}
Value::Array(_) => {
let list = PyList::empty(py);
for v in std::mem::replace(v.as_array_mut()?, Vec::new()) {
let list = PyList::empty_bound(py);
for v in std::mem::take(v.as_array_mut()?) {
list.append(to(v, py)?)?;
}
list.into()
}
Value::Set(_) => {
let set = PySet::empty(py)?;
for v in std::mem::replace(v.as_set_mut()?, BTreeSet::new()) {
let set = PySet::empty_bound(py)?;
for v in std::mem::take(v.as_set_mut()?) {
set.add(to(v, py)?)?;
}
set.into()
}
Value::Object(_) => {
let dict = PyDict::new(py);
for (k, v) in std::mem::replace(v.as_object_mut()?, BTreeMap::new()) {
let dict = PyDict::new_bound(py);
for (k, v) in std::mem::take(v.as_object_mut()?) {
dict.set_item(to(k, py)?, to(v, py)?)?;
}
dict.into()
@@ -181,7 +169,7 @@ impl Engine {
///
/// * `path`: A filename to be associated with the policy.
/// * `rego`: Rego policy.
pub fn add_policy(&mut self, path: String, rego: String) -> Result<()> {
pub fn add_policy(&mut self, path: String, rego: String) -> Result<String> {
self.engine.add_policy(path, rego)
}
@@ -190,15 +178,21 @@ impl Engine {
/// The policy is parsed into AST.
///
/// * `path`: Path to the policy file.
pub fn add_policy_from_file(&mut self, path: String) -> Result<()> {
pub fn add_policy_from_file(&mut self, path: String) -> Result<String> {
self.engine.add_policy_from_file(path)
}
/// Get the list of packages defined by loaded policies.
///
pub fn get_packages(&mut self) -> Result<Vec<String>> {
self.engine.get_packages()
}
/// Add policy data.
///
/// * `data`: Rego value. A Rego value is a number, bool, string, None
/// or a list/set/map whose items themselves are Rego values.
pub fn add_data(&mut self, data: &PyAny) -> Result<()> {
pub fn add_data(&mut self, data: &Bound<'_, PyAny>) -> Result<()> {
let data = from(data)?;
self.engine.add_data(data)
}
@@ -215,7 +209,7 @@ impl Engine {
///
/// * `path`: Path to JSON policy data.
pub fn add_data_from_json_file(&mut self, path: String) -> Result<()> {
let data = Value::from_json_file(&path)?;
let data = Value::from_json_file(path)?;
self.engine.add_data(data)
}
@@ -229,7 +223,7 @@ impl Engine {
///
/// * `input`: Rego value. A Rego value is a number, bool, string, None
/// or a list/set/map whose items themselves are Rego values.
pub fn set_input(&mut self, input: &PyAny) -> Result<()> {
pub fn set_input(&mut self, input: &Bound<'_, PyAny>) -> Result<()> {
let input = from(input)?;
self.engine.set_input(input);
Ok(())
@@ -248,7 +242,7 @@ impl Engine {
///
/// * `path`: Path to JSON input data.
pub fn set_input_from_json_file(&mut self, path: String) -> Result<()> {
let input = Value::from_json_file(&path)?;
let input = Value::from_json_file(path)?;
self.engine.set_input(input);
Ok(())
}
@@ -259,17 +253,17 @@ 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(py);
let rlist = PyList::empty_bound(py);
for result in results.result.into_iter() {
let rdict = PyDict::new(py);
let rdict = PyDict::new_bound(py);
let elist = PyList::empty(py);
let elist = PyList::empty_bound(py);
for expr in result.expressions.into_iter() {
let edict = PyDict::new(py);
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 ldict = PyDict::new(py);
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))?;
@@ -281,7 +275,7 @@ impl Engine {
rdict.set_item("bindings".to_object(py), to(result.bindings, py)?)?;
rlist.append(rdict)?;
}
let dict = PyDict::new(py);
let dict = PyDict::new_bound(py);
dict.set_item("result".to_object(py), rlist)?;
Ok(dict.into())
}
@@ -293,9 +287,72 @@ impl Engine {
let results = self.engine.eval_query(query, false)?;
serde_json::to_string_pretty(&results).map_err(|e| anyhow!("{e}"))
}
/// Evaluate rule.
///
/// * `rule`: Full path to the rule.
pub fn eval_rule(&mut self, rule: String, py: Python<'_>) -> Result<PyObject> {
to(self.engine.eval_rule(rule)?, py)
}
/// Evaluate rule and return value as json.
///
/// * `rule`: Full path to the rule.
pub fn eval_rule_as_json(&mut self, rule: String) -> Result<String> {
let v = self.engine.eval_rule(rule)?;
v.to_json_str()
}
/// Enable code coverage
///
/// * `enable`: Whether to enable coverage or not.
pub fn set_enable_coverage(&mut self, enable: bool) {
self.engine.set_enable_coverage(enable)
}
/// Get coverage report as json.
///
pub fn get_coverage_report_as_json(&self) -> Result<String> {
let report = self.engine.get_coverage_report()?;
serde_json::to_string_pretty(&report).map_err(|e| anyhow!("{e}"))
}
/// Get coverage report as pretty printable string.
///
pub fn get_coverage_report_pretty(&self) -> Result<String> {
self.engine.get_coverage_report()?.to_string_pretty()
}
/// Clear coverage data.
///
pub fn clear_coverage_data(&mut self) {
self.engine.clear_coverage_data();
}
/// Gather print statements instead of printing to stderr.
///
pub fn set_gather_prints(&mut self, b: bool) {
self.engine.set_gather_prints(b)
}
/// Take gathered prints.
///
pub fn take_prints(&mut self) -> Result<Vec<String>> {
self.engine.take_prints()
}
/// Clone a [`Engine`]
///
/// To avoid having to parse same policy again, the engine can be cloned
/// after policies and data have been added.
fn clone(&self) -> Self {
Self {
engine: self.engine.clone(),
}
}
}
#[pymodule]
pub fn regorus(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
pub fn regorus(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<crate::Engine>()
}

View File

@@ -7,9 +7,14 @@ import regorus
engine = regorus.Engine()
# Load policies
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')
pkg = engine.add_policy_from_file('../../tests/aci/framework.rego')
print(' Loaded package %s' % pkg)
pkg = engine.add_policy_from_file('../../tests/aci/api.rego')
print(' Loaded package %s' % pkg)
pkg = engine.add_policy_from_file('../../tests/aci/policy.rego')
print(' Loaded package %s' % pkg)
# Add policy data
data = {
@@ -50,3 +55,40 @@ print(results['result'][0])
# Eval query as json
results_json = engine.eval_query_as_json('data.framework.mount_overlay=x')
print(results_json)
# Eval rule
v = engine.eval_rule('data.framework.mount_overlay')
print(v)
# Eval rule as json
v = engine.eval_rule_as_json('data.framework.mount_overlay')
print(v)
# Enable coverage
engine.set_enable_coverage(True)
engine.eval_rule('data.framework.mount_overlay')
# Print coverage
report_json = engine.get_coverage_report_as_json()
print(report_json)
# Pretty coverage report
report = engine.get_coverage_report_pretty()
print(report)
# Clone engine
engine1 = engine.clone()
# Clear coverage data
engine.clear_coverage_data();
print(engine1.get_coverage_report_pretty())
# Enable gathering prints
engine1.set_gather_prints(True)
# Gather prints
engine1.eval_query('print("Hello")')
ps = engine1.take_prints()
print(ps)

View File

@@ -1 +1 @@
ruby 3.3.0
ruby 3.3.1

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.16"
gem "rake", "~> 13.0"
gem "minitest", "~> 5.23"
gem "rake", "~> 13.2"
gem "rake-compiler"
gem "rake-compiler-dock"
gem "rubocop", "~> 1.62", require: false
gem "rubocop", "~> 1.64", require: false
gem "rubocop-minitest", require: false
gem "rubocop-rake", require: false

View File

@@ -1,8 +1,8 @@
PATH
remote: .
specs:
regorusrb (0.1.0)
rb_sys (~> 0.9.91)
regorusrb (0.1.5)
rb_sys (~> 0.9.97)
GEM
remote: https://rubygems.org/
@@ -10,21 +10,22 @@ GEM
ast (2.4.2)
json (2.7.2)
language_server-protocol (3.17.0.3)
minitest (5.22.3)
minitest (5.23.1)
parallel (1.24.0)
parser (3.3.0.5)
parser (3.3.1.0)
ast (~> 2.4.1)
racc
racc (1.7.3)
racc (1.8.0)
rainbow (3.1.1)
rake (13.2.1)
rake-compiler (1.2.7)
rake
rake-compiler-dock (1.4.0)
rb_sys (0.9.91)
regexp_parser (2.9.0)
rexml (3.2.6)
rubocop (1.63.0)
rake-compiler-dock (1.5.0)
rb_sys (0.9.97)
regexp_parser (2.9.2)
rexml (3.2.8)
strscan (>= 3.0.9)
rubocop (1.64.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
@@ -35,14 +36,15 @@ GEM
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-ast (1.31.3)
parser (>= 3.3.1.0)
rubocop-minitest (0.35.0)
rubocop (>= 1.61, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rake (0.6.0)
rubocop (~> 1.0)
ruby-progressbar (1.13.0)
strscan (3.1.0)
unicode-display_width (2.5.0)
PLATFORMS
@@ -50,14 +52,14 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
minitest (~> 5.16)
rake (~> 13.0)
minitest (~> 5.23)
rake (~> 13.2)
rake-compiler
rake-compiler-dock
regorusrb!
rubocop (~> 1.62)
rubocop (~> 1.64)
rubocop-minitest
rubocop-rake
BUNDLED WITH
2.5.7
2.5.10

View File

@@ -93,7 +93,7 @@ puts results_json
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To update the gem version or dependencies within `bindings/ruby/Gemfile.lock`, run `bundle update --gemfile=bindings/ruby/Gemfile` from the project directory, or `cd bindings/ruby && bundle update`
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
After checking out the repo, `cd bindings/ruby` and run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

View File

@@ -1,6 +1,6 @@
[package]
name = "regorusrb"
version = "0.1.0"
version = "0.1.5"
edition = "2021"
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
publish = false
@@ -9,8 +9,11 @@ publish = false
crate-type = ["cdylib"]
path = "src/lib.rs"
[features]
default = ["regorus/std", "regorus/full-opa"]
[dependencies]
magnus = { version = "0.6.3" }
regorus = { git = "https://github.com/microsoft/regorus" }
serde_json = "1.0.115"
magnus = { version = "0.6.4" }
regorus = { git = "https://github.com/microsoft/regorus", default-features = false, features = ["arc"] }
serde_json = "1.0.117"
serde_magnus = "0.8.1"

View File

@@ -36,14 +36,14 @@ impl Engine {
}
}
fn add_policy(&self, path: String, rego: String) -> Result<(), Error> {
fn add_policy(&self, path: String, rego: String) -> Result<String, Error> {
self.engine
.borrow_mut()
.add_policy(path, rego)
.map_err(|e| Error::new(runtime_error(), format!("Failed to add policy: {}", e)))
}
fn add_policy_from_file(&self, path: String) -> Result<(), Error> {
fn add_policy_from_file(&self, path: String) -> Result<String, Error> {
self.engine
.borrow_mut()
.add_policy_from_file(path)
@@ -72,7 +72,7 @@ impl Engine {
}
fn add_data_from_json_file(&self, path: String) -> Result<(), Error> {
let json_data = regorus::Value::from_json_file(&path).map_err(|e| {
let json_data = regorus::Value::from_json_file(path).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to parse JSON data file: {}", e),
@@ -112,7 +112,7 @@ impl Engine {
}
fn add_input_from_json_file(&self, path: String) -> Result<(), Error> {
let json_data = regorus::Value::from_json_file(&path).map_err(|e| {
let json_data = regorus::Value::from_json_file(path).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to parse JSON input file: {}", e),
@@ -191,6 +191,71 @@ impl Engine {
fn eval_deny_query(&self, query: String) -> Result<bool, Error> {
Ok(self.engine.borrow_mut().eval_deny_query(query, false))
}
fn set_enable_coverage(&self, enable: bool) -> Result<(), Error> {
self.engine.borrow_mut().set_enable_coverage(enable);
Ok(())
}
fn get_coverage_report_as_json(&self) -> Result<String, Error> {
let report = self
.engine
.borrow_mut()
.get_coverage_report()
.map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to get coverage report as json: {}", e),
)
})?;
serde_json::to_string(&report).map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to serialize coverage report: {}", e),
)
})
}
fn get_coverage_report_pretty(&self) -> Result<String, Error> {
let report = self
.engine
.borrow_mut()
.get_coverage_report()
.map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to get coverage report: {}", e),
)
})?;
report.to_string_pretty().map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to convert report to colored string: {}", e),
)
})
}
fn clear_coverage_data(&self) -> Result<(), Error> {
self.engine.borrow_mut().clear_coverage_data();
Ok(())
}
// Print statements can be gathered async instead of printing to stderr
fn set_gather_prints(&self, enable: bool) -> Result<(), Error> {
self.engine.borrow_mut().set_gather_prints(enable);
Ok(())
}
fn take_prints(&self) -> Result<Vec<String>, Error> {
self.engine.borrow_mut().take_prints().map_err(|e| {
Error::new(
runtime_error(),
format!("Failed to gather print statement: {}", e),
)
})
}
}
#[magnus::init]
@@ -237,5 +302,28 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
engine_class.define_method("eval_bool_query", method!(Engine::eval_bool_query, 1))?;
engine_class.define_method("eval_allow_query", method!(Engine::eval_allow_query, 1))?;
engine_class.define_method("eval_deny_query", method!(Engine::eval_deny_query, 1))?;
// coverage operations
engine_class.define_method(
"set_enable_coverage",
method!(Engine::set_enable_coverage, 1),
)?;
engine_class.define_method(
"get_coverage_report_as_json",
method!(Engine::get_coverage_report_as_json, 0),
)?;
engine_class.define_method(
"get_coverage_report_pretty",
method!(Engine::get_coverage_report_pretty, 0),
)?;
engine_class.define_method(
"clear_coverage_data",
method!(Engine::clear_coverage_data, 0),
)?;
// print statements
engine_class.define_method("set_gather_prints", method!(Engine::set_gather_prints, 1))?;
engine_class.define_method("take_prints", method!(Engine::take_prints, 0))?;
Ok(())
}

View File

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

View File

@@ -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.91"
spec.add_dependency "rb_sys", "~> 0.9.97"
end

View File

@@ -59,7 +59,8 @@ class TestRegorus < Minitest::Test
end
def test_policy_addition
assert_silent { @engine.add_policy("example.rego", example_policy) }
# returns the package name from the REGO, not the filename
assert_equal "data.regorus_test", @engine.add_policy("example.rego", example_policy)
end
def test_object_creation_with_new
@@ -155,6 +156,33 @@ class TestRegorus < Minitest::Test
refute_same @engine, cloned_engine
end
def test_coverage_printing_json
@engine.set_input(input_for(ALICE))
@engine.set_enable_coverage(true)
@engine.eval_rule("data.regorus_test.is_employee")
assert_match(/covered":\[7\],"not_covered":\[3,11,12,15,19\]/, @engine.get_coverage_report_as_json)
end
def test_coverage_printing_pretty
@engine.set_input(input_for(ALICE))
@engine.set_enable_coverage(true)
@engine.eval_rule("data.regorus_test.is_employee")
# to see the colors in the ruby terminal, use `puts @engine.get_coverage_report``
pretty_coverage_report = @engine.get_coverage_report_pretty
assert_match(/\e\[31m 3 input.name == data.managers\[_\]/, pretty_coverage_report)
assert_match(/\e\[32m 7 input.name == data.employees\[_\]/, pretty_coverage_report)
end
def test_gather_print_statements
@engine.set_gather_prints(true)
@engine.eval_query('print("Hello")')
assert_equal ["<query.rego>:1: Hello"], @engine.take_prints
end
def alice_results
{
result: [

View File

@@ -1,6 +1,6 @@
[package]
name = "regorusjs"
version = "0.1.0"
version = "0.1.5"
edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/wasm"
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
@@ -10,8 +10,11 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
[lib]
crate-type = ["cdylib"]
[features]
default = ["regorus/std", "regorus/full-opa"]
[dependencies]
regorus = { path = "../.." }
regorus = { path = "../..", default-features = false, features = ["arc"] }
serde_json = "1.0.111"
wasm-bindgen = "0.2.90"

View File

@@ -10,72 +10,10 @@
See [Repository](https://github.com/microsoft/regorus).
To build this binding, see [building](https://github.com/microsoft/regorus/bindings/wasm/building.md)
To build this binding, see [building.md](https://github.com/microsoft/regorus/blob/main/bindings/wasm/building.md)
## Usage
```javascript
var regorus = require('regorusjs')
// Create an engine.
var engine = new regorus.Engine();
// Add Rego policy.
engine.add_policy(
// Associate this file name with policy
'hello.rego',
// Rego policy
`
package test
# Join messages
message = concat(", ", [input.message, data.message])
`)
// Set policy data
engine.add_data_json(`
{
"message" : "World!"
}
`)
// Set policy input
engine.set_input_json(`
{
"message" : "Hello"
}
`)
// Eval query
results = engine.eval_query('data.test.message')
// Display
console.log(results)
// {
// "result": [
// {
// "expressions": [
// {
// "value": "Hello, World!",
// "text": "data.test.message",
// "location": {
// "row": 1,
// "col": 1
// }
// }
// ]
// }
// ]
// }
// Convert results to object
results = JSON.parse(results)
// Process result
console.log(results.result[0].expressions[0].value)
// Hello, World!
```
See [test.js](https://github.com/microsoft/regorus/blob/main/bindings/wasm/test.js) for example usage.

View File

@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(non_snake_case)]
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
@@ -50,7 +52,7 @@ impl Engine {
///
/// * `path`: A filename to be associated with the policy.
/// * `rego`: Rego policy.
pub fn add_policy(&mut self, path: String, rego: String) -> Result<(), JsValue> {
pub fn addPolicy(&mut self, path: String, rego: String) -> Result<String, JsValue> {
self.engine.add_policy(path, rego).map_err(error_to_jsvalue)
}
@@ -58,15 +60,22 @@ impl Engine {
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data
/// * `data`: JSON encoded value to be used as policy data.
pub fn add_data_json(&mut self, data: String) -> Result<(), JsValue> {
pub fn addDataJson(&mut self, data: String) -> Result<(), JsValue> {
let data = regorus::Value::from_json_str(&data).map_err(error_to_jsvalue)?;
self.engine.add_data(data).map_err(error_to_jsvalue)
}
/// Get the list of packages defined by loaded policies.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
pub fn getPackages(&self) -> Result<Vec<String>, JsValue> {
self.engine.get_packages().map_err(error_to_jsvalue)
}
/// Clear policy data.
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data
pub fn clear_data(&mut self) -> Result<(), JsValue> {
pub fn clearData(&mut self) -> Result<(), JsValue> {
self.engine.clear_data();
Ok(())
}
@@ -75,7 +84,7 @@ impl Engine {
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_input
/// * `input`: JSON encoded value to be used as input to query.
pub fn set_input_json(&mut self, input: String) -> Result<(), JsValue> {
pub fn setInputJson(&mut self, input: String) -> Result<(), JsValue> {
let input = regorus::Value::from_json_str(&input).map_err(error_to_jsvalue)?;
self.engine.set_input(input);
Ok(())
@@ -85,13 +94,75 @@ impl Engine {
///
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query
/// * `query`: Rego expression to be evaluate.
pub fn eval_query(&mut self, query: String) -> Result<String, JsValue> {
pub fn evalQuery(&mut self, query: String) -> Result<String, JsValue> {
let results = self
.engine
.eval_query(query, false)
.map_err(error_to_jsvalue)?;
serde_json::to_string_pretty(&results).map_err(error_to_jsvalue)
}
/// Evaluate rule(s) at given path.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule
///
/// * `path`: The full path to the rule(s).
pub fn evalRule(&mut self, path: String) -> Result<String, JsValue> {
let v = self.engine.eval_rule(path).map_err(error_to_jsvalue)?;
v.to_json_str().map_err(error_to_jsvalue)
}
/// Gather output from print statements instead of emiting to stderr.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints
/// * `b`: Whether to enable gathering prints or not.
pub fn setGatherPrints(&mut self, b: bool) {
self.engine.set_gather_prints(b)
}
/// Take the gathered output of print statements.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints
pub fn takePrints(&mut self) -> Result<Vec<String>, JsValue> {
self.engine.take_prints().map_err(error_to_jsvalue)
}
/// Enable/disable policy coverage.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage
/// * `b`: Whether to enable gathering coverage or not.
pub fn setEnableCoverage(&mut self, enable: bool) {
self.engine.set_enable_coverage(enable)
}
/// Get the coverage report as json.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report
pub fn getCoverageReport(&self) -> Result<String, JsValue> {
let report = self
.engine
.get_coverage_report()
.map_err(error_to_jsvalue)?;
serde_json::to_string_pretty(&report).map_err(error_to_jsvalue)
}
/// Clear gathered coverage data.
///
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data
pub fn clearCoverageData(&mut self) {
self.engine.clear_coverage_data()
}
/// Get ANSI color coded coverage report.
///
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
pub fn getCoverageReportPretty(&self) -> Result<String, JsValue> {
let report = self
.engine
.get_coverage_report()
.map_err(error_to_jsvalue)?;
report.to_string_pretty().map_err(error_to_jsvalue)
}
}
#[cfg(test)]
@@ -102,9 +173,10 @@ mod tests {
#[wasm_bindgen_test]
pub fn basic() -> Result<(), JsValue> {
let mut engine = crate::Engine::new();
engine.setEnableCoverage(true);
// Exercise all APIs.
engine.add_data_json(
engine.addDataJson(
r#"
{
"foo" : "bar"
@@ -113,7 +185,7 @@ mod tests {
.to_string(),
)?;
engine.set_input_json(
engine.setInputJson(
r#"
{
"message" : "Hello"
@@ -122,15 +194,16 @@ mod tests {
.to_string(),
)?;
engine.add_policy(
let pkg = engine.addPolicy(
"hello.rego".to_string(),
r#"
package test
message = input.message"#
.to_string(),
)?;
assert_eq!(pkg, "data.test");
let results = engine.eval_query("data".to_string())?;
let results = engine.evalQuery("data".to_string())?;
let r = regorus::Value::from_json_str(&results).map_err(crate::error_to_jsvalue)?;
let v = &r["result"][0]["expressions"][0]["value"];
@@ -141,6 +214,38 @@ mod tests {
// Test that data was set.
assert_eq!(v["foo"], regorus::Value::from("bar"));
// Use eval_rule to perform same query.
let v = engine.evalRule("data.test.message".to_owned())?;
let v = regorus::Value::from_json_str(&v).map_err(crate::error_to_jsvalue)?;
// Ensure that input and policy were evaluated.
assert_eq!(v, regorus::Value::from("Hello"));
let pkgs = engine.getPackages()?;
assert_eq!(pkgs, vec!["data.test"]);
engine.setGatherPrints(true);
let _ = engine.evalQuery("print(\"Hello\")".to_owned());
let prints = engine.takePrints()?;
assert_eq!(prints, vec!["<query.rego>:1: Hello"]);
// Test clone.
let mut engine1 = engine.clone();
// Test code coverage.
let report = engine1.getCoverageReport()?;
let r = regorus::Value::from_json_str(&report).map_err(crate::error_to_jsvalue)?;
assert_eq!(
r["files"][0]["covered"]
.as_array()
.map_err(crate::error_to_jsvalue)?,
&vec![regorus::Value::from(3)]
);
println!("{}", engine1.getCoverageReportPretty()?);
engine1.clearCoverageData();
Ok(())
}
}

View File

@@ -1,47 +1,87 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var regorus = require('./pkg/regorusjs')
var regorus = require('./pkg/regorusjs');
// Create an engine.
var engine = new regorus.Engine();
// Enable code coverage
engine.setEnableCoverage(true);
// Add Rego policy.
engine.add_policy(
var pkg = engine.addPolicy(
// Associate this file name with policy
'hello.rego',
// Rego policy
`
package test
x = 10
# Join messages
message = concat(", ", [input.message, data.message])
`)
`);
console.log(pkg);
// data.test
// Set policy data
engine.add_data_json(`
engine.addDataJson(`
{
"message" : "World!"
}
`)
`);
// Set policy input
engine.set_input_json(`
engine.setInputJson(`
{
"message" : "Hello"
}
`)
`);
// Eval rule as json
var value = engine.evalRule('data.test.message');
value = JSON.parse(value);
// Display value
console.log(value);
// Hello, World!
// Eval query
results = engine.eval_query('data.test.message')
results = engine.evalQuery('data.test.message');
// Display
console.log(results)
console.log(results);
// {
// "result": [
// {
// "expressions": [
// {
// "value": "Hello, World!",
// "text": "data.test.message",
// "location": {
// "row": 1,
// "col": 1
// }
// }
// ]
// }
// ]
// }
// Convert results to object
results = JSON.parse(results)
results = JSON.parse(results);
// Process result
console.log(results.result[0].expressions[0].value)
console.log(results.result[0].expressions[0].value);
// Hello, World!
// Print coverage report
report = engine.getCoverageReport();
console.log(report);
// Print pretty report.
report = engine.getCoverageReportPretty();
console.log(report);

View File

@@ -159,24 +159,25 @@ In future, each builtin will be associated with a feature (many builtins could b
| [type_name](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-type_name) | _ |
- [Encoding](https://www.openpolicyagent.org/docs/latest/policy-reference/#encoding)
| Builtin | Feature |
|----------------------------------------------------------------------------------------------------------------------------------|-------------|
| [base64.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64is_valid) | `base64` |
| [base64url.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urldecode) | `base64` |
| [base64url.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode) | `base64url` |
| [base64url.encode_no_pad](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode_no_pad) | `base64url` |
| [hex.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexdecode) | `hex` |
| [hex.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexencode) | `hex` |
| [json.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonis_valid) | _ |
| [json.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonmarshal) | _ |
| [json.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonunmarshal) | _ |
| [urlquery.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode) | `urlquery` |
| [urlquery.decode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode_object) | `urlquery` |
| [urlquery.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode) | `urlquery` |
| [urlquery.encode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode_object) | `urlquery` |
| [yaml.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlis_valid) | `yaml` |
| [yaml.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlmarshal) | `yaml` |
| [yaml.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlunmarshal) | `yaml` |
| Builtin | Feature |
|--------------------------------------------------------------------------------------------------------------------------------------|-------------|
| [base64.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64is_valid) | `base64` |
| [base64url.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urldecode) | `base64` |
| [base64url.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode) | `base64url` |
| [base64url.encode_no_pad](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode_no_pad) | `base64url` |
| [hex.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexdecode) | `hex` |
| [hex.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexencode) | `hex` |
| [json.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonis_valid) | _ |
| [json.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonmarshal) | _ |
| [json.marshal_with_options](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonmarshal_with_options) | _ |
| [json.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonunmarshal) | _ |
| [urlquery.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode) | `urlquery` |
| [urlquery.decode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode_object) | `urlquery` |
| [urlquery.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode) | `urlquery` |
| [urlquery.encode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode_object) | `urlquery` |
| [yaml.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlis_valid) | `yaml` |
| [yaml.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlmarshal) | `yaml` |
| [yaml.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlunmarshal) | `yaml` |
- [Time](https://www.openpolicyagent.org/docs/latest/policy-reference/#time)
| Builtin | Feature |

View File

@@ -1,7 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use anyhow::{bail, Result};
use anyhow::{anyhow, bail, Result};
#[allow(dead_code)]
fn read_file(path: &String) -> Result<String> {
std::fs::read_to_string(path).map_err(|_| anyhow!("could not read {path}"))
}
#[allow(unused_variables)]
fn read_value_from_yaml_file(path: &String) -> Result<regorus::Value> {
#[cfg(feature = "yaml")]
return regorus::Value::from_yaml_file(path);
#[cfg(not(feature = "yaml"))]
bail!("regorus has not been built with yaml support");
}
fn read_value_from_json_file(path: &String) -> Result<regorus::Value> {
#[cfg(feature = "std")]
return regorus::Value::from_json_file(path);
#[cfg(not(feature = "std"))]
regorus::Value::from_json_str(&read_file(path)?)
}
fn add_policy_from_file(engine: &mut regorus::Engine, path: String) -> Result<String> {
#[cfg(feature = "std")]
return engine.add_policy_from_file(path);
#[cfg(not(feature = "std"))]
engine.add_policy(path.clone(), read_file(&path)?)
}
fn rego_eval(
bundles: &[String],
@@ -35,7 +65,7 @@ fn rego_eval(
_ => continue,
}
engine.add_policy_from_file(entry.path())?;
let _package = add_policy_from_file(&mut engine, entry.path().display().to_string())?;
}
}
@@ -43,15 +73,15 @@ fn rego_eval(
for file in files.iter() {
if file.ends_with(".rego") {
// Read policy file.
engine.add_policy_from_file(file)?;
let _package = add_policy_from_file(&mut engine, file.clone())?;
} else {
// Read data file.
let data = if file.ends_with(".json") {
regorus::Value::from_json_file(file)?
read_value_from_json_file(file)?
} else if file.ends_with(".yaml") {
regorus::Value::from_yaml_file(file)?
read_value_from_yaml_file(file)?
} else {
bail!("Unsupported data file `{file}`. Must be rego, json or yaml.")
bail!("Unsupported data file `{file}`. Must be rego, json or yaml.");
};
// Merge given data.
@@ -61,9 +91,9 @@ fn rego_eval(
if let Some(file) = input {
let input = if file.ends_with(".json") {
regorus::Value::from_json_file(&file)?
read_value_from_json_file(&file)?
} else if file.ends_with(".yaml") {
regorus::Value::from_yaml_file(&file)?
read_value_from_yaml_file(&file)?
} else {
bail!("Unsupported input file `{file}`. Must be json or yaml.")
};
@@ -85,7 +115,7 @@ fn rego_eval(
#[cfg(feature = "coverage")]
if coverage {
let report = engine.get_coverage_report()?;
println!("{}", report.to_colored_string()?);
println!("{}", report.to_string_pretty()?);
}
Ok(())
@@ -95,8 +125,12 @@ fn rego_lex(file: String, verbose: bool) -> Result<()> {
use regorus::unstable::*;
// Create source.
#[cfg(feature = "std")]
let source = Source::from_file(file)?;
#[cfg(not(feature = "std"))]
let source = Source::from_contents(file.clone(), read_file(&file)?)?;
// Create lexer.
let mut lexer = Lexer::new(&source);
@@ -122,8 +156,12 @@ fn rego_parse(file: String) -> Result<()> {
use regorus::unstable::*;
// Create source.
#[cfg(feature = "std")]
let source = Source::from_file(file)?;
#[cfg(not(feature = "std"))]
let source = Source::from_contents(file.clone(), read_file(&file)?)?;
// Create a parser and parse the source.
let mut parser = Parser::new(&source)?;
let ast = parser.parse()?;

10
release-plz.toml Normal file
View File

@@ -0,0 +1,10 @@
[workspace]
git_release_enable = false
git_tag_enable = false
changelog_update = false
[[package]]
name = "regorus"
git_tag_enable = true
git_release_enable = true
changelog_update = true

View File

@@ -5,16 +5,30 @@
set -eo pipefail
if [ -f Cargo.toml ]; then
# Run precommit checks
# Run precommit checks.
dir=$(dirname "${BASH_SOURCE[0]}")
"$dir/pre-commit"
# Ensure that the public API works
# Ensure that the public API works.
cargo test -r --doc
# Ensure that no_std build succeeds.
# Build for a target that has no std available.
if command -v rustup > /dev/null; then
rustup target add thumbv7m-none-eabi
(cd tests/ensure_no_std; cargo build -r --target thumbv7m-none-eabi)
fi
# Ensure that we can build with only std.
cargo build -r --example regorus --no-default-features --features std
# Ensure that we can build with all features.
cargo build -r --all-features
# Ensure that all tests pass
# Ensure that all tests pass.
cargo test -r
cargo test -r --test aci
cargo test -r --test kata
# 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)

View File

@@ -3,9 +3,9 @@
use crate::lexer::*;
use crate::value::Value;
use crate::Rc;
use crate::*;
use std::ops::Deref;
use core::{cmp, fmt, ops::Deref};
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BinOp {
@@ -48,28 +48,28 @@ impl<T> Clone for NodeRef<T> {
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for NodeRef<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl<T: fmt::Debug> fmt::Debug for NodeRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.r.as_ref().fmt(f)
}
}
impl<T> std::cmp::PartialEq for NodeRef<T> {
impl<T> cmp::PartialEq for NodeRef<T> {
fn eq(&self, other: &Self) -> bool {
Rc::as_ptr(&self.r).eq(&Rc::as_ptr(&other.r))
}
}
impl<T> std::cmp::Eq for NodeRef<T> {}
impl<T> cmp::Eq for NodeRef<T> {}
impl<T> std::cmp::Ord for NodeRef<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
impl<T> cmp::Ord for NodeRef<T> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
Rc::as_ptr(&self.r).cmp(&Rc::as_ptr(&other.r))
}
}
impl<T> std::cmp::PartialOrd for NodeRef<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
impl<T> cmp::PartialOrd for NodeRef<T> {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}

View File

@@ -7,12 +7,11 @@ use crate::builtins::utils::{ensure_args_count, ensure_numeric};
use crate::lexer::Span;
use crate::number::Number;
use crate::value::Value;
use std::collections::HashMap;
use crate::*;
use anyhow::{bail, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("count", (count, 1));
m.insert("max", (max, 1));
m.insert("min", (min, 1));

View File

@@ -8,11 +8,9 @@ use crate::lexer::Span;
use crate::Rc;
use crate::Value;
use std::collections::HashMap;
use anyhow::Result;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("array.concat", (concat, 2));
m.insert("array.reverse", (reverse, 1));
m.insert("array.slice", (slice, 3));

View File

@@ -8,11 +8,9 @@ use crate::builtins::utils::{ensure_args_count, ensure_numeric};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use anyhow::Result;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("bits.and", (and, 2));
m.insert("bits.lsh", (lsh, 2));
m.insert("bits.negate", (negate, 1));

View File

@@ -6,12 +6,11 @@ use crate::builtins;
use crate::builtins::utils::ensure_args_count;
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use crate::*;
use anyhow::{bail, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("to_number", (to_number, 1));
}

View File

@@ -7,8 +7,6 @@ use crate::builtins::utils::{ensure_args_count, ensure_string};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use anyhow::{bail, Result};
use constant_time_eq::constant_time_eq;
use hmac::{Hmac, Mac};
@@ -16,7 +14,7 @@ use md5::{Digest, Md5};
use sha1::Sha1;
use sha2::{Sha256, Sha512};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
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));

View File

@@ -1,52 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use anyhow::{bail, Result};
// TODO: Should we avoid this limit?
const MAX_ARGS: u8 = std::u8::MAX;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("print", (print, MAX_ARGS));
}
pub fn print_to_string(
span: &Span,
_params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<String> {
if args.len() > MAX_ARGS as usize {
bail!(span.error("print supports up to 100 arguments"));
}
let mut msg = String::default();
for a in args {
match a {
Value::Undefined => msg += " <undefined>",
Value::String(s) => msg += &format!(" {s}"),
_ => msg += &format!(" {a}"),
};
}
Ok(msg)
}
// Symbol analyzer must ensure that vars used by print are defined before
// the print statement. Scheduler must ensure the above constraint.
// Additionally interpreter must allow undefined inputs to print.
fn print(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let msg = print_to_string(span, params, args, strict)?;
if !msg.is_empty() {
eprintln!("{}", &msg[1..]);
}
Ok(Value::Bool(true))
}

View File

@@ -2,12 +2,12 @@
// Licensed under the MIT License.
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_set};
use crate::builtins::BuiltinFcn;
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use crate::*;
use anyhow::{bail, Result};
use lazy_static::lazy_static;
@@ -17,8 +17,8 @@ use crate::builtins::regex::regex_match;
#[rustfmt::skip]
lazy_static! {
pub static ref DEPRECATED: HashMap<&'static str, BuiltinFcn> = {
let mut m : HashMap<&'static str, BuiltinFcn> = HashMap::new();
pub static ref DEPRECATED: builtins::BuiltinsMap<&'static str, BuiltinFcn> = {
let mut m : builtins::BuiltinsMap<&'static str, BuiltinFcn> = builtins::BuiltinsMap::new();
m.insert("all", (all, 1));
m.insert("any", (any, 1));

View File

@@ -9,13 +9,12 @@ use crate::builtins::utils::{
};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use crate::*;
#[allow(unused)]
use anyhow::{anyhow, bail, Context, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
#[cfg(feature = "base64")]
{
m.insert("base64.decode", (base64_decode, 1));
@@ -42,6 +41,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
}
m.insert("json.is_valid", (json_is_valid, 1));
m.insert("json.marshal", (json_marshal, 1));
m.insert("json.marshal_with_options", (json_marshal_with_options, 2));
m.insert("json.unmarshal", (json_unmarshal, 1));
#[cfg(feature = "yaml")]
@@ -63,7 +63,13 @@ fn base64_decode(
ensure_args_count(span, name, params, args, 1)?;
let encoded_str = ensure_string(name, &params[0], &args[0])?;
let decoded_bytes = data_encoding::BASE64.decode(encoded_str.as_bytes())?;
let decoded_bytes = data_encoding::BASE64
.decode(encoded_str.as_bytes())
.map_err(|e| {
params[0]
.span()
.error(&format!("decode failed\nCaused by\n{e}"))
})?;
Ok(Value::String(
String::from_utf8_lossy(&decoded_bytes).into(),
))
@@ -173,7 +179,13 @@ fn hex_decode(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
ensure_args_count(span, name, params, args, 1)?;
let encoded_str = ensure_string(name, &params[0], &args[0])?;
let decoded_bytes = data_encoding::HEXLOWER_PERMISSIVE.decode(encoded_str.as_bytes())?;
let decoded_bytes = data_encoding::HEXLOWER_PERMISSIVE
.decode(encoded_str.as_bytes())
.map_err(|e| {
params[0]
.span()
.error(&format!("decode failure\nCaused by\n{e}"))
})?;
Ok(Value::String(
String::from_utf8_lossy(&decoded_bytes).into(),
))
@@ -237,7 +249,7 @@ fn urlquery_decode_object(
Err(_) => bail!(params[0].span().error("not a valid url query")),
};
let mut map = std::collections::BTreeMap::new();
let mut map = alloc::collections::BTreeMap::new();
for (k, v) in url.query_pairs() {
let key = Value::String(k.clone().into());
let value = Value::String(v.clone().into());
@@ -361,11 +373,75 @@ fn json_is_valid(
fn json_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "json.marshal";
ensure_args_count(span, name, params, args, 1)?;
Ok(Value::String(
serde_json::to_string(&args[0])
.with_context(|| span.error("could not serialize to json"))?
.into(),
))
Ok(Value::from(serde_json::to_string(&args[0]).map_err(
|e| span.error(&format!("could not serialize to json\nCaused by\n{e}")),
)?))
}
fn json_marshal_with_options(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "json.marshal_with_options";
ensure_args_count(span, name, params, args, 2)?;
let options = ensure_object(name, &params[1], args[1].clone())?;
let (mut pretty, mut indent, mut prefix) = (true, Some("\t".to_owned()), None);
for (option, option_value) in options.iter() {
match option {
Value::String(s) if s.as_ref() == "pretty" && option_value.as_bool().is_ok() => {
pretty = option_value == &Value::Bool(true);
}
Value::String(s) if s.as_ref() == "pretty" => bail!(params[1]
.span()
.error("marshaling option `pretty` must be true or false")),
Value::String(s) if s.as_ref() == "prefix" && option_value.as_string().is_ok() => {
prefix = Some(option_value.as_string()?.as_ref().to_string());
}
Value::String(s) if s.as_ref() == "prefix" => bail!(params[1]
.span()
.error("marshaling option `pretty` must be string")),
Value::String(s) if s.as_ref() == "indent" && option_value.as_string().is_ok() => {
indent = Some(option_value.as_string()?.as_ref().to_string());
}
Value::String(s) if s.as_ref() == "indent" => bail!(params[1]
.span()
.error("marshaling option `pretty` must be string")),
_ => bail!(params[1]
.span()
.error("marshaling option must be one of `indent`, `prefix` or `pretty`")),
}
}
if !pretty || options.is_empty() {
return Ok(Value::from(serde_json::to_string(&args[0]).map_err(
|e| span.error(&format!("could not serialize to json\nCaused by\n{e}")),
)?));
}
let lines: Vec<String> = serde_json::to_string_pretty(&args[0])
.map_err(|e| span.error(&format!("could not serialize to json\nCaused by\n{e}")))?
.split('\n')
.map(|line| {
let mut line = line.to_string();
if let Some(indent) = &indent {
let start_trimmed = line.trim_start();
let leading_spaces = line.len() - start_trimmed.len();
let indentation_level = leading_spaces / 2;
line = indent.repeat(indentation_level) + start_trimmed;
}
if let Some(prefix) = &prefix {
line = prefix.to_owned() + &line;
}
line
})
.collect();
Ok(Value::from(lines.join("\n")))
}
fn json_unmarshal(

View File

@@ -6,14 +6,13 @@ use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_string, ensure_string_collection};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use crate::*;
use anyhow::{bail, Result};
//use glob::{Pattern, MatchOptions};
use wax::{Glob, Pattern};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("glob.match", (glob_match, 3));
m.insert("glob.quote_meta", (quote_meta, 1));
}

View File

@@ -6,12 +6,13 @@ use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_object};
use crate::lexer::Span;
use crate::value::Value;
use crate::*;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use alloc::collections::{BTreeMap, BTreeSet};
use anyhow::{bail, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("graph.reachable", (reachable, 2));
m.insert("graph.reachable_paths", (reachable_paths, 2));
m.insert("walk", (walk, 1));

View File

@@ -8,11 +8,9 @@ use crate::builtins::utils::ensure_args_count;
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use anyhow::Result;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("http.send", (send, 1));
}

View File

@@ -4,16 +4,16 @@
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 std::collections::HashMap;
use anyhow::{bail, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
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));
}

View File

@@ -9,7 +9,6 @@ mod conversions;
#[cfg(feature = "crypto")]
mod crypto;
mod debugging;
#[cfg(feature = "deprecated")]
pub mod deprecated;
mod encoding;
@@ -47,22 +46,20 @@ use crate::ast::{Expr, Ref};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use crate::Map as BuiltinsMap;
use anyhow::Result;
use lazy_static::lazy_static;
pub type BuiltinFcn = (fn(&Span, &[Ref<Expr>], &[Value], bool) -> Result<Value>, u8);
pub use debugging::print_to_string;
#[cfg(feature = "deprecated")]
pub use deprecated::DEPRECATED;
#[rustfmt::skip]
lazy_static! {
pub static ref BUILTINS: HashMap<&'static str, BuiltinFcn> = {
let mut m : HashMap<&'static str, BuiltinFcn> = HashMap::new();
pub static ref BUILTINS: BuiltinsMap<&'static str, BuiltinFcn> = {
let mut m : BuiltinsMap<&'static str, BuiltinFcn> = BuiltinsMap::new();
// comparison functions are directly called.
numbers::register(&mut m);
@@ -104,7 +101,6 @@ lazy_static! {
//rego::register(&mut m);
#[cfg(feature = "opa-runtime")]
opa::register(&mut m);
debugging::register(&mut m);
tracing::register(&mut m);
units::register(&mut m);

View File

@@ -3,22 +3,24 @@
use crate::ast::{ArithOp, Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string};
use crate::builtins::utils::{ensure_args_count, ensure_numeric};
use crate::lexer::Span;
use crate::number::Number;
use crate::value::Value;
use std::collections::HashMap;
use crate::*;
use anyhow::{bail, Result};
#[cfg(feature = "std")]
use rand::{thread_rng, Rng};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("abs", (abs, 1));
m.insert("ceil", (ceil, 1));
m.insert("floor", (floor, 1));
m.insert("numbers.range", (range, 2));
m.insert("numbers.range_step", (range_step, 3));
#[cfg(feature = "std")]
m.insert("rand.intn", (intn, 2));
m.insert("round", (round, 1));
}
@@ -156,10 +158,11 @@ fn round(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
))
}
#[cfg(feature = "std")]
fn intn(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let fcn = "rand.intn";
ensure_args_count(span, fcn, params, args, 2)?;
let _ = ensure_string(fcn, &params[0], &args[0])?;
let _ = crate::builtins::utils::ensure_string(fcn, &params[0], &args[0])?;
let n = ensure_numeric(fcn, &params[0], &args[1])?;
Ok(match n.as_u64() {

View File

@@ -7,13 +7,14 @@ use crate::builtins::utils::{ensure_args_count, ensure_array, ensure_object};
use crate::lexer::Span;
use crate::Rc;
use crate::Value;
use crate::*;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::iter::Iterator;
use alloc::collections::{BTreeMap, BTreeSet};
use core::iter::Iterator;
use anyhow::{bail, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("json.filter", (json_filter, 2));
m.insert("json.remove", (json_remove, 2));
m.insert("object.filter", (filter, 2));

View File

@@ -4,15 +4,16 @@
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::ensure_args_count;
use crate::*;
use crate::lexer::Span;
use crate::value::Value;
use std::collections::{BTreeMap, HashMap};
use alloc::collections::BTreeMap;
use anyhow::Result;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("opa.runtime", (opa_runtime, 0));
}
@@ -37,6 +38,7 @@ fn opa_runtime(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
);
// Emitting environment variables could lead to confidential data being leaked.
#[cfg(feature = "std")]
if false {
obj.insert(
Value::String("env".into()),

View File

@@ -6,13 +6,12 @@ use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use crate::*;
use anyhow::{bail, Result};
use regex::Regex;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert(
"regex.find_all_string_submatch_n",
(find_all_string_submatch_n, 3),

View File

@@ -9,12 +9,11 @@ use crate::value::Value;
use semver::Version;
use std::cmp::Ordering;
use std::collections::HashMap;
use core::cmp::Ordering;
use anyhow::{Ok, Result};
use anyhow::Result;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("semver.compare", (compare, 2));
m.insert("semver.is_valid", (is_valid, 1));
}
@@ -25,8 +24,8 @@ fn compare(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
let v1 = ensure_string(name, &params[0], &args[0])?;
let v2 = ensure_string(name, &params[1], &args[1])?;
let version1 = Version::parse(&v1)?;
let version2 = Version::parse(&v2)?;
let version1 = Version::parse(&v1).map_err(|_| params[0].span().error("invalid semver"))?;
let version2 = Version::parse(&v2).map_err(|_| params[0].span().error("invalid semver"))?;
let result = match version1.cmp_precedence(&version2) {
Ordering::Less => -1,
Ordering::Equal => 0,

View File

@@ -6,12 +6,13 @@ use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_set};
use crate::lexer::Span;
use crate::value::Value;
use crate::*;
use std::collections::{BTreeSet, HashMap};
use alloc::collections::BTreeSet;
use anyhow::{bail, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("intersection", (intersection_of_set_of_sets, 1));
m.insert("union", (union_of_set_of_sets, 1));
}
@@ -56,7 +57,7 @@ fn intersection_of_set_of_sets(
};
if first {
res = (**s).clone();
res.clone_from(s);
first = false;
} else {
res = res.intersection(s).cloned().collect();

View File

@@ -10,12 +10,11 @@ use crate::builtins::utils::{
use crate::lexer::Span;
use crate::number::Number;
use crate::value::Value;
use std::collections::HashMap;
use crate::*;
use anyhow::{bail, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("concat", (concat, 2));
m.insert("contains", (contains, 2));
m.insert("endswith", (endswith, 2));

View File

@@ -7,13 +7,13 @@ use crate::builtins::time;
use crate::builtins::utils::{ensure_args_count, ensure_string};
use crate::lexer::Span;
use crate::value::Value;
use crate::*;
use std::collections::HashMap;
use std::thread;
use anyhow::{Ok, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("test.sleep", (sleep, 1));
}
@@ -22,7 +22,8 @@ fn sleep(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
ensure_args_count(span, name, params, args, 1)?;
let val = ensure_string(name, &params[0], &args[0])?;
let dur = time::compat::parse_duration(val.as_ref())?;
let dur = time::compat::parse_duration(val.as_ref())
.map_err(|e| params[0].span().error(&format!("{e}")))?;
thread::sleep(dur.to_std()?);

View File

@@ -6,8 +6,7 @@ use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use crate::*;
use anyhow::{bail, Result};
@@ -20,7 +19,7 @@ use chrono_tz::Tz;
pub(in crate::builtins) mod compat;
mod diff;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("time.add_date", (add_date, 4));
m.insert("time.clock", (clock, 1));
m.insert("time.date", (date, 1));
@@ -148,7 +147,7 @@ fn parse_duration_ns(
ensure_args_count(span, name, params, args, 1)?;
let value = ensure_string(name, &params[0], &args[0])?;
let dur = compat::parse_duration(value.as_ref())?;
let dur = compat::parse_duration(value.as_ref()).map_err(anyhow::Error::msg)?;
safe_timestamp_nanos(span, strict, dur.num_nanoseconds())
}

View File

@@ -31,9 +31,9 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use std::error::Error;
use std::fmt;
use std::iter;
use crate::*;
use core::fmt;
use core::iter;
use chrono::TimeZone;
use chrono::{
@@ -71,8 +71,6 @@ impl fmt::Display for ParseDurationError {
}
}
impl Error for ParseDurationError {}
// Parses a duration string in the form of `10h12m45s`.
//
// Adapted from Go's `time.ParseDuration`:
@@ -1242,7 +1240,7 @@ mod tests {
];
for tc in test_cases {
println!("Test case {}", tc.name);
std::println!("Test case {}", tc.name);
let time = parse(&tc.format, &tc.value).unwrap();
check_time(time, &tc);
}
@@ -1313,7 +1311,7 @@ mod tests {
let time = PST8PDT.timestamp_nanos(1233810057012345600);
for tc in test_cases {
println!("Test case {}", tc.name);
std::println!("Test case {}", tc.name);
let result = format(time, &tc.format);
assert_eq!(result, tc.result);
}

View File

@@ -7,11 +7,9 @@ use crate::builtins::utils::{ensure_args_count, ensure_string};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use anyhow::Result;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("trace", (trace, 1));
}

View File

@@ -7,11 +7,9 @@ use crate::builtins::utils::ensure_args_count;
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use anyhow::Result;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("is_array", (is_array, 1));
m.insert("is_boolean", (is_boolean, 1));
m.insert("is_null", (is_null, 1));

View File

@@ -7,12 +7,11 @@ use crate::builtins::utils::{ensure_args_count, ensure_string};
use crate::lexer::Span;
use crate::number::Number;
use crate::value::Value;
use crate::*;
use std::collections::HashMap;
use anyhow::{bail, Result};
use anyhow::{bail, Context, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("units.parse", (parse, 1));
m.insert("units.parse_bytes", (parse_bytes, 1));
}
@@ -89,12 +88,13 @@ fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
_ => (string, ""),
};
// Propagating the underlying error is not useful here.
let v: Value = if number_part.starts_with('.') {
serde_json::from_str(format!("0{number_part}").as_str())
} else {
serde_json::from_str(number_part)
}
.with_context(|| span.error("could not parse number"))?;
.map_err(|_| params[0].span().error("could not parse number"))?;
let mut n = match v {
Value::Number(n) => n.clone(),

View File

@@ -6,8 +6,9 @@ use crate::lexer::Span;
use crate::number::Number;
use crate::Rc;
use crate::Value;
use crate::*;
use std::collections::{BTreeMap, BTreeSet};
use alloc::collections::{BTreeMap, BTreeSet};
use anyhow::{bail, Result};

View File

@@ -6,13 +6,14 @@ use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_string};
use crate::lexer::Span;
use crate::value::Value;
use crate::*;
use std::collections::{BTreeMap, HashMap};
use alloc::collections::BTreeMap;
use anyhow::{Ok, Result};
use anyhow::Result;
use uuid::{Timestamp, Uuid};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("uuid.parse", (parse, 1));
m.insert("uuid.rfc4122", (rfc4122, 1));
}

View File

@@ -8,11 +8,9 @@ use crate::parser::*;
use crate::scheduler::*;
use crate::utils::gather_functions;
use crate::value::*;
use crate::*;
use crate::{Extension, QueryResults};
use std::convert::AsRef;
use std::path::Path;
use anyhow::{bail, Result};
/// The Rego evaluation engine.
@@ -45,6 +43,7 @@ impl Engine {
///
/// The policy file will be parsed and converted to AST representation.
/// Multiple policy files may be added to the engine.
/// Returns the Rego package name declared in the policy.
///
/// * `path`: A filename to be associated with the policy.
/// * `rego`: The rego policy code.
@@ -54,29 +53,33 @@ impl Engine {
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// engine.add_policy(
/// let package = engine.add_policy(
/// "test.rego".to_string(),
/// r#"
/// package test
/// allow = input.user == "root"
/// "#.to_string())?;
///
/// assert_eq!(package, "data.test");
/// # Ok(())
/// # }
/// ```
///
pub fn add_policy(&mut self, path: String, rego: String) -> Result<()> {
let source = Source::new(path, rego);
pub fn add_policy(&mut self, path: String, rego: String) -> Result<String> {
let source = Source::from_contents(path, rego)?;
let mut parser = Parser::new(&source)?;
self.modules.push(Ref::new(parser.parse()?));
let module = Ref::new(parser.parse()?);
self.modules.push(module.clone());
// if policies change, interpreter needs to be prepared again
self.prepared = false;
Ok(())
Interpreter::get_path_string(&module.package.refr, Some("data"))
}
/// Add a policy from a given file.
///
/// The policy file will be parsed and converted to AST representation.
/// Multiple policy files may be added to the engine.
/// Returns the Rego package name declared in the policy.
///
/// * `path`: Path to the policy file (.rego).
///
@@ -85,16 +88,45 @@ impl Engine {
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// engine.add_policy_from_file("tests/aci/framework.rego")?;
/// let package = engine.add_policy_from_file("tests/aci/framework.rego")?;
///
/// assert_eq!(package, "data.framework");
/// # Ok(())
/// # }
/// ```
pub fn add_policy_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn add_policy_from_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<String> {
let source = Source::from_file(path)?;
let mut parser = Parser::new(&source)?;
self.modules.push(Ref::new(parser.parse()?));
let module = Ref::new(parser.parse()?);
self.modules.push(module.clone());
// if policies change, interpreter needs to be prepared again
self.prepared = false;
Ok(())
Interpreter::get_path_string(&module.package.refr, Some("data"))
}
/// Get the list of packages defined by loaded policies.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// let _ = engine.add_policy_from_file("tests/aci/framework.rego")?;
///
/// // Package names can be different from file names.
/// let _ = engine.add_policy("policy.rego".into(), "package hello.world".into())?;
///
/// assert_eq!(engine.get_packages()?, vec!["data.framework", "data.hello.world"]);
/// # Ok(())
/// # }
/// ```
pub fn get_packages(&self) -> Result<Vec<String>> {
self.modules
.iter()
.map(|m| Interpreter::get_path_string(&m.package.refr, Some("data")))
.collect()
}
/// Set the input document.
@@ -208,7 +240,7 @@ impl Engine {
&self.modules
}
/// Evaluate rule(s) at given path.
/// Evaluate specified rule(s).
///
/// [`Engine::eval_rule`] is often faster than [`Engine::eval_query`] and should be preferred if
/// OPA style [`QueryResults`] are not needed.
@@ -248,10 +280,10 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
pub fn eval_rule(&mut self, path: String) -> Result<Value> {
pub fn eval_rule(&mut self, rule: String) -> Result<Value> {
self.prepare_for_eval(false)?;
self.interpreter.clean_internal_evaluation_state();
self.interpreter.eval_rule_in_path(path)
self.interpreter.eval_rule_in_path(rule)
}
/// Evaluate a Rego query.
@@ -294,15 +326,15 @@ impl Engine {
self.interpreter.create_rule_prefixes()?;
let query_module = {
let source = Source::new(
let source = Source::from_contents(
"<query_module.rego>".to_owned(),
"package __internal_query_module".to_owned(),
);
)?;
Ref::new(Parser::new(&source)?.parse()?)
};
// Parse the query.
let query_source = Source::new("<query.rego>".to_string(), query);
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
let mut parser = Parser::new(&query_source)?;
let query_node = parser.parse_user_query()?;
if query_node.span.text() == "data" {
@@ -411,15 +443,15 @@ impl Engine {
self.eval_modules(enable_tracing)?;
let query_module = {
let source = Source::new(
let source = Source::from_contents(
"<query_module.rego>".to_owned(),
"package __internal_query_module".to_owned(),
);
)?;
Ref::new(Parser::new(&source)?.parse()?)
};
// Parse the query.
let query_source = Source::new("<query.rego>".to_string(), query);
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
let mut parser = Parser::new(&query_source)?;
let query_node = parser.parse_user_query()?;
let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
@@ -613,7 +645,7 @@ impl Engine {
}
#[cfg(feature = "coverage")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "coverage")))]
#[cfg_attr(docsrs, doc(cfg(feature = "coverage")))]
/// Get the coverage report.
///
/// ```rust
@@ -657,7 +689,7 @@ impl Engine {
}
#[cfg(feature = "coverage")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "coverage")))]
#[cfg_attr(docsrs, doc(cfg(feature = "coverage")))]
/// Enable/disable policy coverage.
///
/// If `enable` is different from the current value, then any existing coverage
@@ -667,7 +699,7 @@ impl Engine {
}
#[cfg(feature = "coverage")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "coverage")))]
#[cfg_attr(docsrs, doc(cfg(feature = "coverage")))]
/// Clear the gathered policy coverage data.
pub fn clear_coverage_data(&mut self) {
self.interpreter.clear_coverage_data()

View File

@@ -8,13 +8,13 @@ use crate::parser::Parser;
use crate::scheduler::*;
use crate::utils::*;
use crate::value::*;
use crate::Rc;
use crate::*;
use crate::{Expression, Extension, Location, QueryResult, QueryResults};
use alloc::collections::btree_map::Entry as BTreeMapEntry;
use alloc::collections::{BTreeMap, BTreeSet};
use anyhow::{anyhow, bail, Result};
use std::collections::btree_map::Entry as BTreeMapEntry;
use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet};
use std::ops::Bound::*;
use core::ops::Bound::*;
type Scope = BTreeMap<SourceStr, Value>;
@@ -52,8 +52,8 @@ pub struct Interpreter {
loop_var_values: BTreeMap<ExprRef, Value>,
contexts: Vec<Context>,
functions: FunctionTable,
rules: HashMap<String, Vec<Ref<Rule>>>,
default_rules: HashMap<String, Vec<DefaultRuleInfo>>,
rules: Map<String, Vec<Ref<Rule>>>,
default_rules: Map<String, Vec<DefaultRuleInfo>>,
processed: BTreeSet<Ref<Rule>>,
processed_paths: Value,
rule_values: BTreeMap<Vec<Value>, (Value, Ref<Expr>)>,
@@ -61,19 +61,20 @@ pub struct Interpreter {
builtins_cache: BTreeMap<(&'static str, Vec<Value>), Value>,
no_rules_lookup: bool,
traces: Option<Vec<Rc<str>>>,
#[cfg(feature = "deprecated")]
allow_deprecated: bool,
strict_builtin_errors: bool,
imports: BTreeMap<String, Ref<Expr>>,
extensions: HashMap<String, (u8, Rc<Box<dyn Extension>>)>,
extensions: Map<String, (u8, Rc<Box<dyn Extension>>)>,
#[cfg(feature = "coverage")]
coverage: HashMap<Source, Vec<bool>>,
coverage: Map<Source, Vec<bool>>,
#[cfg(feature = "coverage")]
enable_coverage: bool,
gather_prints: bool,
prints: Vec<String>,
rule_paths: HashSet<String>,
rule_paths: Set<String>,
}
impl Default for Interpreter {
@@ -94,6 +95,8 @@ struct Context {
rule_value: Value,
is_set: bool,
is_old_style_set: bool,
output_constness_determined: bool,
early_return: bool,
}
impl Default for Context {
@@ -109,6 +112,8 @@ impl Default for Context {
rule_value: Value::new_object(),
is_set: false,
is_old_style_set: false,
output_constness_determined: false,
early_return: false,
}
}
}
@@ -173,8 +178,8 @@ impl Interpreter {
contexts: vec![],
loop_var_values: BTreeMap::new(),
functions: FunctionTable::new(),
rules: HashMap::new(),
default_rules: HashMap::new(),
rules: Map::new(),
default_rules: Map::new(),
processed: BTreeSet::new(),
processed_paths: Value::new_object(),
rule_values: BTreeMap::new(),
@@ -182,19 +187,20 @@ impl Interpreter {
builtins_cache: BTreeMap::new(),
no_rules_lookup: false,
traces: None,
#[cfg(feature = "deprecated")]
allow_deprecated: true,
strict_builtin_errors: true,
imports: BTreeMap::default(),
extensions: HashMap::new(),
extensions: Map::new(),
#[cfg(feature = "coverage")]
coverage: HashMap::new(),
coverage: Map::new(),
#[cfg(feature = "coverage")]
enable_coverage: false,
gather_prints: false,
prints: Vec::default(),
rule_paths: HashSet::new(),
rule_paths: Set::new(),
}
}
@@ -395,7 +401,7 @@ impl Interpreter {
None => {
// Check if ident is a rule.
let path = self.current_module_path.clone() + "." + ident.text();
self.rules.get(&path).is_none()
!self.rules.contains_key(&path)
}
},
}
@@ -608,7 +614,7 @@ impl Interpreter {
bail!(rhs_span
.error("mismatch in number of array elements in lhs and rhs"));
}
for (lhs, rhs) in std::iter::zip(lhs_items.iter(), rhs_items.iter()) {
for (lhs, rhs) in core::iter::zip(lhs_items.iter(), rhs_items.iter()) {
if self.eval_assign_expr(&AssignOp::Eq, lhs, rhs)? != Value::Bool(true)
{
return Ok(Value::Bool(false));
@@ -630,7 +636,7 @@ impl Interpreter {
}
for ((_, lhs_key, lhs_value), (_, rhs_key, rhs_value)) in
std::iter::zip(lhs_fields.iter(), rhs_fields.iter())
core::iter::zip(lhs_fields.iter(), rhs_fields.iter())
{
if self.eval_bool_expr(&BoolOp::Eq, lhs_key, rhs_key)?
!= Value::Bool(true)
@@ -708,7 +714,7 @@ impl Interpreter {
// Allow variable overwritten inside a loop
let lhs_val = self.lookup_local_var(&name);
if !matches!(lhs_val, None | Some(Value::Undefined))
&& self.loop_var_values.get(rhs).is_none()
&& !self.loop_var_values.contains_key(rhs)
{
bail!(rhs
.span()
@@ -785,10 +791,9 @@ impl Interpreter {
}
}
}
Value::Undefined | Value::Null => r = false,
// Other types cause every to evaluate to true even though
// it is supposed to happen only for empty domain.
_ => (),
_ => {
r = false;
}
};
self.contexts.pop();
self.scopes.pop();
@@ -1192,7 +1197,8 @@ impl Interpreter {
let rule_values = self.rule_values.clone();
self.processed.clear();
let processed_paths = std::mem::replace(&mut self.processed_paths, Value::new_object());
let processed_paths =
core::mem::replace(&mut self.processed_paths, Value::new_object());
self.rule_values.clear();
let mut skip_exec = false;
@@ -1203,7 +1209,7 @@ impl Interpreter {
let mut target = path.join(".");
let mut target_is_function = self.lookup_function_by_name(&target).is_some()
|| matches!(self.lookup_builtin(wm.refr.span(), &target), Ok(Some(_)));
|| self.is_builtin(wm.refr.span(), &target);
if !target_is_function
&& !target.starts_with("data.")
@@ -1212,7 +1218,7 @@ impl Interpreter {
{
// target must be a function.
if self.lookup_function_by_name(&target).is_none()
&& !matches!(self.lookup_builtin(wm.refr.span(), &target), Ok(Some(_)))
&& !self.is_builtin(wm.refr.span(), &target)
{
// Prefix target with current module path.
target = self.current_module_path.clone() + "." + &target;
@@ -1239,10 +1245,7 @@ impl Interpreter {
// Lookup without current module path prefixed.
function_path = get_path_string(&wm.r#as, None)?;
if self.lookup_function_by_name(&function_path).is_none()
&& !matches!(
self.lookup_builtin(wm.r#as.span(), &function_path),
Ok(Some(_))
)
&& !self.is_builtin(wm.r#as.span(), &function_path)
{
// bail!(wm.r#as.span().error("could not evaluate expression"));
skip_exec = true;
@@ -1427,7 +1430,10 @@ impl Interpreter {
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result = query_result.clone();
ctx.result.clone_from(&query_result);
if ctx.early_return {
break;
}
}
}
@@ -1451,7 +1457,10 @@ impl Interpreter {
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result = query_result.clone();
ctx.result.clone_from(&query_result);
if ctx.early_return {
break;
}
}
}
self.loop_var_values.remove(&loop_expr.expr());
@@ -1473,7 +1482,10 @@ impl Interpreter {
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result = query_result.clone();
ctx.result.clone_from(&query_result);
if ctx.early_return {
break;
}
}
}
self.loop_var_values.remove(&loop_expr.expr());
@@ -1586,30 +1598,95 @@ impl Interpreter {
Ok(())
}
// A ref is a constant ref, if it does not contain any local variables.
// For now, we restrict constant refs to those that contain only simple literals.
fn is_constant_ref(&self, mut expr: &Ref<Expr>) -> Result<bool> {
loop {
match expr.as_ref() {
Expr::Var(_) => break,
Expr::RefDot { refr, .. } => expr = refr,
Expr::RefBrack { refr, index, .. } if self.is_simple_literal(index)? => expr = refr,
_ => return Ok(false),
}
}
Ok(true)
}
fn is_simple_literal(&self, expr: &Ref<Expr>) -> Result<bool> {
Ok(matches!(
expr.as_ref(),
Expr::String(_)
| Expr::RawString(_)
| Expr::True(_)
| Expr::False(_)
| Expr::Null(_)
| Expr::Number(_)
))
}
// A rule's output expression is constant if it does not contain local variables.
// For now, we restrict output expressions to those that contain only simple literals.
fn is_constant_output(
&self,
key_expr: &Option<Ref<Expr>>,
output_expr: &Ref<Expr>,
) -> Result<bool> {
let mut is_const = true;
if let Some(key_expr) = key_expr {
is_const = self.is_simple_literal(key_expr)?;
}
Ok(is_const && self.is_simple_literal(output_expr)?)
}
fn eval_output_expr_in_loop(&mut self, loops: &[LoopExpr]) -> Result<bool> {
if loops.is_empty() {
let (key_expr, output_expr) = self.get_exprs_from_context()?;
let ctx = self.get_current_context()?;
let (is_set, is_old_style_set) = (ctx.is_set, ctx.is_old_style_set);
let (is_set, is_old_style_set, is_rule, constness_determined) = (
ctx.is_set,
ctx.is_old_style_set,
!ctx.is_compr,
ctx.output_constness_determined,
);
if let Some(rule_ref) = ctx.rule_ref.clone() {
let mut is_const_rule = if is_rule && !constness_determined {
self.is_constant_ref(&rule_ref)?
} else {
// Constness has already been determined or is not a rule.
// Treat the expression as not constant.
false
};
let mut comps = self.eval_rule_ref(&rule_ref)?;
if let Some(ke) = &key_expr {
comps.push(self.eval_expr(ke)?);
}
let output = if let Some(oe) = &output_expr {
// Rule is constant only if its ref, key and output are constant.
is_const_rule = is_const_rule && self.is_constant_output(&key_expr, oe)?;
self.eval_expr(oe)?
} else if is_old_style_set && !comps.is_empty() {
// Rule's constness is determined only by its ref.
let output = comps[comps.len() - 1].clone();
comps.pop();
output
} else {
// Rule's constness is determined only by its ref.
Value::Bool(true)
};
let comps_defined = comps.iter().all(|v| v != &Value::Undefined);
let ctx = self.contexts.last_mut().expect("no current context");
if is_const_rule {
ctx.early_return = true;
}
if is_rule {
ctx.output_constness_determined = true;
}
if output == Value::Undefined || !comps_defined {
return Ok(false);
}
@@ -1664,9 +1741,9 @@ impl Interpreter {
span.col,
format!(
"value for key `{}` generated multiple times: `{}` and `{}`",
serde_json::to_string_pretty(&key)?,
serde_json::to_string_pretty(&pv)?,
serde_json::to_string_pretty(&value)?,
serde_json::to_string_pretty(&key).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&pv).map_err(anyhow::Error::msg)?,
serde_json::to_string_pretty(&value).map_err(anyhow::Error::msg)?,
)
.as_str(),
));
@@ -2042,27 +2119,11 @@ impl Interpreter {
name: &str,
builtin: builtins::BuiltinFcn,
params: &[ExprRef],
args: Vec<Value>,
) -> Result<Value> {
let mut args = vec![];
let is_print = name == "print"; // TODO: with modifier
let allow_undefined = is_print;
for p in params {
match self.eval_expr(p)? {
// If any argument is undefined, then the call is undefined.
Value::Undefined if !allow_undefined => return Ok(Value::Undefined),
p => args.push(p),
}
}
if is_print && self.gather_prints {
// Do not print to stderr. Instead, gather.
let msg =
builtins::print_to_string(span, params, &args[..], self.strict_builtin_errors)?;
// Prefix location information.
self.prints
.push(format!("{}:{}: {msg}", span.source.file(), span.line));
return Ok(Value::Bool(true));
// If any argument is undefined, then the call is undefined.
if args.iter().any(|a| a == &Value::Undefined) {
return Ok(Value::Undefined);
}
let cache = builtins::must_cache(name);
@@ -2094,6 +2155,7 @@ impl Interpreter {
Ok(v)
}
#[allow(unused_variables)]
fn lookup_builtin(&self, span: &Span, path: &str) -> Result<Option<&BuiltinFcn>> {
if let Some(builtin) = builtins::BUILTINS.get(path) {
return Ok(Some(builtin));
@@ -2108,12 +2170,92 @@ impl Interpreter {
return Ok(Some(builtin));
}
// Mark as used when deprecated feature is not enabled.
std::convert::identity((span, self.allow_deprecated));
Ok(None)
}
fn is_builtin(&self, span: &Span, path: &str) -> bool {
path == "print" || matches!(self.lookup_builtin(span, path), Ok(Some(_)))
}
fn to_printable(v: &Value, s: &mut String) {
match v {
Value::Array(array) => {
s.push('[');
for (idx, e) in array.iter().enumerate() {
if idx > 0 {
s.push_str(", ");
}
Self::to_printable(e, s);
}
s.push(']');
}
Value::Set(set) => {
s.push('{');
for (idx, e) in set.iter().enumerate() {
if idx > 0 {
s.push_str(", ");
}
Self::to_printable(e, s);
}
s.push('}');
}
Value::Object(map) => {
s.push('{');
for (idx, (k, v)) in map.iter().enumerate() {
if idx > 0 {
s.push_str(", ");
}
Self::to_printable(k, s);
s.push_str(": ");
Self::to_printable(v, s);
}
s.push('}');
}
v => s.push_str(&format!("{v}")),
}
}
fn eval_print(&mut self, span: &Span, params: &[ExprRef], args: Vec<Value>) -> Result<Value> {
const MAX_ARGS: u8 = 100;
if args.len() > MAX_ARGS as usize {
bail!(span.error(&format!("print supports upto {MAX_ARGS} arguments")));
}
// If not compiling for std target, return early if gathering is not
// requested.
#[cfg(not(feature = "std"))]
if !self.gather_prints {
return Ok(Value::Bool(true));
}
let mut msg = String::default();
for (i, p) in params.iter().enumerate() {
if i > 0 {
msg.push(' ');
}
match self.eval_expr(p)? {
Value::Undefined => msg.push_str("<undefined>"),
// Do not print quotes for string values.
Value::String(s) => msg.push_str(&format!("{s}")),
a => Self::to_printable(&a, &mut msg),
}
}
if self.gather_prints {
// Prefix location information.
self.prints
.push(format!("{}:{}: {msg}", span.source.file(), span.line));
}
// Print to stderr only if not gathering.
#[cfg(feature = "std")]
if !self.gather_prints {
std::eprintln!("{msg}");
}
Ok(Value::Bool(true))
}
fn eval_call_impl(
&mut self,
span: &Span,
@@ -2170,11 +2312,10 @@ impl Interpreter {
let (fcns_rules, fcn_module) = match self.lookup_function_by_name(&fcn_path) {
Some((fcns, m)) => (fcns, Some(m.clone())),
_ => {
if self.default_rules.get(&fcn_path).is_some()
if self.default_rules.contains_key(&fcn_path)
|| self
.default_rules
.get(&get_path_string(fcn, Some(&self.current_module_path))?)
.is_some()
.contains_key(&get_path_string(fcn, Some(&self.current_module_path))?)
{
// process default functions later.
(&empty, self.module.clone())
@@ -2183,10 +2324,18 @@ impl Interpreter {
else if let Some(ext) = self.extensions.get_mut(&fcn_path) {
extension = Some(ext);
(&empty, None)
} else if fcn_path == "print" {
return self.eval_print(span, params, param_values);
}
// Look up builtin function.
else if let Some(builtin) = self.lookup_builtin(span, &fcn_path)? {
let r = self.eval_builtin_call(span, &fcn_path.clone(), *builtin, params);
let r = self.eval_builtin_call(
span,
&fcn_path.clone(),
*builtin,
params,
param_values,
);
if let Some(with_functions) = with_functions_saved {
self.with_functions = with_functions;
}
@@ -2248,7 +2397,7 @@ impl Interpreter {
// Back up local variables of current function and empty
// the local variables of callee function.
let scopes = std::mem::take(&mut self.scopes);
let scopes = core::mem::take(&mut self.scopes);
// Set the arguments scope.
let args_scope = Scope::new();
@@ -2327,7 +2476,7 @@ impl Interpreter {
if results.is_empty() {
// Back up local variables of current function and empty
// the local variables of callee function.
let scopes = std::mem::take(&mut self.scopes);
let scopes = core::mem::take(&mut self.scopes);
if errors.is_empty() {
// Check if any default rules can be evaluated.
// TODO: with mod
@@ -2570,7 +2719,7 @@ impl Interpreter {
for i in (1..fields.len() + 1).rev() {
let path = "data.".to_owned() + &fields[0..i].join(".");
if self.rules.get(&path).is_some() || self.default_rules.get(&path).is_some() {
if self.rules.contains_key(&path) || self.default_rules.contains_key(&path) {
self.ensure_rule_evaluated(path)?;
break;
}
@@ -2591,9 +2740,9 @@ impl Interpreter {
let rule_path = "data.".to_owned() + &path.join(".");
if !no_error
&& self.rules.get(&rule_path).is_none()
&& self.default_rules.get(&rule_path).is_none()
&& self.imports.get(&rule_path).is_none()
&& !self.rules.contains_key(&rule_path)
&& !self.default_rules.contains_key(&rule_path)
&& !self.imports.contains_key(&rule_path)
{
bail!(span.error("var is unsafe"));
}
@@ -2609,7 +2758,7 @@ impl Interpreter {
rule_path.clone() + "." + &fields[0..i].join(".")
};
if self.rules.get(&path).is_some() || self.default_rules.get(&path).is_some() {
if self.rules.contains_key(&path) || self.default_rules.contains_key(&path) {
self.ensure_rule_evaluated(path)?;
found = true;
break;
@@ -3014,7 +3163,7 @@ impl Interpreter {
return Ok(());
}
let scopes = std::mem::take(&mut self.scopes);
let scopes = core::mem::take(&mut self.scopes);
let mut path =
Parser::get_path_ref_components(&self.module.clone().unwrap().package.refr)?;
@@ -3242,7 +3391,7 @@ impl Interpreter {
// Back up local variables of current function and empty
// the local variables of callee function.
let scopes = std::mem::take(&mut self.scopes);
let scopes = core::mem::take(&mut self.scopes);
let prev_module = self.set_current_module(Some(module.clone()))?;
let res = self.eval_rule_impl(module, rule);
@@ -3417,10 +3566,10 @@ impl Interpreter {
}
match self.rules.entry(path) {
Entry::Occupied(o) => {
MapEntry::Occupied(o) => {
o.into_mut().push(rule.clone());
}
Entry::Vacant(v) => {
MapEntry::Vacant(v) => {
v.insert(vec![rule.clone()]);
}
}
@@ -3444,7 +3593,7 @@ impl Interpreter {
}
match self.default_rules.entry(path) {
Entry::Occupied(o) => {
MapEntry::Occupied(o) => {
if idx + 1 == comps.len() {
for (_, i) in o.get() {
if index.is_some() && i.is_some() {
@@ -3460,7 +3609,7 @@ impl Interpreter {
}
o.into_mut().push((rule.clone(), index.clone()));
}
Entry::Vacant(v) => {
MapEntry::Vacant(v) => {
v.insert(vec![(rule.clone(), index.clone())]);
}
}
@@ -3483,7 +3632,8 @@ impl Interpreter {
},
Expr::Var(v) if v.0.text() == "input" => {
// Warn redundant import of input. Ignore it.
eprintln!(
#[cfg(feature = "std")]
std::eprintln!(
"{}",
import
.refr
@@ -3560,7 +3710,7 @@ impl Interpreter {
nargs: u8,
extension: Box<dyn Extension>,
) -> Result<()> {
if let std::collections::hash_map::Entry::Vacant(v) = self.extensions.entry(path) {
if let MapEntry::Vacant(v) = self.extensions.entry(path) {
v.insert((nargs, Rc::new(extension)));
Ok(())
} else {
@@ -3686,19 +3836,19 @@ impl Interpreter {
#[cfg(feature = "coverage")]
pub fn clear_coverage_data(&mut self) {
self.coverage = HashMap::new();
self.coverage = Map::new();
}
pub fn set_gather_prints(&mut self, b: bool) {
if b != self.gather_prints {
// Clear existing prints.
std::mem::take(&mut self.prints);
core::mem::take(&mut self.prints);
}
self.gather_prints = b;
}
pub fn take_prints(&mut self) -> Result<Vec<String>> {
Ok(std::mem::take(&mut self.prints))
Ok(core::mem::take(&mut self.prints))
}
pub fn eval_rule_in_path(&mut self, path: String) -> Result<Value> {

View File

@@ -1,15 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use core::fmt::{Debug, Formatter};
use crate::*;
use core::cmp;
use core::fmt::{self, Debug, Formatter};
use core::iter::Peekable;
use core::str::CharIndices;
use std::convert::AsRef;
use std::hash::{Hash, Hasher};
use std::path::Path;
use crate::Rc;
use crate::Value;
use anyhow::{anyhow, bail, Result};
@@ -18,7 +15,7 @@ use anyhow::{anyhow, bail, Result};
struct SourceInternal {
pub file: String,
pub contents: String,
pub lines: Vec<(u16, u16)>,
pub lines: Vec<(u32, u32)>,
}
#[derive(Clone)]
@@ -26,34 +23,35 @@ pub struct Source {
src: Rc<SourceInternal>,
}
impl std::cmp::Ord for Source {
fn cmp(&self, other: &Source) -> std::cmp::Ordering {
impl cmp::Ord for Source {
fn cmp(&self, other: &Source) -> cmp::Ordering {
Rc::as_ptr(&self.src).cmp(&Rc::as_ptr(&other.src))
}
}
impl std::cmp::PartialOrd for Source {
fn partial_cmp(&self, other: &Source) -> Option<std::cmp::Ordering> {
impl cmp::PartialOrd for Source {
fn partial_cmp(&self, other: &Source) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::PartialEq for Source {
impl cmp::PartialEq for Source {
fn eq(&self, other: &Source) -> bool {
Rc::as_ptr(&self.src) == Rc::as_ptr(&other.src)
}
}
impl std::cmp::Eq for Source {}
impl cmp::Eq for Source {}
impl Hash for Source {
fn hash<H: Hasher>(&self, state: &mut H) {
#[cfg(feature = "std")]
impl std::hash::Hash for Source {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Rc::as_ptr(&self.src).hash(state)
}
}
impl Debug for Source {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
self.src.file.fmt(f)
}
}
@@ -61,24 +59,24 @@ impl Debug for Source {
#[derive(Clone)]
pub struct SourceStr {
source: Source,
start: u16,
end: u16,
start: u32,
end: u32,
}
impl Debug for SourceStr {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
self.text().fmt(f)
}
}
impl std::fmt::Display for SourceStr {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
std::fmt::Display::fmt(&self.text(), f)
impl fmt::Display for SourceStr {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Display::fmt(&self.text(), f)
}
}
impl SourceStr {
pub fn new(source: Source, start: u16, end: u16) -> Self {
pub fn new(source: Source, start: u32, end: u32) -> Self {
Self { source, start, end }
}
@@ -95,72 +93,74 @@ impl SourceStr {
}
}
impl std::cmp::PartialEq for SourceStr {
impl cmp::PartialEq for SourceStr {
fn eq(&self, other: &Self) -> bool {
self.text().eq(other.text())
}
}
impl std::cmp::Eq for SourceStr {}
impl cmp::Eq for SourceStr {}
impl std::cmp::PartialOrd for SourceStr {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
impl cmp::PartialOrd for SourceStr {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.text().cmp(other.text()))
}
}
impl std::cmp::Ord for SourceStr {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
impl cmp::Ord for SourceStr {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.text().cmp(other.text())
}
}
impl Source {
pub fn new(file: String, contents: String) -> Source {
pub fn from_contents(file: String, contents: String) -> Result<Source> {
let max_size = u32::MAX as usize - 2; // Account for rows, cols possibly starting at 1, EOF etc.
if contents.len() > max_size {
bail!("{file} exceeds maximum allowed policy file size {max_size}");
}
let mut lines = vec![];
let mut prev_ch = ' ';
let mut prev_pos = 0u16;
let mut start = 0u16;
let mut prev_pos = 0u32;
let mut start = 0u32;
for (i, ch) in contents.char_indices() {
if ch == '\n' {
let end = match prev_ch {
'\r' => prev_pos,
_ => i as u16,
_ => i as u32,
};
lines.push((start, end));
start = i as u16 + 1;
start = i as u32 + 1;
}
prev_ch = ch;
prev_pos = i as u16;
prev_pos = i as u32;
}
if (start as usize) < contents.len() {
lines.push((start, contents.len() as u16));
lines.push((start, contents.len() as u32));
} else if contents.is_empty() {
lines.push((0, 0));
} else {
let s = (contents.len() - 1) as u16;
let s = (contents.len() - 1) as u32;
lines.push((s, s));
}
Self {
Ok(Self {
src: Rc::new(SourceInternal {
file,
contents,
lines,
}),
}
})
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Source> {
#[cfg(feature = "std")]
pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Source> {
let contents = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(e) => bail!("Failed to read {}. {e}", path.as_ref().display()),
};
// TODO: retain path instead of converting to string
Ok(Self::new(
path.as_ref().to_string_lossy().to_string(),
contents,
))
Self::from_contents(path.as_ref().to_string_lossy().to_string(), contents)
}
pub fn file(&self) -> &String {
@@ -169,7 +169,7 @@ impl Source {
pub fn contents(&self) -> &String {
&self.src.contents
}
pub fn line(&self, idx: u16) -> &str {
pub fn line(&self, idx: u32) -> &str {
let idx = idx as usize;
if idx < self.src.lines.len() {
let (start, end) = self.src.lines[idx];
@@ -179,7 +179,7 @@ impl Source {
}
}
pub fn message(&self, line: u16, col: u16, kind: &str, msg: &str) -> String {
pub fn message(&self, line: u32, col: u32, kind: &str, msg: &str) -> String {
if line as usize > self.src.lines.len() {
return format!("{}: invalid line {} specified", self.src.file, line);
}
@@ -206,7 +206,7 @@ impl Source {
)
}
pub fn error(&self, line: u16, col: u16, msg: &str) -> anyhow::Error {
pub fn error(&self, line: u32, col: u32, msg: &str) -> anyhow::Error {
anyhow!(self.message(line, col, "error", msg))
}
}
@@ -214,10 +214,10 @@ impl Source {
#[derive(Clone)]
pub struct Span {
pub source: Source,
pub line: u16,
pub col: u16,
pub start: u16,
pub end: u16,
pub line: u32,
pub col: u32,
pub start: u32,
pub end: u32,
}
impl Span {
@@ -239,7 +239,7 @@ impl Span {
}
impl Debug for Span {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
let t = self.text().escape_debug().to_string();
let max = 32;
let (txt, trailer) = if t.len() > max {
@@ -272,8 +272,8 @@ pub struct Token(pub TokenKind, pub Span);
pub struct Lexer<'source> {
source: Source,
iter: Peekable<CharIndices<'source>>,
line: u16,
col: u16,
line: u32,
col: u32,
}
impl<'source> Lexer<'source> {
@@ -312,15 +312,15 @@ impl<'source> Lexer<'source> {
}
}
let end = self.peek().0;
self.col += (end - start) as u16;
self.col += (end - start) as u32;
Ok(Token(
TokenKind::Ident,
Span {
source: self.source.clone(),
line: self.line,
col,
start: start as u16,
end: end as u16,
start: start as u32,
end: end as u32,
},
))
}
@@ -363,7 +363,7 @@ impl<'source> Lexer<'source> {
}
let end = self.peek().0;
self.col += (end - start) as u16;
self.col += (end - start) as u32;
// Check for invalid number.Valid number cannot be followed by
// these characters:
@@ -403,8 +403,8 @@ impl<'source> Lexer<'source> {
source: self.source.clone(),
line: self.line,
col,
start: start as u16,
end: end as u16,
start: start as u32,
end: end as u32,
},
))
}
@@ -440,8 +440,8 @@ impl<'source> Lexer<'source> {
source: self.source.clone(),
line,
col,
start: start as u16,
end: end as u16 - 1,
start: start as u32,
end: end as u32 - 1,
},
))
}
@@ -453,9 +453,9 @@ impl<'source> Lexer<'source> {
let (start, _) = self.peek();
loop {
let (offset, ch) = self.peek();
let col = self.col + (offset - start) as u16;
let col = self.col + (offset - start) as u32;
match ch {
'"' | '#' | '\x00' => {
'"' | '\x00' => {
break;
}
'\\' => {
@@ -468,7 +468,7 @@ impl<'source> Lexer<'source> {
'u' => {
for _i in 0..4 {
let (offset, ch) = self.peek();
let col = self.col + (offset - start) as u16;
let col = self.col + (offset - start) as u32;
if !ch.is_ascii_hexdigit() {
return Err(self.source.error(
line,
@@ -484,7 +484,7 @@ impl<'source> Lexer<'source> {
}
_ => {
// check for valid json chars
let col = self.col + (offset - start) as u16;
let col = self.col + (offset - start) as u32;
if !('\u{0020}'..='\u{10FFFF}').contains(&ch) {
return Err(self.source.error(line, col, "invalid character in string"));
}
@@ -499,7 +499,7 @@ impl<'source> Lexer<'source> {
self.iter.next();
let end = self.peek().0;
self.col += (end - start) as u16;
self.col += (end - start) as u32;
// Ensure that the string is parsable in Rust.
match serde_json::from_str::<String>(&self.source.contents()[start - 1..end]) {
@@ -522,8 +522,8 @@ impl<'source> Lexer<'source> {
source: self.source.clone(),
line,
col: col + 1,
start: start as u16,
end: end as u16 - 1,
start: start as u32,
end: end as u32 - 1,
},
))
}
@@ -593,14 +593,14 @@ impl<'source> Lexer<'source> {
source: self.source.clone(),
line: self.line,
col,
start: start as u16,
end: start as u16 + 1,
start: start as u32,
end: start as u32 + 1,
}))
}
':' => {
self.col += 1;
self.iter.next();
let mut end = start as u16 + 1;
let mut end = start as u32 + 1;
if self.peek().1 == '=' {
self.col += 1;
self.iter.next();
@@ -610,7 +610,7 @@ impl<'source> Lexer<'source> {
source: self.source.clone(),
line: self.line,
col,
start: start as u16,
start: start as u32,
end
}))
}
@@ -626,8 +626,8 @@ impl<'source> Lexer<'source> {
source: self.source.clone(),
line: self.line,
col,
start: start as u16,
end: self.peek().0 as u16,
start: start as u32,
end: self.peek().0 as u32,
}))
}
'!' if self.peekahead(1).1 == '=' => {
@@ -638,8 +638,8 @@ impl<'source> Lexer<'source> {
source: self.source.clone(),
line: self.line,
col,
start: start as u16,
end: self.peek().0 as u16,
start: start as u32,
end: self.peek().0 as u32,
}))
}
'"' => self.read_string(),
@@ -648,8 +648,8 @@ impl<'source> Lexer<'source> {
source: self.source.clone(),
line:self.line,
col,
start: start as u16,
end: start as u16
start: start as u32,
end: start as u32
})),
_ if chr.is_ascii_digit() => self.read_number(),
_ if chr.is_ascii_alphabetic() || chr == '_' => {

View File

@@ -3,10 +3,19 @@
// Use README.md as crate documentation.
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
#![cfg_attr(docsrs, feature(doc_cfg))]
// We'll default to building for no_std - use core, alloc instead of std.
#![no_std]
extern crate alloc;
use serde::Serialize;
// Import std crate if building with std support.
// We don't import types or macros from std.
// As a result, types and macros from std must be qualified via `std::`
// making dependencies on std easier to spot.
#[cfg(any(feature = "std", test))]
extern crate std;
mod ast;
mod builtins;
mod engine;
@@ -22,10 +31,27 @@ pub use engine::Engine;
pub use value::Value;
#[cfg(feature = "arc")]
use std::sync::Arc as Rc;
use alloc::sync::Arc as Rc;
#[cfg(not(feature = "arc"))]
use std::rc::Rc;
use alloc::rc::Rc;
#[cfg(feature = "std")]
use std::collections::{hash_map::Entry as MapEntry, HashMap as Map, HashSet as Set};
#[cfg(not(feature = "std"))]
use alloc::collections::{btree_map::Entry as MapEntry, BTreeMap as Map, BTreeSet as Set};
use alloc::{
borrow::ToOwned,
boxed::Box,
format,
string::{String, ToString},
vec,
vec::Vec,
};
use core::fmt;
/// Location of an [`Expression`] in a Rego query.
///
@@ -47,9 +73,9 @@ use std::rc::Rc;
#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
pub struct Location {
/// Line number. Starts at 1.
pub row: u16,
pub row: u32,
/// Column number. Starts at 1.
pub col: u16,
pub col: u32,
}
/// An expression in a Rego query.
@@ -334,8 +360,8 @@ impl<'a> Clone for Box<dyn 'a + Extension> {
}
}
impl std::fmt::Debug for dyn Extension {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
impl fmt::Debug for dyn Extension {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> {
f.write_fmt(format_args!("<extension>"))
}
}
@@ -343,6 +369,8 @@ impl std::fmt::Debug for dyn Extension {
#[cfg(feature = "coverage")]
#[cfg_attr(docsrs, doc(cfg(feature = "coverage")))]
pub mod coverage {
use crate::*;
#[derive(Default, serde::Serialize, serde::Deserialize)]
/// Coverage information about a rego policy file.
pub struct File {
@@ -353,10 +381,10 @@ pub mod coverage {
pub code: String,
/// Lines that were evaluated.
pub covered: std::collections::BTreeSet<u32>,
pub covered: alloc::collections::BTreeSet<u32>,
/// Lines that were not evaluated.
pub not_covered: std::collections::BTreeSet<u32>,
pub not_covered: alloc::collections::BTreeSet<u32>,
}
#[derive(Default, serde::Serialize, serde::Deserialize)]
@@ -374,31 +402,30 @@ pub mod coverage {
///
/// <img src="https://github.com/microsoft/regorus/blob/main/docs/coverage.png?raw=true">
pub fn to_colored_string(&self) -> anyhow::Result<String> {
use std::io::Write;
let mut s = Vec::new();
writeln!(&mut s, "COVERAGE REPORT:")?;
pub fn to_string_pretty(&self) -> anyhow::Result<String> {
let mut s = String::default();
s.push_str("COVERAGE REPORT:\n");
for file in self.files.iter() {
if file.not_covered.is_empty() {
writeln!(&mut s, "{} has full coverage", file.path)?;
s.push_str(&format!("{} has full coverage\n", file.path));
continue;
}
writeln!(&mut s, "{}:", file.path)?;
s.push_str(&format!("{}:\n", file.path));
for (line, code) in file.code.split('\n').enumerate() {
let line = line as u32 + 1;
if file.not_covered.contains(&line) {
writeln!(&mut s, "\x1b[31m {line:4} {code}\x1b[0m")?;
s.push_str(&format!("\x1b[31m {line:4} {code}\x1b[0m\n"));
} else if file.covered.contains(&line) {
writeln!(&mut s, "\x1b[32m {line:4} {code}\x1b[0m")?;
s.push_str(&format!("\x1b[32m {line:4} {code}\x1b[0m\n"));
} else {
writeln!(&mut s, " {line:4} {code}")?;
s.push_str(&format!(" {line:4} {code}\n"));
}
}
}
writeln!(&mut s)?;
Ok(std::str::from_utf8(&s)?.to_string())
s.push('\n');
Ok(s)
}
}
}

View File

@@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::str::FromStr;
use core::cmp::{Ord, Ordering};
use core::fmt::{Debug, Formatter};
use std::cmp::{Ord, Ordering};
use std::str::FromStr;
use anyhow::{anyhow, bail, Result};
use serde::ser::Serializer;
use serde::Serialize;
use crate::Rc;
use crate::*;
pub type BigInt = i128;
@@ -61,7 +61,7 @@ pub enum Number {
}
impl Debug for Number {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
match self {
Number::Big(b) => b.d.fmt(f),
}

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