mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
35 Commits
regorus-v0
...
regorus-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
658f34753b | ||
|
|
5eda76fd00 | ||
|
|
97914d5596 | ||
|
|
ffb79f1b30 | ||
|
|
536c6ede5c | ||
|
|
ff0adf054e | ||
|
|
2b82f2842f | ||
|
|
d09c445add | ||
|
|
33fe9d5039 | ||
|
|
3d08f6eef9 | ||
|
|
3ac9b1121e | ||
|
|
9894f00829 | ||
|
|
495e91c75a | ||
|
|
25ac1b6c95 | ||
|
|
209ece9dee | ||
|
|
0952d42432 | ||
|
|
56bd8841d0 | ||
|
|
67f65c0561 | ||
|
|
5422bde391 | ||
|
|
68a0f8fcce | ||
|
|
97b70a1c1b | ||
|
|
e86b590f91 | ||
|
|
01fc234a33 | ||
|
|
e9cd6d6afc | ||
|
|
69d6426663 | ||
|
|
0a39e434db | ||
|
|
9832a297ed | ||
|
|
c6fb8cf044 | ||
|
|
55abbb2b42 | ||
|
|
3743f32edc | ||
|
|
744dad6126 | ||
|
|
7fde3382f6 | ||
|
|
316f3a7692 | ||
|
|
de56cce7cb | ||
|
|
72ced23366 |
31
.github/workflows/pr.yml
vendored
31
.github/workflows/pr.yml
vendored
@@ -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)
|
||||
|
||||
10
.github/workflows/publish-python.yml
vendored
10
.github/workflows/publish-python.yml
vendored
@@ -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:
|
||||
|
||||
2
.github/workflows/release-plz.yml
vendored
2
.github/workflows/release-plz.yml
vendored
@@ -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
43
.github/workflows/test-c-cpp.yml
vendored
Normal 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
26
.github/workflows/test-csharp.yml
vendored
Normal 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
22
.github/workflows/test-ffi.yml
vendored
Normal 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
33
.github/workflows/test-go.yml
vendored
Normal 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
37
.github/workflows/test-java.yml
vendored
Normal 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
33
.github/workflows/test-musl.yml
vendored
Normal 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
24
.github/workflows/test-no-std.yml
vendored
Normal 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
38
.github/workflows/test-python.yml
vendored
Normal 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
31
.github/workflows/test-ruby.yml
vendored
Normal 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
32
.github/workflows/test-wasm.yml
vendored
Normal 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
37
.github/workflows/tests-debug.yml
vendored
Normal 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
12
.gitignore
vendored
@@ -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
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -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
|
||||
|
||||
87
Cargo.toml
87
Cargo.toml
@@ -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
|
||||
|
||||
87
README.md
87
README.md
@@ -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:
|
||||
|
||||
41
bindings/c-nostd/CMakeLists.txt
Normal file
41
bindings/c-nostd/CMakeLists.txt
Normal 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
98
bindings/c-nostd/main.c
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 = []
|
||||
@@ -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
|
||||
@@ -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 */
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module regorus-test
|
||||
module regorus_test
|
||||
|
||||
go 1.21.5
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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
36
bindings/java/Test.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(®o)?.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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
|
||||
@@ -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>()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1 +1 @@
|
||||
ruby 3.3.0
|
||||
ruby 3.3.1
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Regorus
|
||||
VERSION = "0.1.0"
|
||||
VERSION = "0.1.5"
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
10
release-plz.toml
Normal 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
|
||||
@@ -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)
|
||||
|
||||
20
src/ast.rs
20
src/ast.rs
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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, ¶ms[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, ¶ms[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, ¶ms[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(
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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, ¶ms[0], &args[0])?;
|
||||
let _ = crate::builtins::utils::ensure_string(fcn, ¶ms[0], &args[0])?;
|
||||
let n = ensure_numeric(fcn, ¶ms[0], &args[1])?;
|
||||
|
||||
Ok(match n.as_u64() {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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, ¶ms[0], &args[0])?;
|
||||
let v2 = ensure_string(name, ¶ms[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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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, ¶ms[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()?);
|
||||
|
||||
|
||||
@@ -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, ¶ms[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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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() + "." + ⌖
|
||||
@@ -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> {
|
||||
|
||||
158
src/lexer.rs
158
src/lexer.rs
@@ -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 == '_' => {
|
||||
|
||||
67
src/lib.rs
67
src/lib.rs
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user