mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
49 Commits
v0.1.0-alp
...
regorus-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b80ef2d015 | ||
|
|
7e3fc08a14 | ||
|
|
48982222c5 | ||
|
|
90757210bc | ||
|
|
7bc9a50a52 | ||
|
|
08f3007b5c | ||
|
|
863601c2d5 | ||
|
|
976c04be8a | ||
|
|
fbfed6b49c | ||
|
|
595f9d34d5 | ||
|
|
a8c0588426 | ||
|
|
0e053832db | ||
|
|
f51731e584 | ||
|
|
10f2caf0c0 | ||
|
|
22047287b4 | ||
|
|
3a86c83827 | ||
|
|
d3d5367fd4 | ||
|
|
f3d9652a73 | ||
|
|
bdb2aba596 | ||
|
|
8d282f1ffd | ||
|
|
7d32bd9377 | ||
|
|
53b990f97d | ||
|
|
13eb06e4be | ||
|
|
3b2e639918 | ||
|
|
a381c38a90 | ||
|
|
5044d54d18 | ||
|
|
bb1b25ff2f | ||
|
|
5fd826d79a | ||
|
|
d4dcbe7b9e | ||
|
|
fc68bf9c8b | ||
|
|
a95a9d21b3 | ||
|
|
5717f9c249 | ||
|
|
dda525b989 | ||
|
|
22260ac46f | ||
|
|
25b1ffe6d7 | ||
|
|
bcde71b8f0 | ||
|
|
1ab27b253b | ||
|
|
761d11ef48 | ||
|
|
beea2274d3 | ||
|
|
5799a3e6c4 | ||
|
|
ca91c0ea20 | ||
|
|
bf75813c43 | ||
|
|
35ec9c03ad | ||
|
|
8ca863c661 | ||
|
|
055bdd295f | ||
|
|
0af97840f7 | ||
|
|
6eca85b497 | ||
|
|
f3884e87e5 | ||
|
|
d39200a52c |
@@ -1,14 +0,0 @@
|
||||
[build]
|
||||
#target = "x86_64-unknown-linux-musl"
|
||||
|
||||
# Flags to enable code-coverage for all builds.
|
||||
# These can be removed later.
|
||||
#rustflags = ["-Cinstrument-coverage"]
|
||||
incremental = true
|
||||
|
||||
[env]
|
||||
# Name of coverage instrumentation log file.
|
||||
LLVM_PROFILE_FILE="target/cargo-test-%p-%m.profraw"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-Cinstrument-coverage"]
|
||||
35
.devcontainer/devcontainer.json
Normal file
35
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,35 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
|
||||
{
|
||||
"name": "Rust",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/dotnet:2": {},
|
||||
"ghcr.io/devcontainers/features/python:1": {}
|
||||
}
|
||||
|
||||
// Use 'mounts' to make the cargo cache persistent in a Docker Volume.
|
||||
// "mounts": [
|
||||
// {
|
||||
// "source": "devcontainer-cargo-cache-${devcontainerId}",
|
||||
// "target": "/usr/local/cargo",
|
||||
// "type": "volume"
|
||||
// }
|
||||
// ]
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "rustc --version",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
44
.github/workflows/pr.yml
vendored
Normal file
44
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Add musl target
|
||||
run: rustup target add x86_64-unknown-linux-musl
|
||||
- name: Install musl-gcc
|
||||
run: sudo apt update && sudo apt install -y musl-tools
|
||||
- name: Format Check
|
||||
run: cargo fmt --check
|
||||
- name: Build
|
||||
run: cargo build -r --verbose
|
||||
- name: Doc Tests
|
||||
run: cargo test -r --doc
|
||||
- name: Run tests
|
||||
run: cargo test -r --verbose
|
||||
- name: Run tests (ACI)
|
||||
run: cargo test -r --test aci
|
||||
- name: Run tests (OPA Conformance)
|
||||
run: >-
|
||||
cargo test -r --test opa --features opa-testutil -- $(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 --target x86_64-unknown-linux-musl -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
89
.github/workflows/publish-java.yml
vendored
Normal file
89
.github/workflows/publish-java.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
name: publish-java
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build for ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
extension: so
|
||||
build_cmd: zigbuild
|
||||
glibc: "2.17"
|
||||
- target: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
extension: so
|
||||
build_cmd: zigbuild
|
||||
glibc: "2.17"
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
extension: dylib
|
||||
- target: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
extension: dylib
|
||||
- target: x86_64-pc-windows-msvc
|
||||
os: windows-latest
|
||||
extension: dll
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 8
|
||||
distribution: "corretto"
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- if: ${{ matrix.build_cmd == 'zigbuild' }}
|
||||
run: pip install cargo-zigbuild
|
||||
- run: cargo ${{ matrix.build_cmd || 'build' }} --release --target ${{ matrix.target }}${{ matrix.glibc && format('.{0}', matrix.glibc) || '' }} --manifest-path ./bindings/java/Cargo.toml
|
||||
- run: mkdir -p native/${{ matrix.target }}
|
||||
- run: mv target/${{ matrix.target }}/release/*.${{ matrix.extension }} ./native/${{ matrix.target }}/
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: native-libraries-${{ matrix.target }}
|
||||
path: native/
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 8
|
||||
distribution: "corretto"
|
||||
server-id: ossrh
|
||||
server-username: MAVEN_USERNAME
|
||||
server-password: MAVEN_PASSWORD
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: native-libraries-*
|
||||
merge-multiple: true
|
||||
path: ./bindings/java/native/
|
||||
- run: mvn package
|
||||
working-directory: ./bindings/java
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: built-jars
|
||||
path: ./bindings/java/target/regorus-java-*.jar
|
||||
- run: mvn deploy
|
||||
working-directory: ./bindings/java
|
||||
env:
|
||||
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
|
||||
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
|
||||
114
.github/workflows/publish-python.yml
vendored
Normal file
114
.github/workflows/publish-python.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# This file is autogenerated by maturin v1.4.0
|
||||
# To update, run
|
||||
#
|
||||
# maturin generate-ci --manifest-path bindings/python/Cargo.toml github
|
||||
#
|
||||
name: publish-python
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
architecture: ${{ matrix.target }}
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, aarch64, universal2-apple-darwin]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist --find-interpreter --manifest-path bindings/python/Cargo.toml
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build sdist
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist --manifest-path bindings/python/Cargo.toml
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
# Commented out for initial release.
|
||||
# if: "startsWith(github.ref, 'refs/tags/')"
|
||||
needs: [linux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
- name: Publish to PyPI
|
||||
uses: PyO3/maturin-action@v1
|
||||
env:
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
with:
|
||||
command: upload
|
||||
args: --non-interactive --skip-existing *
|
||||
32
.github/workflows/publish-wasm.yml
vendored
Normal file
32
.github/workflows/publish-wasm.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: publish-wasm
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
publish-wasm:
|
||||
name: publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- name: Build
|
||||
run: wasm-pack build --target nodejs --release
|
||||
working-directory: ./bindings/wasm
|
||||
- name: Publish
|
||||
run: wasm-pack publish --target nodejs
|
||||
working-directory: ./bindings/wasm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||
7
.github/workflows/release-plz.yml
vendored
7
.github/workflows/release-plz.yml
vendored
@@ -4,10 +4,7 @@ permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
release-plz:
|
||||
@@ -24,4 +21,4 @@ jobs:
|
||||
uses: MarcoIeni/release-plz-action@v0.5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
54
.github/workflows/rust-clippy.yml
vendored
Normal file
54
.github/workflows/rust-clippy.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# rust-clippy is a tool that runs a bunch of lints to catch common
|
||||
# mistakes in your Rust code and help improve your Rust code.
|
||||
# More details at https://github.com/rust-lang/rust-clippy
|
||||
# and https://rust-lang.github.io/rust-clippy/
|
||||
|
||||
name: rust-clippy analyze
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main" ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
rust-clippy-analyze:
|
||||
name: Run rust-clippy analyzing
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
override: true
|
||||
|
||||
- name: Install required cargo
|
||||
run: cargo install clippy-sarif sarif-fmt
|
||||
|
||||
- name: Run rust-clippy
|
||||
run:
|
||||
cargo clippy
|
||||
--all-features
|
||||
--message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload analysis results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v1
|
||||
with:
|
||||
sarif_file: rust-clippy-results.sarif
|
||||
wait-for-processing: true
|
||||
37
.github/workflows/rust.yml
vendored
37
.github/workflows/rust.yml
vendored
@@ -1,37 +0,0 @@
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Add musl target
|
||||
run: rustup target add x86_64-unknown-linux-musl
|
||||
- name: Format Check
|
||||
run: cargo fmt --check
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Build Tests
|
||||
run: cargo build --all-targets --verbose
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
- name: Build (MUSL)
|
||||
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl
|
||||
- name: Run tests (MUSL)
|
||||
run: cargo test --verbose --target x86_64-unknown-linux-musl
|
||||
- name: Run tests (OPA Conformance)
|
||||
run: >-
|
||||
cargo test --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
162
CHANGELOG.md
162
CHANGELOG.md
@@ -6,6 +6,168 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.2](https://github.com/microsoft/regorus/compare/regorus-v0.1.1...regorus-v0.1.2) - 2024-03-22
|
||||
|
||||
### Other
|
||||
- Handle non simple refs in chained expressions ([#182](https://github.com/microsoft/regorus/pull/182))
|
||||
- Ability to gather print statements ([#179](https://github.com/microsoft/regorus/pull/179))
|
||||
- Top-down evaluation ([#177](https://github.com/microsoft/regorus/pull/177))
|
||||
- Make unary `-` operator OPA compatible. ([#175](https://github.com/microsoft/regorus/pull/175))
|
||||
- Don't use deprecated chrono `Duration` methods ([#173](https://github.com/microsoft/regorus/pull/173))
|
||||
- Propagate Undefined in object expressions ([#171](https://github.com/microsoft/regorus/pull/171))
|
||||
- Bump to OPA v0.62.0 ([#169](https://github.com/microsoft/regorus/pull/169))
|
||||
- Fix regression ([#164](https://github.com/microsoft/regorus/pull/164))
|
||||
- Separately keep track of whether rules have been evaluated or not ([#163](https://github.com/microsoft/regorus/pull/163))
|
||||
- Link Linux libraries against glibc 2.17 using `cargo-zigbuild` ([#158](https://github.com/microsoft/regorus/pull/158))
|
||||
|
||||
## [0.1.1](https://github.com/microsoft/regorus/compare/regorus-v0.1.0...regorus-v0.1.1) - 2024-02-23
|
||||
|
||||
### Other
|
||||
- Handle else block without body ([#155](https://github.com/microsoft/regorus/pull/155))
|
||||
- Ignore errors from builtin functions in non strict mode ([#154](https://github.com/microsoft/regorus/pull/154))
|
||||
- Java publishing ([#151](https://github.com/microsoft/regorus/pull/151))
|
||||
- Document coverage feature; Convenience query functions ([#152](https://github.com/microsoft/regorus/pull/152))
|
||||
- Policy Coverage ([#149](https://github.com/microsoft/regorus/pull/149))
|
||||
- Initial implementation of policy coverage ([#146](https://github.com/microsoft/regorus/pull/146))
|
||||
- Java bindings ([#147](https://github.com/microsoft/regorus/pull/147))
|
||||
- Preserve false in single-expression queries ([#145](https://github.com/microsoft/regorus/pull/145))
|
||||
- Create rust-clippy.yml ([#143](https://github.com/microsoft/regorus/pull/143))
|
||||
- `arc` feature to enable using Engine and other data structures from multiple threads ([#142](https://github.com/microsoft/regorus/pull/142))
|
||||
- genpolicy tweaks ([#141](https://github.com/microsoft/regorus/pull/141))
|
||||
- io.jwt.decode ([#140](https://github.com/microsoft/regorus/pull/140))
|
||||
- Use compact_rc ([#139](https://github.com/microsoft/regorus/pull/139))
|
||||
- Scripting tweaks ([#138](https://github.com/microsoft/regorus/pull/138))
|
||||
|
||||
## [0.1.0-alpha.3](https://github.com/microsoft/regorus/compare/regorus-v0.1.0-alpha.2...regorus-v0.1.0-alpha.3) - 2024-02-01
|
||||
|
||||
### Fixed
|
||||
- fix bitwise.and and add tests ([#19](https://github.com/microsoft/regorus/pull/19))
|
||||
|
||||
### Other
|
||||
- Document bindings ([#119](https://github.com/microsoft/regorus/pull/119))
|
||||
- Conform to OPA 0.61.0. ([#118](https://github.com/microsoft/regorus/pull/118))
|
||||
- Update publish-python.yml
|
||||
- Publish python packages ([#117](https://github.com/microsoft/regorus/pull/117))
|
||||
- Publish wasm ([#116](https://github.com/microsoft/regorus/pull/116))
|
||||
- Set working-directory for wasm-pack
|
||||
- Python bindings ([#115](https://github.com/microsoft/regorus/pull/115))
|
||||
- WASM binding ([#114](https://github.com/microsoft/regorus/pull/114))
|
||||
- release ([#112](https://github.com/microsoft/regorus/pull/112))
|
||||
- Improve crate documentation ([#111](https://github.com/microsoft/regorus/pull/111))
|
||||
- Try out manual trigger for release-plz ([#110](https://github.com/microsoft/regorus/pull/110))
|
||||
- - Document Location, Expression, QueryResult ([#109](https://github.com/microsoft/regorus/pull/109))
|
||||
- Update Cargo.toml ([#108](https://github.com/microsoft/regorus/pull/108))
|
||||
- Change version to `0.1.0-alpha.1` ([#107](https://github.com/microsoft/regorus/pull/107))
|
||||
- Add crate documentation ([#106](https://github.com/microsoft/regorus/pull/106))
|
||||
- Release preparation ([#105](https://github.com/microsoft/regorus/pull/105))
|
||||
- Update READEME.md with current status, grammar etc. ([#102](https://github.com/microsoft/regorus/pull/102))
|
||||
- Implement builtin `time.parse_duration_ns` method ([#100](https://github.com/microsoft/regorus/pull/100))
|
||||
- Implement import keyword ([#101](https://github.com/microsoft/regorus/pull/101))
|
||||
- OPA conformance: Pass refheads test suite ([#90](https://github.com/microsoft/regorus/pull/90))
|
||||
- OPA conformance: Ensure that `withkeyword` OPA tests pass ([#88](https://github.com/microsoft/regorus/pull/88))
|
||||
- Handle walk builtin as a loop expression ([#86](https://github.com/microsoft/regorus/pull/86))
|
||||
- Implement most of the builtin `time` module ([#82](https://github.com/microsoft/regorus/pull/82))
|
||||
- OPA Conformance
|
||||
- OPA conformance ([#81](https://github.com/microsoft/regorus/pull/81))
|
||||
- More OPA conformance ([#77](https://github.com/microsoft/regorus/pull/77))
|
||||
- OPA conformance ([#71](https://github.com/microsoft/regorus/pull/71))
|
||||
- Builtin UUID module ([#68](https://github.com/microsoft/regorus/pull/68))
|
||||
- Add tests for builtin `string::format_int` method ([#65](https://github.com/microsoft/regorus/pull/65))
|
||||
- More builtins and semantic improvements ([#66](https://github.com/microsoft/regorus/pull/66))
|
||||
- More OPA conformance; in-progress: ability to trace interpreter ([#63](https://github.com/microsoft/regorus/pull/63))
|
||||
- More OPA conformant semantics ([#62](https://github.com/microsoft/regorus/pull/62))
|
||||
- Updated readme. Added bundle support. ([#61](https://github.com/microsoft/regorus/pull/61))
|
||||
- crypto builtins ([#57](https://github.com/microsoft/regorus/pull/57))
|
||||
- Regex and Glob builtins ([#56](https://github.com/microsoft/regorus/pull/56))
|
||||
- Formalize concept of a Number ([#55](https://github.com/microsoft/regorus/pull/55))
|
||||
- Lock down ACI tests and more OPA test folders ([#54](https://github.com/microsoft/regorus/pull/54))
|
||||
- Fix scheduling regression ([#53](https://github.com/microsoft/regorus/pull/53))
|
||||
- add full api to engine ([#50](https://github.com/microsoft/regorus/pull/50))
|
||||
- Use Rc<str> instead of string. ([#52](https://github.com/microsoft/regorus/pull/52))
|
||||
- More library functions ([#51](https://github.com/microsoft/regorus/pull/51))
|
||||
- Added semver.is_valid and semver.compare ([#49](https://github.com/microsoft/regorus/pull/49))
|
||||
- OPA conformance tests ([#45](https://github.com/microsoft/regorus/pull/45))
|
||||
- Avoid dependency on `source lifetime. ([#43](https://github.com/microsoft/regorus/pull/43))
|
||||
- Allow with modifier for builtin and user functions ([#42](https://github.com/microsoft/regorus/pull/42))
|
||||
- Special cases of refs to data ([#41](https://github.com/microsoft/regorus/pull/41))
|
||||
- Fix scheduling statements that don't create bindings ([#40](https://github.com/microsoft/regorus/pull/40))
|
||||
- Ability to run the OPA testsuite ([#39](https://github.com/microsoft/regorus/pull/39))
|
||||
- Engine ([#38](https://github.com/microsoft/regorus/pull/38))
|
||||
- Use Ref for storing ast nodes in collections. ([#37](https://github.com/microsoft/regorus/pull/37))
|
||||
- all, any deprecated functions ([#35](https://github.com/microsoft/regorus/pull/35))
|
||||
- all, any deprecated functions ([#34](https://github.com/microsoft/regorus/pull/34))
|
||||
- Improvements ([#33](https://github.com/microsoft/regorus/pull/33))
|
||||
- Order query expression results ([#32](https://github.com/microsoft/regorus/pull/32))
|
||||
- Scheduling of statements in user queries ([#31](https://github.com/microsoft/regorus/pull/31))
|
||||
- eval, lex, parse commands ([#30](https://github.com/microsoft/regorus/pull/30))
|
||||
- eval_user_query for OPA style results ([#29](https://github.com/microsoft/regorus/pull/29))
|
||||
- Arity for builtins ([#28](https://github.com/microsoft/regorus/pull/28))
|
||||
- Handle chained _ ([#27](https://github.com/microsoft/regorus/pull/27))
|
||||
- Minimize PR 22 ([#26](https://github.com/microsoft/regorus/pull/26))
|
||||
- improve errors location ([#23](https://github.com/microsoft/regorus/pull/23))
|
||||
- Fix clippy warning ([#25](https://github.com/microsoft/regorus/pull/25))
|
||||
- negation of an undefined value should return true ([#21](https://github.com/microsoft/regorus/pull/21))
|
||||
- Ensure that scopes are cleaned up correctly upon error. ([#20](https://github.com/microsoft/regorus/pull/20))
|
||||
- support of or-functions ([#18](https://github.com/microsoft/regorus/pull/18))
|
||||
- Statement Scheduler Implementation
|
||||
- Remove unnecessary lifetime
|
||||
- json.filter, object.filter, object.get, object.keys, object.remove
|
||||
- :to_number builtin
|
||||
- :trace builtin
|
||||
- bitwise builtins
|
||||
- :print builtin
|
||||
- Partial sprintf implementation.
|
||||
- All string functions except sprintf. TODO: Add tests
|
||||
- More string functions without tests
|
||||
- More string functions
|
||||
- concat and contains
|
||||
- string concat (WIP)
|
||||
- Support build on non Linux platforms
|
||||
- Prepare for upstreaming
|
||||
- Test for multi-assign
|
||||
- Support dependencies between vars defined in same statement
|
||||
- Statement scheduler (WIP)
|
||||
- Print small-form table of files without 100% coverage.
|
||||
- Code tweaks to improve coverage
|
||||
- Tests for aggregates builtins
|
||||
- Tests for numbers builtins
|
||||
- Tests for arrays builtins
|
||||
- Tests for types functions
|
||||
- Destructuring of arrays and objects in some-in expressions
|
||||
- `some .. in` implementation
|
||||
- Fix key, value in membership and some-in
|
||||
- refactor
|
||||
- Arrays and Aggregates
|
||||
- Implement `every` statement ([#4](https://github.com/microsoft/regorus/pull/4))
|
||||
- Set loop index variable if not "_" ([#3](https://github.com/microsoft/regorus/pull/3))
|
||||
- Allow comprehensions in default value. ([#2](https://github.com/microsoft/regorus/pull/2))
|
||||
- Lock down numbers
|
||||
- mod function
|
||||
- Builtin functions for numbers (WIP)
|
||||
- Implement comparison operators. Formalize semantics.
|
||||
- Rework assign operations ([#6](https://github.com/microsoft/regorus/pull/6))
|
||||
- Locked down supported values in default rule.
|
||||
- Improvements to github workflow ([#4](https://github.com/microsoft/regorus/pull/4))
|
||||
- Update name to regorus
|
||||
- Update rust.yml
|
||||
- Add simple git action
|
||||
- Add missing config.toml
|
||||
- Update license to MIT
|
||||
- Code from github.com/anakrish/rego-rs
|
||||
- SUPPORT.md committed
|
||||
- SECURITY.md committed
|
||||
- README.md committed
|
||||
- LICENSE committed
|
||||
- CODE_OF_CONDUCT.md committed
|
||||
- Initial commit
|
||||
|
||||
## [0.1.0-alpha.2](https://github.com/microsoft/regorus/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) - 2024-01-19
|
||||
|
||||
### Other
|
||||
- Improve crate documentation ([#111](https://github.com/microsoft/regorus/pull/111))
|
||||
- Try out manual trigger for release-plz ([#110](https://github.com/microsoft/regorus/pull/110))
|
||||
- - Document Location, Expression, QueryResult ([#109](https://github.com/microsoft/regorus/pull/109))
|
||||
|
||||
## [0.1.0-alpha.1](https://github.com/microsoft/regorus/releases/tag/v0.1.0-alpha.1) - 2024-01-15
|
||||
|
||||
### Fixed
|
||||
|
||||
36
Cargo.toml
36
Cargo.toml
@@ -1,7 +1,16 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"bindings/ffi",
|
||||
"bindings/python",
|
||||
"bindings/wasm",
|
||||
"bindings/java",
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "regorus"
|
||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||
version = "0.1.0-alpha.1"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
@@ -9,16 +18,21 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["full-opa"]
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
default = ["full-opa", "arc"]
|
||||
|
||||
arc = ["scientific/arc"]
|
||||
base64 = ["dep:data-encoding"]
|
||||
base64url = ["dep:data-encoding"]
|
||||
coverage = []
|
||||
crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha1", "dep:sha2"]
|
||||
deprecated = []
|
||||
hex = ["dep:data-encoding"]
|
||||
http = []
|
||||
jwt = []
|
||||
jwt = ["dep:jsonwebtoken", "dep:data-encoding"]
|
||||
glob = ["dep:wax"]
|
||||
graph = []
|
||||
jsonschema = ["dep:jsonschema"]
|
||||
@@ -32,6 +46,7 @@ yaml = ["serde_yaml"]
|
||||
full-opa = [
|
||||
"base64",
|
||||
"base64url",
|
||||
"coverage",
|
||||
"crypto",
|
||||
"deprecated",
|
||||
"glob",
|
||||
@@ -58,7 +73,7 @@ serde = {version = "1.0.150", features = ["derive", "rc"] }
|
||||
serde_json = {version = "1.0.89", features = ["arbitrary_precision"] }
|
||||
serde_yaml = {version = "0.9.16", optional = true }
|
||||
log = "0.4.17"
|
||||
env_logger="0.10.0"
|
||||
env_logger="0.11.1"
|
||||
lazy_static = "1.4.0"
|
||||
rand = "0.8.5"
|
||||
num = "0.4.1"
|
||||
@@ -82,9 +97,12 @@ uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }
|
||||
jsonschema = { version = "0.17.1", default-features = false, optional = true }
|
||||
chrono = { version = "0.4.31", optional = true }
|
||||
chrono-tz = { version = "0.8.5", optional = true }
|
||||
|
||||
compact-rc = "0.5.2"
|
||||
jsonwebtoken = { version = "9.2.0", optional = true }
|
||||
itertools = "0.12.1"
|
||||
|
||||
[dev-dependencies]
|
||||
cfg-if = "1.0.0"
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
colored-diff = "0.2.3"
|
||||
serde_yaml = "0.9.16"
|
||||
@@ -107,3 +125,9 @@ required-features = ["full-opa"]
|
||||
name="aci"
|
||||
harness=false
|
||||
test=false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
# To build locally:
|
||||
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
106
README.md
106
README.md
@@ -2,10 +2,26 @@
|
||||
|
||||
**Regorus** is
|
||||
|
||||
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) interpreter written in Rust.
|
||||
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
|
||||
interpreter written in Rust.
|
||||
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
|
||||
|
||||
Regorus is also
|
||||
- *cross-platform* - Written in platform-agnostic Rust.
|
||||
- *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.62.0](https://github.com/open-policy-agent/opa/releases/tag/v0.62.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
|
||||
- *extensible* - Extend the Rego language by implementing custom stateful builtins in Rust.
|
||||
See [add_extension](https://github.com/microsoft/regorus/blob/fc68bf9c8bea36427dae9401a7d1f6ada771f7ab/src/engine.rs#L352).
|
||||
Support for extensibility using other languages coming soon.
|
||||
- *polyglot* - In addition to Rust, Regorus can be used from *C*, *C++*, *C#*, *Golang*, *Java*, *Javascript* and *Python*.
|
||||
This is made possible by the excellent FFI tools available in the Rust ecosystem. See [bindings](#bindings) for information on how to use Regorus from different languages.
|
||||
|
||||
To try out a *Javascript(WASM)* compiled version of Regorus from your browser, visit [Regorus Playground](https://anakrish.github.io/regorus-playground/).
|
||||
|
||||
|
||||
|
||||
Regorus is available as a library that can be easily integrated into your Rust projects.
|
||||
Here is an example of evaluating a simple Rego policy:
|
||||
|
||||
```rust
|
||||
use anyhow::Result;
|
||||
@@ -21,11 +37,11 @@ fn main() -> Result<()> {
|
||||
// Filename to be associated with the policy.
|
||||
"hello.rego".to_string(),
|
||||
|
||||
// Rego policy that just sets a message.
|
||||
r#"
|
||||
package test
|
||||
message = "Hello, World!"
|
||||
"#.to_string()
|
||||
// Rego policy that just sets a message.
|
||||
r#"
|
||||
package test
|
||||
message = "Hello, World!"
|
||||
"#.to_string()
|
||||
)?;
|
||||
|
||||
// Evaluate the policy, fetch the message and print it.
|
||||
@@ -36,12 +52,53 @@ fn main() -> Result<()> {
|
||||
}
|
||||
```
|
||||
|
||||
Regorus passes the [OPA v0.60.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few builtins.
|
||||
See [OPA Conformance](#opa-conformance) below.
|
||||
Regorus is designed with [Confidential Computing](https://confidentialcomputing.io/about/) in mind. In Confidential Computing environments,
|
||||
it is important to be able to control exactly what is being run. Regorus allows enabling and disabling various components using cargo
|
||||
features. By default all features are enabled.
|
||||
|
||||
The default build of regorus example program is 6.4M:
|
||||
```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*
|
||||
```
|
||||
|
||||
|
||||
When all features except for `yaml` are disabled, the binary size drops down to 2.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*
|
||||
```
|
||||
|
||||
Regorus passes the [OPA v0.61.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
||||
builtins. See [OPA Conformance](#opa-conformance) below.
|
||||
|
||||
## Bindings
|
||||
|
||||
Regorus can be used from a variety of languages:
|
||||
|
||||
- *C*: C binding is generated using [cbindgen](https://github.com/mozilla/cbindgen).
|
||||
[corrosion-rs](https://github.com/corrosion-rs/corrosion) can be used to seamlessly use Regorous
|
||||
in your CMake based projects. See [bindings/c](https://github.com/microsoft/regorus/tree/main/bindings/c).
|
||||
- *C++*: C++ binding is generated using [cbindgen](https://github.com/mozilla/cbindgen).
|
||||
[corrosion-rs](https://github.com/corrosion-rs/corrosion) can be used to seamlessly use Regorous
|
||||
in your CMake based projects. See [bindings/cpp](https://github.com/microsoft/regorus/tree/main/bindings/cpp).
|
||||
- *C#*: C# binding is generated using [csbindgen](https://github.com/Cysharp/csbindgen). See [bindings/csharp](https://github.com/microsoft/regorus/tree/main/bindings/csharp) for an example of how to build and use Regorus in your C# projects.
|
||||
- *Golang*: The C bindings are exposed to Golang via [CGo](https://pkg.go.dev/cmd/cgo). See [bindings/go](https://github.com/microsoft/regorus/tree/main/bindings/go) for an example of how to build and use Regorus in your Go projects.
|
||||
- *Python*: Python bindings are generated using [pyo3](https://github.com/PyO3/pyo3). Wheels are created using [maturin](https://github.com/PyO3/maturin). See [bindings/python](https://github.com/microsoft/regorus/tree/main/bindings/python).
|
||||
- *Java*: Java bindings are developed using [jni-rs](https://github.com/jni-rs/jni-rs).
|
||||
See [bindings/java](https://github.com/microsoft/regorus/tree/main/bindings/java).
|
||||
- *Javascript*: Regorus is compiled to WASM using [wasmpack](https://github.com/rustwasm/wasm-pack).
|
||||
See [bindings/wasm](https://github.com/microsoft/regorus/tree/main/bindings/wasm) for an example of using Regorus from nodejs.
|
||||
To try out a *Javascript(WASM)* compiled version of Regorus from your browser, visit [Regorus Playground](https://anakrish.github.io/regorus-playground/).
|
||||
|
||||
To avoid operational overhead, we currently don't publish these bindings to various repositories.
|
||||
It is straight-forward to build these bindings yourself.
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
[examples/regorus](https://github.com/microsoft/regorus/blob/main/examples/regorus.rs) is an example program that shows how to integrate Regorus into your project and evaluate Rego policies.
|
||||
[examples/regorus](https://github.com/microsoft/regorus/blob/main/examples/regorus.rs) is an example program that
|
||||
shows how to integrate Regorus into your project and evaluate Rego policies.
|
||||
|
||||
To build and install it, do
|
||||
|
||||
@@ -94,7 +151,8 @@ This produces the following output
|
||||
}
|
||||
```
|
||||
|
||||
Next, evaluate a sample [policy](examples/example.rego) and [input](examples/input.json) (borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)):
|
||||
Next, evaluate a sample [policy](https://github.com/microsoft/regorus/blob/main/examples/example.rego) and [input](https://github.com/microsoft/regorus/blob/main/examples/input.json)
|
||||
(borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)):
|
||||
|
||||
```bash
|
||||
$ regorus eval -d examples/example.rego -i examples/input.json data.example
|
||||
@@ -106,6 +164,23 @@ Finally, evaluate real-world [policies](tests/aci/) used in Azure Container Inst
|
||||
$ regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.policy.mount_overlay=x
|
||||
```
|
||||
|
||||
## Policy coverage
|
||||
|
||||
Regorus allows determining which lines of a policy have been executed using the `coverage` feature (enabled by default).
|
||||
|
||||
We can try it out using the `regorus` example program by passing in the `--coverage` flag.
|
||||
|
||||
```shell
|
||||
$ regorus eval -d examples/example.rego -i examples/input.json data.example --coverage
|
||||
```
|
||||
|
||||
It produces the following coverage report which shows that all lines are executed except the line that sets `allow` to true.
|
||||
|
||||

|
||||
|
||||
See [Engine::get_coverage_report](https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report) for details.
|
||||
Policy coverage information is useful for debugging your policy as well as to write tests for your policy so that all
|
||||
lines of the policy are exercised by the tests.
|
||||
|
||||
## ACI Policies
|
||||
|
||||
@@ -168,7 +243,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.60.0](https://github.com/open-policy-agent/opa/releases/tag/v0.60.0)
|
||||
Regorus has been verified to be compliant with [OPA v0.61.0](https://github.com/open-policy-agent/opa/releases/tag/v0.61.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.
|
||||
|
||||
@@ -178,7 +253,8 @@ The test driver can be invoked by running:
|
||||
$ cargo test -r --test opa
|
||||
```
|
||||
|
||||
Currently, Regorus passes all the non-builtin specific tests. See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing).
|
||||
Currently, Regorus passes all the non-builtin specific tests.
|
||||
See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing).
|
||||
|
||||
The following test suites don't pass fully due to mising builtins:
|
||||
- `cryptoparsersaprivatekeys`
|
||||
@@ -191,7 +267,6 @@ The following test suites don't pass fully due to mising builtins:
|
||||
- `graphql`
|
||||
- `invalidkeyerror`
|
||||
- `jsonpatch`
|
||||
- `jwtbuiltins`
|
||||
- `jwtdecodeverify`
|
||||
- `jwtencodesign`
|
||||
- `jwtencodesignraw`
|
||||
@@ -212,14 +287,15 @@ The following test suites don't pass fully due to mising builtins:
|
||||
- `regometadatarule`
|
||||
- `regoparsemodule`
|
||||
- `rendertemplate`
|
||||
- `time`
|
||||
|
||||
They are captured in the following [github issues](https://github.com/microsoft/regorus/issues?q=is%3Aopen+is%3Aissue+label%3Alib).
|
||||
|
||||
|
||||
### Grammar
|
||||
|
||||
The grammar used by Regorus to parse Rego policies is described in [grammar.md](https://github.com/microsoft/regorus/blob/main/docs/grammar.md) in both [W3C EBNF](https://www.w3.org/Notation.html) and [RailRoad Diagram](https://en.wikipedia.org/wiki/Syntax_diagram) formats.
|
||||
The grammar used by Regorus to parse Rego policies is described in [grammar.md](https://github.com/microsoft/regorus/blob/main/docs/grammar.md)
|
||||
in both [W3C EBNF](https://www.w3.org/Notation.html) and [RailRoad Diagram](https://en.wikipedia.org/wiki/Syntax_diagram) formats.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
27
bindings/c/CMakeLists.txt
Normal file
27
bindings/c/CMakeLists.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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 "regorusc" crate.
|
||||
CRATES "regorus-ffi")
|
||||
|
||||
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)
|
||||
55
bindings/c/main.c
Normal file
55
bindings/c/main.c
Normal file
@@ -0,0 +1,55 @@
|
||||
#include <stdio.h>
|
||||
#include "regorus.h"
|
||||
|
||||
int main() {
|
||||
// Create engine.
|
||||
RegorusEngine* engine = regorus_engine_new();
|
||||
RegorusResult r;
|
||||
|
||||
// Load policies.
|
||||
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
|
||||
if (r.status != RegorusStatusOk)
|
||||
goto error;
|
||||
regorus_result_drop(r);
|
||||
|
||||
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/api.rego");
|
||||
if (r.status != RegorusStatusOk)
|
||||
goto error;
|
||||
regorus_result_drop(r);
|
||||
|
||||
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/policy.rego");
|
||||
if (r.status != RegorusStatusOk)
|
||||
goto error;
|
||||
regorus_result_drop(r);
|
||||
|
||||
// Add data
|
||||
r = regorus_engine_add_data_from_json_file(engine, "../../../tests/aci/data.json");
|
||||
if (r.status != RegorusStatusOk)
|
||||
goto error;
|
||||
regorus_result_drop(r);
|
||||
|
||||
// Set input
|
||||
r = regorus_engine_set_input_from_json_file(engine, "../../../tests/aci/input.json");
|
||||
if (r.status != RegorusStatusOk)
|
||||
goto error;
|
||||
regorus_result_drop(r);
|
||||
|
||||
// Eval query
|
||||
r = regorus_engine_eval_query(engine, "data.framework.mount_overlay=x");
|
||||
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;
|
||||
}
|
||||
28
bindings/cpp/CMakeLists.txt
Normal file
28
bindings/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
# 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")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
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 "regorusc" crate.
|
||||
CRATES "regorus-ffi")
|
||||
|
||||
add_executable(regorus_test main.cpp)
|
||||
# Add path to <regorus-source-folder>/bindings/ffi
|
||||
target_include_directories(regorus_test PRIVATE "../ffi")
|
||||
target_link_libraries(regorus_test regorus-ffi)
|
||||
118
bindings/cpp/main.cpp
Normal file
118
bindings/cpp/main.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include <iostream>
|
||||
#include "regorus.hpp"
|
||||
|
||||
void example()
|
||||
{
|
||||
// Create engine
|
||||
regorus::Engine engine;
|
||||
|
||||
// Add policies.
|
||||
engine.add_policy("objects.rego",R"(package objects
|
||||
|
||||
rect := {`width`: 2, "height": 4}
|
||||
cube := {"width": 3, `height`: 4, "depth": 5}
|
||||
a := 42
|
||||
b := false
|
||||
c := null
|
||||
d := {"a": a, "x": [b, c]}
|
||||
index := 1
|
||||
shapes := [rect, cube]
|
||||
names := ["prod", `smoke1`, "dev"]
|
||||
sites := [{"name": "prod"}, {"name": names[index]}, {"name": "dev"}]
|
||||
e := {
|
||||
a: "foo",
|
||||
"three": c,
|
||||
names[2]: b,
|
||||
"four": d,
|
||||
}
|
||||
f := e["dev"])");
|
||||
|
||||
// Add data.
|
||||
engine.add_data_json(R"({
|
||||
"one": {
|
||||
"bar": "Foo",
|
||||
"baz": 5,
|
||||
"be": true,
|
||||
"bop": 23.4
|
||||
},
|
||||
"two": {
|
||||
"bar": "Bar",
|
||||
"baz": 12.3,
|
||||
"be": false,
|
||||
"bop": 42
|
||||
}
|
||||
})");
|
||||
|
||||
engine.add_data_json(R"({
|
||||
"three": {
|
||||
"bar": "Baz",
|
||||
"baz": 15,
|
||||
"be": true,
|
||||
"bop": 4.23
|
||||
}
|
||||
})");
|
||||
|
||||
// Set input.
|
||||
engine.set_input_json(R"({
|
||||
"a": 10,
|
||||
"b": "20",
|
||||
"c": 30.0,
|
||||
"d": true
|
||||
})");
|
||||
|
||||
// Eval query.
|
||||
auto result = engine.eval_query("[data.one, input.b, data.objects.sites[1]] = x");
|
||||
if (result) {
|
||||
std::cout<<result.output()<<std::endl;
|
||||
} else {
|
||||
std::cerr<<result.error()<<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
// Create engine.
|
||||
regorus::Engine engine;
|
||||
|
||||
|
||||
// Load policies.
|
||||
const char* policies[] = {
|
||||
"../../../tests/aci/framework.rego",
|
||||
"../../../tests/aci/policy.rego",
|
||||
"../../../tests/aci/api.rego",
|
||||
};
|
||||
|
||||
// Add policies and data.
|
||||
for (auto policy : policies) {
|
||||
auto result = engine.add_policy_from_file(policy);
|
||||
if (!result) {
|
||||
std::cerr<<result.error()<<std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
{
|
||||
auto result = engine.add_data_from_json_file("../../../tests/aci/data.json");
|
||||
if (!result) {
|
||||
std::cerr<<result.error()<<std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Set input and eval query.
|
||||
{
|
||||
auto result = engine.set_input_from_json_file("../../../tests/aci/input.json");
|
||||
if (!result) {
|
||||
std::cerr<<result.error()<<std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
auto result = engine.eval_query("data.framework.mount_overlay = x");
|
||||
if (!result) {
|
||||
std::cerr<<result.error()<<std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout<<result.output()<<std::endl;
|
||||
|
||||
example();
|
||||
}
|
||||
101
bindings/cpp/regorus.hpp
Normal file
101
bindings/cpp/regorus.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#ifndef REGORUS_WRAPPER_HPP
|
||||
#define REGORUS_WRAPPER_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
#include "regorus.ffi.hpp"
|
||||
|
||||
namespace regorus {
|
||||
|
||||
class Result {
|
||||
public:
|
||||
|
||||
operator bool() const { return result.status == RegorusStatus::RegorusStatusOk; }
|
||||
bool operator !() const { return result.status != RegorusStatus::RegorusStatusOk; }
|
||||
|
||||
const char* output() const {
|
||||
if (*this && result.output) {
|
||||
return result.output;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const char* error() const {
|
||||
if (!*this && result.error_message) {
|
||||
return result.error_message;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
~Result() {
|
||||
regorus_result_drop(result);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Engine;
|
||||
RegorusResult result;
|
||||
|
||||
Result(RegorusResult r) : result(r) {}
|
||||
private:
|
||||
Result(const Result&) = delete;
|
||||
Result(Result&&) = delete;
|
||||
Result& operator=(const Result&) = delete;
|
||||
|
||||
};
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
Engine() : Engine(regorus_engine_new()) {}
|
||||
|
||||
std::unique_ptr<Engine> clone() const {
|
||||
return std::unique_ptr<Engine>(new Engine(regorus_engine_clone(engine)));
|
||||
}
|
||||
|
||||
|
||||
Result add_policy(const char* path, const char* policy) {
|
||||
return Result(regorus_engine_add_policy(engine, path, policy));
|
||||
}
|
||||
|
||||
Result add_policy_from_file(const char* path) {
|
||||
return Result(regorus_engine_add_policy_from_file(engine, path));
|
||||
}
|
||||
|
||||
Result add_data_json(const char* data) {
|
||||
return Result(regorus_engine_add_data_json(engine, data));
|
||||
}
|
||||
|
||||
Result add_data_from_json_file(const char* path) {
|
||||
return Result(regorus_engine_add_data_from_json_file(engine, path));
|
||||
}
|
||||
|
||||
Result set_input_json(const char* input) {
|
||||
return Result(regorus_engine_set_input_json(engine, input));
|
||||
}
|
||||
|
||||
Result set_input_from_json_file(const char* path) {
|
||||
return Result(regorus_engine_set_input_from_json_file(engine, path));
|
||||
}
|
||||
|
||||
Result eval_query(const char* query) {
|
||||
return Result(regorus_engine_eval_query(engine, query));
|
||||
}
|
||||
|
||||
~Engine() {
|
||||
regorus_engine_drop(engine);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
RegorusEngine* engine;
|
||||
private:
|
||||
Engine(RegorusEngine* e) : engine(e) {}
|
||||
Engine(const Engine&) = delete;
|
||||
Engine(Engine&&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // REGORUS_WRAPPER_HPP
|
||||
51
bindings/csharp/Program.cs
Normal file
51
bindings/csharp/Program.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
long nanosecPerTick = (1000L*1000L*1000L) / Stopwatch.Frequency;
|
||||
var w = new Stopwatch();
|
||||
|
||||
|
||||
// Force load of modules.
|
||||
{
|
||||
var _e = new Regorus.Engine();
|
||||
var _j = System.Text.Json.JsonDocument.Parse("{}");
|
||||
}
|
||||
|
||||
w.Restart();
|
||||
|
||||
var engine = new Regorus.Engine();
|
||||
|
||||
w.Stop();
|
||||
var newEngineTicks = w.ElapsedTicks;
|
||||
|
||||
|
||||
w.Restart();
|
||||
|
||||
// Load policies and data.
|
||||
engine.AddPolicyFromFile("../../tests/aci/framework.rego");
|
||||
engine.AddPolicyFromFile("../../tests/aci/api.rego");
|
||||
engine.AddPolicyFromFile("../../tests/aci/policy.rego");
|
||||
engine.AddDataFromJsonFile("../../tests/aci/data.json");
|
||||
|
||||
|
||||
w.Stop();
|
||||
var loadPoliciesTicks = w.ElapsedTicks;
|
||||
|
||||
|
||||
w.Restart();
|
||||
|
||||
// Set input and eval query.
|
||||
engine.SetInputFromJsonFile("../../tests/aci/input.json");
|
||||
var results = engine.EvalQuery("data.framework.mount_overlay = x");
|
||||
var resultsDoc = System.Text.Json.JsonDocument.Parse(results);
|
||||
|
||||
w.Stop();
|
||||
var evalTicks = w.ElapsedTicks;
|
||||
|
||||
Console.WriteLine("{0}", results);
|
||||
|
||||
|
||||
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));
|
||||
|
||||
|
||||
169
bindings/csharp/Regorus.cs
Normal file
169
bindings/csharp/Regorus.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Regorus
|
||||
{
|
||||
public class Exception : System.Exception
|
||||
{
|
||||
public Exception(string? message) : base(message) {}
|
||||
}
|
||||
|
||||
public class Engine : ICloneable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
public Engine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var clone = (Engine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
public void AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
var regoBytes = Encoding.UTF8.GetBytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed(byte* regoPtr = regoBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (resultJson is not null) {
|
||||
return resultJson;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
~Engine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
RegorusFFI.API.regorus_engine_drop(E);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CheckAndDropResult(RegorusFFI.RegorusResult result)
|
||||
{
|
||||
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
|
||||
unsafe {
|
||||
var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message);
|
||||
var ex = new Exception(message);
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
24
bindings/csharp/regorus-test.csproj
Normal file
24
bindings/csharp/regorus-test.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Target Name="BuildRegorusFFI">
|
||||
<Exec Command="cargo build -r --manifest-path ../ffi/Cargo.toml" />
|
||||
<Copy SourceFiles="../ffi/RegorusFFI.g.cs" DestinationFolder="." />
|
||||
<ItemGroup>
|
||||
<RegorusDylib Include="..\..\target\release\*regorus_ffi*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
|
||||
</Target>
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>regorus_test</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
13
bindings/ffi/CHANGELOG.md
Normal file
13
bindings/ffi/CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.0](https://github.com/microsoft/regorus/releases/tag/regorus-ffi-v0.1.0) - 2024-02-08
|
||||
|
||||
### Other
|
||||
- C++ binding ([#129](https://github.com/microsoft/regorus/pull/129))
|
||||
- Bindings for C, C#, Golang ([#124](https://github.com/microsoft/regorus/pull/124))
|
||||
17
bindings/ffi/Cargo.toml
Normal file
17
bindings/ffi/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "regorus-ffi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.79"
|
||||
regorus = { path = "../.." }
|
||||
serde_json = "1.0.113"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.26.0"
|
||||
csbindgen = "1.9.0"
|
||||
88
bindings/ffi/RegorusFFI.g.cs
Normal file
88
bindings/ffi/RegorusFFI.g.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
// <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,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
32
bindings/ffi/build.rs
Normal file
32
bindings/ffi/build.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
extern crate cbindgen;
|
||||
extern crate csbindgen;
|
||||
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
|
||||
cbindgen::Builder::new()
|
||||
.with_crate(&crate_dir)
|
||||
.with_language(cbindgen::Language::C)
|
||||
.with_include_guard("REGORUS_H")
|
||||
.generate()
|
||||
.expect("Unable to generate bindings")
|
||||
.write_to_file("regorus.h");
|
||||
|
||||
cbindgen::Builder::new()
|
||||
.with_crate(crate_dir)
|
||||
.with_language(cbindgen::Language::Cxx)
|
||||
.with_include_guard("REGORUS_FFI_HPP")
|
||||
.generate()
|
||||
.expect("Unable to generate bindings")
|
||||
.write_to_file("regorus.ffi.hpp");
|
||||
|
||||
csbindgen::Builder::default()
|
||||
.input_extern_file("src/lib.rs")
|
||||
.csharp_dll_name("regorusc")
|
||||
.csharp_class_name("API")
|
||||
.csharp_namespace("RegorusFFI")
|
||||
.generate_csharp_file("./RegorusFFI.g.cs")
|
||||
.unwrap();
|
||||
}
|
||||
158
bindings/ffi/cbindgen.toml
Normal file
158
bindings/ffi/cbindgen.toml
Normal file
@@ -0,0 +1,158 @@
|
||||
# This is a template cbindgen.toml file with all of the default values.
|
||||
# Some values are commented out because their absence is the real default.
|
||||
#
|
||||
# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml
|
||||
# for detailed documentation of every option here.
|
||||
|
||||
|
||||
|
||||
language = "C++"
|
||||
|
||||
|
||||
|
||||
############## Options for Wrapping the Contents of the Header #################
|
||||
|
||||
# header = "/* Text to put at the beginning of the generated file. Probably a license. */"
|
||||
# trailer = "/* Text to put at the end of the generated file */"
|
||||
# include_guard = "my_bindings_h"
|
||||
# pragma_once = true
|
||||
# autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
|
||||
include_version = false
|
||||
# namespace = "my_namespace"
|
||||
namespaces = []
|
||||
using_namespaces = []
|
||||
sys_includes = []
|
||||
includes = []
|
||||
no_includes = false
|
||||
after_includes = ""
|
||||
|
||||
|
||||
|
||||
|
||||
############################ Code Style Options ################################
|
||||
|
||||
braces = "SameLine"
|
||||
line_length = 100
|
||||
tab_width = 2
|
||||
documentation = true
|
||||
documentation_style = "auto"
|
||||
documentation_length = "full"
|
||||
line_endings = "LF" # also "CR", "CRLF", "Native"
|
||||
|
||||
|
||||
|
||||
|
||||
############################# Codegen Options ##################################
|
||||
|
||||
style = "both"
|
||||
sort_by = "Name" # default for `fn.sort_by` and `const.sort_by`
|
||||
usize_is_size_t = true
|
||||
|
||||
|
||||
|
||||
[defines]
|
||||
# "target_os = freebsd" = "DEFINE_FREEBSD"
|
||||
# "feature = serde" = "DEFINE_SERDE"
|
||||
|
||||
|
||||
|
||||
[export]
|
||||
include = []
|
||||
exclude = []
|
||||
# prefix = "CAPI_"
|
||||
item_types = []
|
||||
renaming_overrides_prefixing = false
|
||||
|
||||
|
||||
|
||||
[export.rename]
|
||||
|
||||
|
||||
|
||||
[export.body]
|
||||
|
||||
|
||||
[export.mangle]
|
||||
|
||||
|
||||
[fn]
|
||||
rename_args = "None"
|
||||
# must_use = "MUST_USE_FUNC"
|
||||
# deprecated = "DEPRECATED_FUNC"
|
||||
# deprecated_with_note = "DEPRECATED_FUNC_WITH_NOTE"
|
||||
# no_return = "NO_RETURN"
|
||||
# prefix = "START_FUNC"
|
||||
# postfix = "END_FUNC"
|
||||
args = "auto"
|
||||
sort_by = "Name"
|
||||
|
||||
|
||||
|
||||
|
||||
[struct]
|
||||
rename_fields = "None"
|
||||
# must_use = "MUST_USE_STRUCT"
|
||||
# deprecated = "DEPRECATED_STRUCT"
|
||||
# deprecated_with_note = "DEPRECATED_STRUCT_WITH_NOTE"
|
||||
derive_constructor = false
|
||||
derive_eq = false
|
||||
derive_neq = false
|
||||
derive_lt = false
|
||||
derive_lte = false
|
||||
derive_gt = false
|
||||
derive_gte = false
|
||||
|
||||
|
||||
|
||||
|
||||
[enum]
|
||||
rename_variants = "None"
|
||||
# must_use = "MUST_USE_ENUM"
|
||||
# deprecated = "DEPRECATED_ENUM"
|
||||
# deprecated_with_note = "DEPRECATED_ENUM_WITH_NOTE"
|
||||
add_sentinel = false
|
||||
prefix_with_name = false
|
||||
derive_helper_methods = false
|
||||
derive_const_casts = false
|
||||
derive_mut_casts = false
|
||||
# cast_assert_name = "ASSERT"
|
||||
derive_tagged_enum_destructor = false
|
||||
derive_tagged_enum_copy_constructor = false
|
||||
enum_class = true
|
||||
private_default_tagged_enum_constructor = false
|
||||
|
||||
|
||||
|
||||
|
||||
[const]
|
||||
allow_static_const = true
|
||||
allow_constexpr = false
|
||||
sort_by = "Name"
|
||||
|
||||
|
||||
|
||||
|
||||
[macro_expansion]
|
||||
bitflags = false
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
############## Options for How Your Rust library Should Be Parsed ##############
|
||||
|
||||
[parse]
|
||||
parse_deps = false
|
||||
# include = []
|
||||
exclude = []
|
||||
clean = false
|
||||
extra_bindings = []
|
||||
|
||||
|
||||
|
||||
[parse.expand]
|
||||
crates = []
|
||||
all_features = false
|
||||
default_features = true
|
||||
features = []
|
||||
95
bindings/ffi/regorus.ffi.hpp
Normal file
95
bindings/ffi/regorus.ffi.hpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#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
|
||||
127
bindings/ffi/regorus.h
Normal file
127
bindings/ffi/regorus.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#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 */
|
||||
248
bindings/ffi/src/lib.rs
Normal file
248
bindings/ffi/src/lib.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
/// Status of a call on `RegorusEngine`.
|
||||
#[repr(C)]
|
||||
pub enum RegorusStatus {
|
||||
/// The operation was successful.
|
||||
RegorusStatusOk,
|
||||
|
||||
/// The operation was unsuccessful.
|
||||
RegorusStatusError,
|
||||
}
|
||||
|
||||
/// Result of a call on `RegorusEngine`.
|
||||
///
|
||||
/// Must be freed using `regorus_result_drop`.
|
||||
#[repr(C)]
|
||||
pub struct RegorusResult {
|
||||
/// Status
|
||||
status: RegorusStatus,
|
||||
|
||||
/// Output produced by the call.
|
||||
/// Owned by Rust.
|
||||
output: *mut c_char,
|
||||
|
||||
/// Errors produced by the call.
|
||||
/// Owned by Rust.
|
||||
error_message: *mut c_char,
|
||||
}
|
||||
|
||||
fn to_c_str(s: String) -> *mut c_char {
|
||||
match CString::new(s) {
|
||||
Ok(cs) => cs.into_raw(),
|
||||
_ => to_c_str("binding error: failed to create c-style string".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_c_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(|s| s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_ref<T>(t: &*mut T) -> Result<&mut T> {
|
||||
unsafe { t.as_mut().ok_or_else(|| anyhow!("null pointer")) }
|
||||
}
|
||||
|
||||
fn to_regorus_result(r: Result<()>) -> RegorusResult {
|
||||
match r {
|
||||
Ok(()) => RegorusResult {
|
||||
status: RegorusStatus::RegorusStatusOk,
|
||||
output: std::ptr::null_mut(),
|
||||
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 {
|
||||
engine: ::regorus::Engine,
|
||||
}
|
||||
|
||||
/// Drop a `RegorusResult`.
|
||||
///
|
||||
/// `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 {
|
||||
let _ = CString::from_raw(r.error_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// Construct a new Engine
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html
|
||||
pub extern "C" fn regorus_engine_new() -> *mut RegorusEngine {
|
||||
let engine = ::regorus::Engine::new();
|
||||
Box::into_raw(Box::new(RegorusEngine { engine }))
|
||||
}
|
||||
|
||||
/// Clone a [`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()))
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_drop(engine: *mut RegorusEngine) {
|
||||
if !engine.is_null() {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(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.
|
||||
|
||||
#[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_ref(&engine)?
|
||||
.engine
|
||||
.add_policy(from_c_str(path)?, from_c_str(rego)?)
|
||||
}())
|
||||
}
|
||||
|
||||
#[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_ref(&engine)?
|
||||
.engine
|
||||
.add_policy_from_file(from_c_str(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.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_add_data_json(
|
||||
engine: *mut RegorusEngine,
|
||||
data: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
to_ref(&engine)?
|
||||
.engine
|
||||
.add_data(regorus::Value::from_json_str(&from_c_str(data)?)?)
|
||||
}())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_add_data_from_json_file(
|
||||
engine: *mut RegorusEngine,
|
||||
path: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
to_ref(&engine)?
|
||||
.engine
|
||||
.add_data(regorus::Value::from_json_file(&from_c_str(path)?)?)
|
||||
}())
|
||||
}
|
||||
|
||||
/// Clear policy data.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
to_ref(&engine)?.engine.clear_data();
|
||||
Ok(())
|
||||
}())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_set_input_json(
|
||||
engine: *mut RegorusEngine,
|
||||
input: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
to_ref(&engine)?
|
||||
.engine
|
||||
.set_input(regorus::Value::from_json_str(&from_c_str(input)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_set_input_from_json_file(
|
||||
engine: *mut RegorusEngine,
|
||||
path: *const c_char,
|
||||
) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
to_ref(&engine)?
|
||||
.engine
|
||||
.set_input(regorus::Value::from_json_file(&from_c_str(path)?)?);
|
||||
Ok(())
|
||||
}())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_eval_query(
|
||||
engine: *mut RegorusEngine,
|
||||
query: *const c_char,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
let results = to_ref(&engine)?
|
||||
.engine
|
||||
.eval_query(from_c_str(query)?, false)?;
|
||||
Ok(serde_json::to_string_pretty(&results)?)
|
||||
}();
|
||||
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)),
|
||||
}
|
||||
}
|
||||
3
bindings/go/go.mod
Normal file
3
bindings/go/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module regorus-test
|
||||
|
||||
go 1.21.5
|
||||
58
bindings/go/main.go
Normal file
58
bindings/go/main.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regorus-test/pkg/regorus"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var output string
|
||||
var err error
|
||||
|
||||
t := time.Now();
|
||||
|
||||
// Create new engine
|
||||
engine := regorus.NewEngine()
|
||||
defer engine.Close()
|
||||
elapsed1 := time.Since(t)
|
||||
|
||||
t = time.Now()
|
||||
// Add policies and data.
|
||||
policies := []string{
|
||||
"../../tests/aci/framework.rego",
|
||||
"../../tests/aci/api.rego",
|
||||
"../../tests/aci/policy.rego",
|
||||
}
|
||||
for _, policy := range policies {
|
||||
if err := engine.AddPolicyFromFile(policy); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if err = engine.AddDataFromJsonFile("../../tests/aci/data.json"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
elapsed2 := time.Since(t)
|
||||
|
||||
t = time.Now()
|
||||
// Set input and eval query.
|
||||
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 {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
elapsed3 := time.Since(t)
|
||||
|
||||
fmt.Println("{%s}", 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)
|
||||
}
|
||||
117
bindings/go/pkg/regorus/mod.go
Normal file
117
bindings/go/pkg/regorus/mod.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package regorus
|
||||
|
||||
// #cgo LDFLAGS: -L ../../../../target/release -lregorus_ffi
|
||||
// #include "../../../ffi/regorus.h"
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
e *C.RegorusEngine
|
||||
}
|
||||
|
||||
func NewEngine() *Engine {
|
||||
e := new(Engine)
|
||||
e.e = C.regorus_engine_new()
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Engine) Close() {
|
||||
C.regorus_engine_drop(e.e)
|
||||
}
|
||||
|
||||
func (e *Engine) Clone() *Engine {
|
||||
c := new(Engine)
|
||||
c.e = C.regorus_engine_clone(e.e)
|
||||
return c
|
||||
}
|
||||
|
||||
func (e *Engine) AddPolicy(path string, rego string) error {
|
||||
path_c := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(path_c))
|
||||
|
||||
rego_c := C.CString(rego)
|
||||
defer C.free(unsafe.Pointer(rego_c))
|
||||
|
||||
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 nil
|
||||
}
|
||||
|
||||
func (e *Engine) AddPolicyFromFile(path 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 nil
|
||||
}
|
||||
|
||||
func (e *Engine) AddDataJson(data string) error {
|
||||
data_c := C.CString(data)
|
||||
defer C.free(unsafe.Pointer(data_c))
|
||||
|
||||
result := C.regorus_engine_add_data_json(e.e, data_c)
|
||||
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) AddDataFromJsonFile(path string) error {
|
||||
path_c := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(path_c))
|
||||
|
||||
result := C.regorus_engine_add_data_from_json_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 nil
|
||||
}
|
||||
|
||||
func (e *Engine) SetInputJson(input string) error {
|
||||
input_c := C.CString(input)
|
||||
defer C.free(unsafe.Pointer(input_c))
|
||||
|
||||
result := C.regorus_engine_set_input_json(e.e, input_c)
|
||||
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) SetInputFromJsonFile(path string) error {
|
||||
path_c := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(path_c))
|
||||
|
||||
result := C.regorus_engine_set_input_from_json_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 nil
|
||||
}
|
||||
|
||||
func (e *Engine) EvalQuery(query string) (string, error) {
|
||||
query_c := C.CString(query)
|
||||
defer C.free(unsafe.Pointer(query_c))
|
||||
|
||||
result := C.regorus_engine_eval_query(e.e, query_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
|
||||
}
|
||||
1
bindings/java/.gitignore
vendored
Normal file
1
bindings/java/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target/
|
||||
13
bindings/java/CHANGELOG.md
Normal file
13
bindings/java/CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.0](https://github.com/microsoft/regorus/releases/tag/regorus-java-v0.1.0) - 2024-02-23
|
||||
|
||||
### Other
|
||||
- Java publishing ([#151](https://github.com/microsoft/regorus/pull/151))
|
||||
- Java bindings ([#147](https://github.com/microsoft/regorus/pull/147))
|
||||
17
bindings/java/Cargo.toml
Normal file
17
bindings/java/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "regorus-java"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/java"
|
||||
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.79"
|
||||
serde_json = "1.0.112"
|
||||
jni = "0.21.1"
|
||||
regorus = { path = "../.." }
|
||||
97
bindings/java/README.md
Normal file
97
bindings/java/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Regorus Java
|
||||
|
||||
**Regorus** is
|
||||
|
||||
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
|
||||
interpreter written in Rust.
|
||||
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
|
||||
|
||||
See main [Regorus page](https://github.com/microsoft/regorus) for more details about the project.
|
||||
|
||||
## 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:
|
||||
|
||||
```java
|
||||
import com.microsoft.regorus.Engine;
|
||||
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
try (Engine engine = new Engine()) {
|
||||
engine.addPolicy(
|
||||
"hello.rego",
|
||||
"package test\nmessage = concat(\", \", [input.message, data.message])"
|
||||
);
|
||||
engine.addDataJson("{\"message\":\"World!\"}");
|
||||
engine.setInputJson("{\"message\":\"Hello\"}");
|
||||
String resJson = engine.evalQuery("data.test.message");
|
||||
|
||||
System.out.println(resJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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}}]}]}
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
93
bindings/java/com_microsoft_regorus_Engine.h
Normal file
93
bindings/java/com_microsoft_regorus_Engine.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||
#include <jni.h>
|
||||
/* Header for class com_microsoft_regorus_Engine */
|
||||
|
||||
#ifndef _Included_com_microsoft_regorus_Engine
|
||||
#define _Included_com_microsoft_regorus_Engine
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeNewEngine
|
||||
* Signature: ()J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeNewEngine
|
||||
(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeAddPolicy
|
||||
* Signature: (JLjava/lang/String;Ljava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicy
|
||||
(JNIEnv *, jclass, jlong, jstring, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeAddPolicyFromFile
|
||||
* Signature: (JLjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile
|
||||
(JNIEnv *, jclass, jlong, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeClearData
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeClearData
|
||||
(JNIEnv *, jclass, jlong);
|
||||
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeAddDataJson
|
||||
* Signature: (JLjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeAddDataJson
|
||||
(JNIEnv *, jclass, jlong, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeAddDataJsonFromFile
|
||||
* Signature: (JLjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeAddDataJsonFromFile
|
||||
(JNIEnv *, jclass, jlong, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeSetInputJson
|
||||
* Signature: (JLjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeSetInputJson
|
||||
(JNIEnv *, jclass, jlong, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeSetInputJsonFromFile
|
||||
* Signature: (JLjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromFile
|
||||
(JNIEnv *, jclass, jlong, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeEvalQuery
|
||||
* Signature: (JLjava/lang/String;)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeEvalQuery
|
||||
(JNIEnv *, jclass, jlong, jstring);
|
||||
|
||||
/*
|
||||
* Class: com_microsoft_regorus_Engine
|
||||
* Method: nativeDestroyEngine
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeDestroyEngine
|
||||
(JNIEnv *, jclass, jlong);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
138
bindings/java/pom.xml
Normal file
138
bindings/java/pom.xml
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT License.
|
||||
-->
|
||||
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.microsoft.regorus</groupId>
|
||||
<artifactId>regorus-java</artifactId>
|
||||
<version>0.0.1</version>
|
||||
|
||||
<name>Regorus Java</name>
|
||||
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>
|
||||
<url>https://github.com/microsoft/regorus/bindings/java</url>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>https://opensource.org/blog/license/mit</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>ossrh</id>
|
||||
<name>Central Repository OSSRH</name>
|
||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<!--
|
||||
Include native/ folder in built JAR.
|
||||
During CI build we build native libraries for various platforms
|
||||
and put them into native/ folder.
|
||||
See `.github/publish-java.yml`.
|
||||
-->
|
||||
<directory>${project.basedir}/native</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- Build a debug release for tests -->
|
||||
<id>build-native-lib-for-test</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>python3</executable>
|
||||
<arguments>
|
||||
<argument>${project.basedir}/tools/testbuild.py</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<configuration>
|
||||
<!-- Add debug build to Java path, so it's discoverable by JVM. This is only for tests. -->
|
||||
<argLine>-Djava.library.path=${project.basedir}/target/debug:${java.library.path}</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Build javadoc JAR, this is required by Maven Central. -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.6.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadoc</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Build sources JAR, this is required by Maven Central. -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
</project>
|
||||
163
bindings/java/src/lib.rs
Normal file
163
bindings/java/src/lib.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use anyhow::Result;
|
||||
use jni::objects::{JClass, JObject, JString};
|
||||
use jni::sys::{jlong, jstring};
|
||||
use jni::JNIEnv;
|
||||
|
||||
use regorus::{Engine, Value};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
|
||||
_env: JNIEnv,
|
||||
_class: JClass,
|
||||
) -> jlong {
|
||||
let engine = Engine::new();
|
||||
Box::into_raw(Box::new(engine)) as jlong
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
path: JString,
|
||||
rego: JString,
|
||||
) {
|
||||
let _ = 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(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
path: JString,
|
||||
) {
|
||||
let _ = 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(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearData(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
) {
|
||||
let _ = throw_err(env, |_env| {
|
||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||
engine.clear_data();
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJson(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
data: JString,
|
||||
) {
|
||||
let _ = throw_err(env, |env| {
|
||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||
let data: String = env.get_string(&data)?.into();
|
||||
engine.add_data_json(&data)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJsonFromFile(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
path: JString,
|
||||
) {
|
||||
let _ = throw_err(env, |env| {
|
||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||
let path: String = env.get_string(&path)?.into();
|
||||
engine.add_data(Value::from_json_file(path)?)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJson(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
input: JString,
|
||||
) {
|
||||
let _ = throw_err(env, |env| {
|
||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||
let input: String = env.get_string(&input)?.into();
|
||||
engine.set_input_json(&input)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromFile(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
path: JString,
|
||||
) {
|
||||
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)?);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalQuery(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
query: JString,
|
||||
) -> jstring {
|
||||
let res = throw_err(env, |env| {
|
||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||
let query: String = env.get_string(&query)?.into();
|
||||
let results = engine.eval_query(query, false)?;
|
||||
let output = env.new_string(serde_json::to_string(&results)?)?;
|
||||
Ok(output.into_raw())
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(_) => JObject::null().into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe 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);
|
||||
}
|
||||
|
||||
fn throw_err<T>(mut env: JNIEnv, mut f: impl FnMut(&mut JNIEnv) -> Result<T>) -> Result<T> {
|
||||
match f(&mut env) {
|
||||
Ok(val) => Ok(val),
|
||||
Err(err) => {
|
||||
env.throw(err.to_string())?;
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
199
bindings/java/src/main/java/com/microsoft/regorus/Engine.java
Normal file
199
bindings/java/src/main/java/com/microsoft/regorus/Engine.java
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
**/
|
||||
|
||||
package com.microsoft.regorus;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Regorus Engine.
|
||||
*/
|
||||
public class Engine implements AutoCloseable {
|
||||
// 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 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 void nativeDestroyEngine(long enginePtr);
|
||||
|
||||
// Pointer to Engine allocated on Rust's heap, all native methods works on
|
||||
// engine expects this pointer. It is free'd in `close` method.
|
||||
private final long enginePtr;
|
||||
|
||||
/**
|
||||
* Creates a new Regorus Engine.
|
||||
*/
|
||||
public Engine() {
|
||||
enginePtr = nativeNewEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an inline Rego policy.
|
||||
*
|
||||
* @param filename Filename of this Rego policy.
|
||||
* @param rego Rego policy.
|
||||
*/
|
||||
public void addPolicy(String filename, String rego) {
|
||||
nativeAddPolicy(enginePtr, filename, rego);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Rego policy from given path.
|
||||
*
|
||||
* @param path Path of the Rego policy.
|
||||
*/
|
||||
public void addPolicyFromFile(String path) {
|
||||
nativeAddPolicyFromFile(enginePtr, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the data document.
|
||||
*/
|
||||
public void clearData() {
|
||||
nativeClearData(enginePtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds inline data document from given JSON.
|
||||
* The specified data document is merged into existing data document.
|
||||
* It will throw an error if new data conflicts with the existing document.
|
||||
*
|
||||
* Example:
|
||||
* addDataJson("[]") - Throws as it's not an object.
|
||||
* addDataJson('{"a": 1}') - Fine
|
||||
* addDataJson('{"b": 2}') - Fine, now {"a": 1, "b": 2}
|
||||
* addDataJson('{"b": 3}') - Throws as `b` conflicts.
|
||||
*
|
||||
* @see clearData
|
||||
*
|
||||
* @throws RuntimeException If data conflicts with the existing document
|
||||
* or data is not an object.
|
||||
*
|
||||
* @param data Inline data document.
|
||||
*/
|
||||
public void addDataJson(String data) throws RuntimeException {
|
||||
nativeAddDataJson(enginePtr, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data document from given JSON file.
|
||||
* The specified data document is merged into existing data document.
|
||||
* It will throw an error if new data conflicts with the existing document.
|
||||
*
|
||||
* @see addDataJson
|
||||
* @see clearData
|
||||
*
|
||||
* @throws RuntimeException If data conflicts with the existing document
|
||||
* or data is not an object.
|
||||
*
|
||||
* @param path Path to JSON data document.
|
||||
*/
|
||||
public void addDataJsonFromFile(String path) throws RuntimeException {
|
||||
nativeAddDataJsonFromFile(enginePtr, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets inline JSON input.
|
||||
*
|
||||
* @param input inline JSON input.
|
||||
*/
|
||||
public void setInputJson(String input) {
|
||||
nativeSetInputJson(enginePtr, input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets JSON input from given path.
|
||||
*
|
||||
* @param path Path to JSON input.
|
||||
*/
|
||||
public void setInputJsonFromFile(String path) {
|
||||
nativeSetInputJsonFromFile(enginePtr, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates given Rego query and returns a JSON string as a result.
|
||||
*
|
||||
* @param query The Rego query.
|
||||
*
|
||||
* @return Query results as a JSON string.
|
||||
*/
|
||||
public String evalQuery(String query) {
|
||||
return nativeEvalQuery(enginePtr, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
nativeDestroyEngine(enginePtr);
|
||||
}
|
||||
|
||||
// Loading native library from JAR is adapted from:
|
||||
// https://github.com/apache/opendal/blob/93e5f65bbf30df2fed4bdd95bb0685c73c6418c2/bindings/java/src/main/java/org/apache/opendal/NativeLibrary.java
|
||||
// https://github.com/apache/opendal/blob/93e5f65bbf30df2fed4bdd95bb0685c73c6418c2/bindings/java/src/main/java/org/apache/opendal/Environment.java
|
||||
static {
|
||||
// Build a Rust target triple, like: 'aarch64-unknown-linux-gnu'.
|
||||
final StringBuilder targetTripleBuilder = new StringBuilder();
|
||||
|
||||
final String arch = System.getProperty("os.arch").toLowerCase();
|
||||
if (arch.equals("aarch64")) {
|
||||
targetTripleBuilder.append("aarch64");
|
||||
} else {
|
||||
targetTripleBuilder.append("x86_64");
|
||||
}
|
||||
targetTripleBuilder.append("-");
|
||||
|
||||
final String os = System.getProperty("os.name").toLowerCase();
|
||||
if (os.startsWith("windows")) {
|
||||
targetTripleBuilder.append("pc-windows-msvc");
|
||||
} else if (os.startsWith("mac")) {
|
||||
targetTripleBuilder.append("apple-darwin");
|
||||
} else {
|
||||
targetTripleBuilder.append("unknown-linux-gnu");
|
||||
}
|
||||
|
||||
loadNativeLibrary(targetTripleBuilder.toString());
|
||||
}
|
||||
|
||||
private static void loadNativeLibrary(String targetTriple) {
|
||||
try {
|
||||
// try dynamic library - the search path can be configured via "-Djava.library.path"
|
||||
System.loadLibrary("regorus_java");
|
||||
return;
|
||||
} catch (UnsatisfiedLinkError ignore) {
|
||||
// ignore - try from classpath
|
||||
}
|
||||
|
||||
// Native libraries will be bundles into JARs like:
|
||||
// `aarch64-apple-darwin/libregorus_java.dylib`
|
||||
final String libraryName = System.mapLibraryName("regorus_java");
|
||||
final String libraryPath = "/" + targetTriple + "/" + libraryName;
|
||||
|
||||
try (final InputStream is = Engine.class.getResourceAsStream(libraryPath)) {
|
||||
if (is == null) {
|
||||
throw new RuntimeException("Cannot find " + libraryPath + "\nSee https://github.com/microsoft/regorus/tree/main/bindings/java for help.");
|
||||
}
|
||||
final int dot = libraryPath.indexOf('.');
|
||||
final File tmpFile = File.createTempFile(libraryPath.substring(0, dot), libraryPath.substring(dot));
|
||||
tmpFile.deleteOnExit();
|
||||
Files.copy(is, tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
System.load(tmpFile.getAbsolutePath());
|
||||
} catch (IOException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
**/
|
||||
package com.microsoft.regorus;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import junit.framework.TestCase;
|
||||
import junit.framework.Assert;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
public class EngineTest extends TestCase
|
||||
{
|
||||
public void test_engine()
|
||||
{
|
||||
String resJson;
|
||||
try (Engine engine = new Engine()) {
|
||||
engine.addPolicy(
|
||||
"hello.rego",
|
||||
"package test\nmessage = concat(\", \", [input.message, data.message])"
|
||||
);
|
||||
engine.addDataJson("{\"message\":\"World!\"}");
|
||||
engine.setInputJson("{\"message\":\"Hello\"}");
|
||||
resJson = engine.evalQuery("data.test.message");
|
||||
}
|
||||
|
||||
Gson gson = new Gson();
|
||||
Map res = gson.fromJson(resJson, Map.class);
|
||||
ArrayList results = (ArrayList) res.get("result");
|
||||
ArrayList expressions = (ArrayList) ((Map) results.get(0)).get("expressions");
|
||||
Map expression = (Map) expressions.get(0);
|
||||
Assert.assertEquals("Hello, World!", expression.get("value"));
|
||||
}
|
||||
}
|
||||
17
bindings/java/tools/testbuild.py
Normal file
17
bindings/java/tools/testbuild.py
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
#
|
||||
# Builds Regorus Java to use in Java tests. See `pom.xml`.
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
if __name__ == "__main__":
|
||||
basedir = Path(__file__).parent.parent
|
||||
|
||||
output = basedir / "target"
|
||||
Path(output).mkdir(exist_ok=True, parents=True)
|
||||
cmd = ["cargo", "build", "--target-dir", str(output)]
|
||||
print("$ " + subprocess.list2cmdline(cmd))
|
||||
subprocess.run(cmd, cwd=basedir, check=True)
|
||||
1
bindings/python/.python-version
Normal file
1
bindings/python/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
pyo3
|
||||
14
bindings/python/CHANGELOG.md
Normal file
14
bindings/python/CHANGELOG.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.0](https://github.com/microsoft/regorus/releases/tag/regoruspy-v0.1.0) - 2024-02-01
|
||||
|
||||
### Other
|
||||
- Document bindings ([#119](https://github.com/microsoft/regorus/pull/119))
|
||||
- Publish python packages ([#117](https://github.com/microsoft/regorus/pull/117))
|
||||
- Python bindings ([#115](https://github.com/microsoft/regorus/pull/115))
|
||||
20
bindings/python/Cargo.toml
Normal file
20
bindings/python/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "regoruspy"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/python"
|
||||
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.79"
|
||||
ordered-float = "4.2.0"
|
||||
pyo3 = {version = "0.20.2", features = ["anyhow", "extension-module"] }
|
||||
regorus = { path = "../.." }
|
||||
serde_json = "1.0.112"
|
||||
|
||||
67
bindings/python/README.md
Normal file
67
bindings/python/README.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# regorus
|
||||
|
||||
**Regorus** is
|
||||
|
||||
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
|
||||
interpreter written in Rust.
|
||||
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
|
||||
|
||||
Regorus can be used in Python via `regorus` package. (It is not yet available in PyPI, but can be manually built.)
|
||||
|
||||
See [Repository](https://github.com/microsoft/regorus).
|
||||
|
||||
To build this binding, see [building](https://github.com/microsoft/regorus/bindings/python/building.md)
|
||||
|
||||
## Usage
|
||||
```Python
|
||||
import regorus
|
||||
|
||||
# Create engine
|
||||
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')
|
||||
|
||||
# Add policy data
|
||||
data = {
|
||||
"metadata": {
|
||||
"devices": {
|
||||
"/run/layers/p0-layer0": "1b80f120dbd88e4355d6241b519c3e25290215c469516b49dece9cf07175a766",
|
||||
"/run/layers/p0-layer1": "e769d7487cc314d3ee748a4440805317c19262c7acd2fdbdb0d47d2e4613a15c",
|
||||
"/run/layers/p0-layer2": "eb36921e1f82af46dfe248ef8f1b3afb6a5230a64181d960d10237a08cd73c79",
|
||||
"/run/layers/p0-layer3": "41d64cdeb347bf236b4c13b7403b633ff11f1cf94dbc7cf881a44d6da88c5156",
|
||||
"/run/layers/p0-layer4": "4dedae42847c704da891a28c25d32201a1ae440bce2aecccfa8e6f03b97a6a6c",
|
||||
"/run/layers/p0-layer5": "fe84c9d5bfddd07a2624d00333cf13c1a9c941f3a261f13ead44fc6a93bc0e7a"
|
||||
}
|
||||
}
|
||||
}
|
||||
engine.add_data(data)
|
||||
|
||||
# Set input
|
||||
input = {
|
||||
"containerID": "container0",
|
||||
"layerPaths": [
|
||||
"/run/layers/p0-layer0",
|
||||
"/run/layers/p0-layer1",
|
||||
"/run/layers/p0-layer2",
|
||||
"/run/layers/p0-layer3",
|
||||
"/run/layers/p0-layer4",
|
||||
"/run/layers/p0-layer5"
|
||||
],
|
||||
"target": "/run/gcs/c/container0/rootfs"
|
||||
}
|
||||
engine.set_input(input)
|
||||
|
||||
# Eval query
|
||||
results = engine.eval_query('data.framework.mount_overlay=x')
|
||||
|
||||
# Print results
|
||||
print(results['result'][0])
|
||||
|
||||
# Eval query as json
|
||||
results_json = engine.eval_query_as_json('data.framework.mount_overlay=x')
|
||||
print(results_json)
|
||||
```
|
||||
|
||||
22
bindings/python/building.md
Normal file
22
bindings/python/building.md
Normal file
@@ -0,0 +1,22 @@
|
||||
- Install maturin
|
||||
```
|
||||
pipx install maturin
|
||||
```
|
||||
See [Maturin User Guide](https://www.maturin.rs)
|
||||
|
||||
- Build bindings for Python
|
||||
```
|
||||
cd bindings/python
|
||||
maturin build --release --target-dir wheels
|
||||
```
|
||||
|
||||
- Install python wheel
|
||||
```
|
||||
pip3 install ../../target/wheels/regorus*.whl --force-reinstall
|
||||
```
|
||||
|
||||
- Run test script
|
||||
```
|
||||
python3 test.py
|
||||
```
|
||||
|
||||
16
bindings/python/pyproject.toml
Normal file
16
bindings/python/pyproject.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[build-system]
|
||||
requires = ["maturin>=1.4,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "regorus"
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
[tool.maturin]
|
||||
features = ["pyo3/extension-module"]
|
||||
301
bindings/python/src/lib.rs
Normal file
301
bindings/python/src/lib.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
use anyhow::{anyhow, Result};
|
||||
use pyo3::exceptions::PyTypeError;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::*;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use ::regorus::Value;
|
||||
|
||||
/// Regorus engine.
|
||||
#[pyclass(unsendable)]
|
||||
pub struct Engine {
|
||||
engine: ::regorus::Engine,
|
||||
}
|
||||
|
||||
impl Default for Engine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
// 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.into()
|
||||
}
|
||||
// set
|
||||
else if let Ok(pset) = ob.downcast::<PySet>() {
|
||||
let mut set = BTreeSet::new();
|
||||
for v in pset {
|
||||
set.insert(from(v)?);
|
||||
}
|
||||
set.into()
|
||||
}
|
||||
// frozen set
|
||||
else if let Ok(pfset) = ob.downcast::<PyFrozenSet>() {
|
||||
//
|
||||
let mut set = BTreeSet::new();
|
||||
for v in pfset {
|
||||
set.insert(from(v)?);
|
||||
}
|
||||
set.into()
|
||||
}
|
||||
// lists and tuples
|
||||
else if let Ok(plist) = ob.downcast::<PyList>() {
|
||||
let mut array = Vec::new();
|
||||
for v in plist {
|
||||
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.into()
|
||||
}
|
||||
// String
|
||||
else if let Ok(s) = String::extract(ob) {
|
||||
s.into()
|
||||
}
|
||||
// Numeric
|
||||
else if let Ok(v) = i64::extract(ob) {
|
||||
v.into()
|
||||
} else if let Ok(v) = u64::extract(ob) {
|
||||
v.into()
|
||||
} else if let Ok(v) = f64::extract(ob) {
|
||||
v.into()
|
||||
}
|
||||
// Boolean
|
||||
else if let Ok(b) = bool::extract(ob) {
|
||||
b.into()
|
||||
}
|
||||
// None
|
||||
else if ob.downcast::<PyNone>().is_ok() {
|
||||
Value::Null
|
||||
}
|
||||
// Anything that is a sequence
|
||||
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.into()
|
||||
}
|
||||
// Anything that is a map
|
||||
else if let Ok(pmap) = ob.downcast::<PyMapping>() {
|
||||
let mut map = BTreeMap::new();
|
||||
let keys = pmap.keys()?;
|
||||
let values = pmap.values()?;
|
||||
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.into()
|
||||
} else {
|
||||
return Err(PyErr::new::<PyTypeError, _>(
|
||||
"object cannot be converted to RegoValue",
|
||||
));
|
||||
})
|
||||
}
|
||||
|
||||
fn to(mut v: Value, py: Python<'_>) -> Result<PyObject> {
|
||||
Ok(match v {
|
||||
Value::Null => None::<u64>.to_object(py),
|
||||
|
||||
// TODO: Revisit this mapping
|
||||
Value::Undefined => None::<u64>.to_object(py),
|
||||
|
||||
Value::Bool(b) => b.to_object(py),
|
||||
Value::String(s) => s.to_object(py),
|
||||
|
||||
Value::Number(_) => {
|
||||
if let Ok(f) = v.as_f64() {
|
||||
f.to_object(py)
|
||||
} else if let Ok(u) = v.as_u64() {
|
||||
u.to_object(py)
|
||||
} else {
|
||||
v.as_i64()?.to_object(py)
|
||||
}
|
||||
}
|
||||
|
||||
Value::Array(_) => {
|
||||
let list = PyList::empty(py);
|
||||
for v in std::mem::replace(v.as_array_mut()?, Vec::new()) {
|
||||
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()) {
|
||||
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()) {
|
||||
dict.set_item(to(k, py)?, to(v, py)?)?;
|
||||
}
|
||||
dict.into()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Engine {
|
||||
/// Construct a new Engine
|
||||
#[new]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
engine: ::regorus::Engine::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a policy
|
||||
///
|
||||
/// The policy is parsed into AST.
|
||||
///
|
||||
/// * `path`: A filename to be associated with the policy.
|
||||
/// * `rego`: Rego policy.
|
||||
pub fn add_policy(&mut self, path: String, rego: String) -> Result<()> {
|
||||
self.engine.add_policy(path, rego)
|
||||
}
|
||||
|
||||
/// Add a policy from given file.
|
||||
///
|
||||
/// The policy is parsed into AST.
|
||||
///
|
||||
/// * `path`: Path to the policy file.
|
||||
pub fn add_policy_from_file(&mut self, path: String) -> Result<()> {
|
||||
self.engine.add_policy_from_file(path)
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
let data = from(data)?;
|
||||
self.engine.add_data(data)
|
||||
}
|
||||
|
||||
/// Add policy data.
|
||||
///
|
||||
/// * `data`: JSON encoded value to be used as policy data.
|
||||
pub fn add_data_json(&mut self, data: String) -> Result<()> {
|
||||
let data = Value::from_json_str(&data)?;
|
||||
self.engine.add_data(data)
|
||||
}
|
||||
|
||||
/// Add policy data from file.
|
||||
///
|
||||
/// * `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)?;
|
||||
self.engine.add_data(data)
|
||||
}
|
||||
|
||||
/// Clear policy data.
|
||||
pub fn clear_data(&mut self) -> Result<()> {
|
||||
self.engine.clear_data();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set input.
|
||||
///
|
||||
/// * `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<()> {
|
||||
let input = from(input)?;
|
||||
self.engine.set_input(input);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set input.
|
||||
///
|
||||
/// * `input`: JSON encoded value to be used as input to query.
|
||||
pub fn set_input_json(&mut self, input: String) -> Result<()> {
|
||||
let input = Value::from_json_str(&input)?;
|
||||
self.engine.set_input(input);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set input.
|
||||
///
|
||||
/// * `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)?;
|
||||
self.engine.set_input(input);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Evaluate query.
|
||||
///
|
||||
/// * `query`: Rego expression to be evaluate.
|
||||
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);
|
||||
for result in results.result.into_iter() {
|
||||
let rdict = PyDict::new(py);
|
||||
|
||||
let elist = PyList::empty(py);
|
||||
for expr in result.expressions.into_iter() {
|
||||
let edict = PyDict::new(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);
|
||||
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))?;
|
||||
|
||||
edict.set_item("location".to_object(py), ldict)?;
|
||||
elist.append(edict)?;
|
||||
}
|
||||
|
||||
rdict.set_item("expressions".to_object(py), elist)?;
|
||||
rdict.set_item("bindings".to_object(py), to(result.bindings, py)?)?;
|
||||
rlist.append(rdict)?;
|
||||
}
|
||||
let dict = PyDict::new(py);
|
||||
dict.set_item("result".to_object(py), rlist)?;
|
||||
Ok(dict.into())
|
||||
}
|
||||
|
||||
/// Evaluate query. Returns result as JSON.
|
||||
///
|
||||
/// * `query`: Rego expression to be evaluate.
|
||||
pub fn eval_query_as_json(&mut self, query: String) -> Result<String> {
|
||||
let results = self.engine.eval_query(query, false)?;
|
||||
serde_json::to_string_pretty(&results).map_err(|e| anyhow!("{e}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
pub fn regorus(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<crate::Engine>()
|
||||
}
|
||||
52
bindings/python/test.py
Normal file
52
bindings/python/test.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import regorus
|
||||
|
||||
# Create engine
|
||||
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')
|
||||
|
||||
# Add policy data
|
||||
data = {
|
||||
"metadata": {
|
||||
"devices": {
|
||||
"/run/layers/p0-layer0": "1b80f120dbd88e4355d6241b519c3e25290215c469516b49dece9cf07175a766",
|
||||
"/run/layers/p0-layer1": "e769d7487cc314d3ee748a4440805317c19262c7acd2fdbdb0d47d2e4613a15c",
|
||||
"/run/layers/p0-layer2": "eb36921e1f82af46dfe248ef8f1b3afb6a5230a64181d960d10237a08cd73c79",
|
||||
"/run/layers/p0-layer3": "41d64cdeb347bf236b4c13b7403b633ff11f1cf94dbc7cf881a44d6da88c5156",
|
||||
"/run/layers/p0-layer4": "4dedae42847c704da891a28c25d32201a1ae440bce2aecccfa8e6f03b97a6a6c",
|
||||
"/run/layers/p0-layer5": "fe84c9d5bfddd07a2624d00333cf13c1a9c941f3a261f13ead44fc6a93bc0e7a"
|
||||
}
|
||||
}
|
||||
}
|
||||
engine.add_data(data)
|
||||
|
||||
# Set input
|
||||
input = {
|
||||
"containerID": "container0",
|
||||
"layerPaths": [
|
||||
"/run/layers/p0-layer0",
|
||||
"/run/layers/p0-layer1",
|
||||
"/run/layers/p0-layer2",
|
||||
"/run/layers/p0-layer3",
|
||||
"/run/layers/p0-layer4",
|
||||
"/run/layers/p0-layer5"
|
||||
],
|
||||
"target": "/run/gcs/c/container0/rootfs"
|
||||
}
|
||||
engine.set_input(input)
|
||||
|
||||
# Eval query
|
||||
results = engine.eval_query('data.framework.mount_overlay=x')
|
||||
|
||||
# Print results
|
||||
print(results['result'][0])
|
||||
|
||||
# Eval query as json
|
||||
results_json = engine.eval_query_as_json('data.framework.mount_overlay=x')
|
||||
print(results_json)
|
||||
14
bindings/wasm/CHANGELOG.md
Normal file
14
bindings/wasm/CHANGELOG.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.0](https://github.com/microsoft/regorus/releases/tag/regorusjs-v0.1.0) - 2024-02-01
|
||||
|
||||
### Other
|
||||
- Document bindings ([#119](https://github.com/microsoft/regorus/pull/119))
|
||||
- Python bindings ([#115](https://github.com/microsoft/regorus/pull/115))
|
||||
- WASM binding ([#114](https://github.com/microsoft/regorus/pull/114))
|
||||
19
bindings/wasm/Cargo.toml
Normal file
19
bindings/wasm/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "regorusjs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/wasm"
|
||||
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
regorus = { path = "../.." }
|
||||
serde_json = "1.0.111"
|
||||
wasm-bindgen = "0.2.90"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.40"
|
||||
81
bindings/wasm/README.md
Normal file
81
bindings/wasm/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# regorusjs
|
||||
|
||||
**Regorus** is
|
||||
|
||||
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
|
||||
interpreter written in Rust.
|
||||
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
|
||||
|
||||
`regorusjs` is Regorus compiled into WASM.
|
||||
|
||||
See [Repository](https://github.com/microsoft/regorus).
|
||||
|
||||
To build this binding, see [building](https://github.com/microsoft/regorus/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!
|
||||
```
|
||||
34
bindings/wasm/building.md
Normal file
34
bindings/wasm/building.md
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
- Install `wasm-pack`
|
||||
```
|
||||
cargo install wasm-pack
|
||||
```
|
||||
|
||||
- Build `regorusjs` for nodejs.
|
||||
```
|
||||
cd bindings/wasm
|
||||
wasm-pack build --target nodejs --release
|
||||
```
|
||||
|
||||
- Install [nodejs](https://nodejs.org/en/download)
|
||||
|
||||
- Run the test script
|
||||
```
|
||||
$ node test.js
|
||||
\\{
|
||||
\\ "result": [
|
||||
\\ {
|
||||
\\ "expressions": [
|
||||
\\ {
|
||||
\\ "value": "Hello, World!",
|
||||
\\ "text": "data.test.message",
|
||||
\\ "location": {
|
||||
\\ "row": 1,
|
||||
\\ "col": 1
|
||||
\\ }
|
||||
\\ }
|
||||
\\ ]
|
||||
\\ }
|
||||
\\ ]
|
||||
\\}
|
||||
```
|
||||
146
bindings/wasm/src/lib.rs
Normal file
146
bindings/wasm/src/lib.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
/// WASM wrapper for [`regorus::Engine`]
|
||||
pub struct Engine {
|
||||
engine: regorus::Engine,
|
||||
}
|
||||
|
||||
fn error_to_jsvalue<E: std::fmt::Display>(e: E) -> JsValue {
|
||||
JsValue::from_str(&format!("{e}"))
|
||||
}
|
||||
|
||||
impl Default for Engine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Engine {
|
||||
#[wasm_bindgen(constructor)]
|
||||
/// Construct a new Engine
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
engine: regorus::Engine::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn add_policy(&mut self, path: String, rego: String) -> Result<(), JsValue> {
|
||||
self.engine.add_policy(path, rego).map_err(error_to_jsvalue)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn add_data_json(&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)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
self.engine.clear_data();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn set_input_json(&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(())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn eval_query(&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)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
pub fn basic() -> Result<(), JsValue> {
|
||||
let mut engine = crate::Engine::new();
|
||||
|
||||
// Exercise all APIs.
|
||||
engine.add_data_json(
|
||||
r#"
|
||||
{
|
||||
"foo" : "bar"
|
||||
}
|
||||
"#
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
engine.set_input_json(
|
||||
r#"
|
||||
{
|
||||
"message" : "Hello"
|
||||
}
|
||||
"#
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
engine.add_policy(
|
||||
"hello.rego".to_string(),
|
||||
r#"
|
||||
package test
|
||||
message = input.message"#
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
let results = engine.eval_query("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"];
|
||||
|
||||
// Ensure that input and policy were evaluated.
|
||||
assert_eq!(v["test"]["message"], regorus::Value::from("Hello"));
|
||||
|
||||
// Test that data was set.
|
||||
assert_eq!(v["foo"], regorus::Value::from("bar"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
47
bindings/wasm/test.js
Normal file
47
bindings/wasm/test.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
var regorus = require('./pkg/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)
|
||||
|
||||
// Convert results to object
|
||||
results = JSON.parse(results)
|
||||
|
||||
// Process result
|
||||
console.log(results.result[0].expressions[0].value)
|
||||
|
||||
239
docs/builtins.md
Normal file
239
docs/builtins.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Built-in Functions
|
||||
|
||||
|
||||
This page lists all the supported Rego built-in functions and the cargo feature that is needed to enable each builtin.
|
||||
|
||||
Those builtins that are not need for a specific use of the Regorus crate can be excluded from the binary by not specifying
|
||||
the corresponding feature. This is useful in Confidential Computing scenarios where
|
||||
- There needs to be control over what a policy execution can and cannot do.
|
||||
- There needs to be control over exactly what goes into the [Trusted Computing Base](https://en.wikipedia.org/wiki/Trusted_computing_base).
|
||||
|
||||
Currently many builtins are `baked-in`, i.e. there is no way to exclude them from the TCB.
|
||||
In future, each builtin will be associated with a feature (many builtins could be associated with the same feature).
|
||||
|
||||
- [Comparison](https://www.openpolicyagent.org/docs/latest/policy-reference/#comparison)
|
||||
| Builtin | Feature |
|
||||
|--------------------------------------------------------------------------------------------------|---------|
|
||||
| [x == y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-equal) | _ |
|
||||
| [x > y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-gt) | _ |
|
||||
| [x >= y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-gte) | _ |
|
||||
| [x < y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-lt) | _ |
|
||||
| [x <= y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-lte) | _ |
|
||||
| [x != y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-neq) | _ |
|
||||
|
||||
- [Numbers](https://www.openpolicyagent.org/docs/latest/policy-reference/#numbers)
|
||||
| Builtin | Feature |
|
||||
|-----------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| [abs](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-abs) | _ |
|
||||
| [ceil](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-ceil) | _ |
|
||||
| [x / y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-div) | _ |
|
||||
| [floor](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-floor) | _ |
|
||||
| [x - y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-minus) | _ |
|
||||
| [x * y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-mul) | _ |
|
||||
| [numbers.range](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-numbersrange) | _ |
|
||||
| [numbers.range_step](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-numbersrange_step) | _ |
|
||||
| [x + y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-plus) | _ |
|
||||
| [rand.intn](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-randintn) | _ |
|
||||
| [x % y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-rem) | _ |
|
||||
| [round](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-round) | _ |
|
||||
|
||||
|
||||
- [Aggregates](https://www.openpolicyagent.org/docs/latest/policy-reference/#aggregates)
|
||||
| Builtin | Feature |
|
||||
|-----------------------------------------------------------------------------------------------------|---------|
|
||||
| [count](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-count) | _ |
|
||||
| [max](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-max) | _ |
|
||||
| [min](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-min) | _ |
|
||||
| [product](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-product) | _ |
|
||||
| [sort](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-sort) | _ |
|
||||
| [sum](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-sum) | _ |
|
||||
|
||||
- [Arrays](https://www.openpolicyagent.org/docs/latest/policy-reference/#arrays-2)
|
||||
| Builtin | Feature |
|
||||
|-----------------------------------------------------------------------------------------------------------|---------|
|
||||
| [array.concat](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-array-arrayconcat) | _ |
|
||||
| [array.reverse](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-array-arrayreverse) | _ |
|
||||
| [array.slice](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-array-arrayslice) | _ |
|
||||
|
||||
- [Sets](https://www.openpolicyagent.org/docs/latest/policy-reference/#sets-2)
|
||||
| Builtin | Feature |
|
||||
|---------------------------------------------------------------------------------------------------------|---------|
|
||||
| [x & y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-and) | _ |
|
||||
| [intersection](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-intersection) | _ |
|
||||
| [x - y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-minus) | _ |
|
||||
| [x \| y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-or) | _ |
|
||||
| [union](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-union) | _ |
|
||||
|
||||
- [Objects](https://www.openpolicyagent.org/docs/latest/policy-reference/#object)
|
||||
| Builtin | Feature |
|
||||
|----------------------------------------------------------------------------------------------------------------------|--------------|
|
||||
| [json.filter](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-jsonfilter) | _ |
|
||||
| [json.match_schema](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-jsonmatch_schema) | `jsonschema` |
|
||||
| [json.remove](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-jsonremove) | _ |
|
||||
| [json.verify_schema](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-jsonverify_schema) | `jsonschema` |
|
||||
| [object.filter](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectfilter) | _ |
|
||||
| [object.get](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectget) | _ |
|
||||
| [object.keys](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectkeys) | _ |
|
||||
| [object.remove](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectremove) | _ |
|
||||
| [object.subset](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectsubset) | _ |
|
||||
| [object.union](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectunion) | _ |
|
||||
| [object.union_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectunion_n) | _ |
|
||||
|
||||
- [Strings](https://www.openpolicyagent.org/docs/latest/policy-reference/#strings)
|
||||
| Builtin | Feature |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| [concat](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-concat) | _ |
|
||||
| [contains](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-contains) | _ |
|
||||
| [endswith](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-endswith) | _ |
|
||||
| [format_int](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-format_int) | _ |
|
||||
| [indexof](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-indexof) | _ |
|
||||
| [indexof_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-indexof_n) | _ |
|
||||
| [lower](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-lower) | _ |
|
||||
| [replace](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-replace) | _ |
|
||||
| [split](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-split) | _ |
|
||||
| [sprintf](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-sprintf) | _ |
|
||||
| [startswith](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-startswith) | _ |
|
||||
| [strings.any_prefix_match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsany_prefix_match) | _ |
|
||||
| [strings.any_suffix_match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsany_suffix_match) | _ |
|
||||
| [strings.render_template](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsrender_template) | _ |
|
||||
| [strings.replace_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsreplace_n) | _ |
|
||||
| [strings.reverse](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsreverse) | _ |
|
||||
| [substring](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-substring) | _ |
|
||||
| [trim](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim) | _ |
|
||||
| [trim_left](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_left) | _ |
|
||||
| [trim_prefix](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_prefix) | _ |
|
||||
| [trim_right](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_right) | _ |
|
||||
| [trim_space](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_space) | _ |
|
||||
| [trim_suffix](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_suffix) | _ |
|
||||
| [upper](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-upper) | _ |
|
||||
|
||||
- [Regex](https://www.openpolicyagent.org/docs/latest/policy-reference/#regex)
|
||||
| Builtin | Feature |
|
||||
|-------------------------------------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| [regex.find_all_string_submatch_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexfind_all_string_submatch_n) | `regex` |
|
||||
| [regex.find_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexfind_n) | `regex` |
|
||||
| [regex.globs_match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexglobs_match) | `regex` |
|
||||
| [regex.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexis_valid) | `regex` |
|
||||
| [regex.match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexmatch) | `regex` |
|
||||
| [regex.replace](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexreplace) | `regex` |
|
||||
| [regex.split](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexsplit) | `regex` |
|
||||
| [regex.template_match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regextemplate_match) | `regex` |
|
||||
|
||||
- [Glob](https://www.openpolicyagent.org/docs/latest/policy-reference/#regex)
|
||||
| Builtin | Feature |
|
||||
|--------------------------------------------------------------------------------------------------------------|---------|
|
||||
| [glob.match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-glob-globmatch) | `glob` |
|
||||
| [glob.quote_meta](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-glob-globquote_meta) | `glob` |
|
||||
|
||||
- [Bitwise](https://www.openpolicyagent.org/docs/latest/policy-reference/#regex)
|
||||
| Builtin | Feature |
|
||||
|------------------------------------------------------------------------------------------------------|---------|
|
||||
| [bits.and](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsand) | _ |
|
||||
| [bits.lsh](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitslsh) | _ |
|
||||
| [bits.negate](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsnegate) | _ |
|
||||
| [bits.or](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsor) | _ |
|
||||
| [bits.rsh](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsrsh) | _ |
|
||||
| [bits.xor](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsxor) | _ |
|
||||
|
||||
- [Conversions](https://www.openpolicyagent.org/docs/latest/policy-reference/#conversions)
|
||||
| Builtin | Feature |
|
||||
|-------|---------|
|
||||
[to_number](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-conversions-to_number) | _ |
|
||||
|
|
||||
- [Units](https://www.openpolicyagent.org/docs/latest/policy-reference/#units)
|
||||
| Builtin | Feature |
|
||||
|-------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| [units.parse](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-units-unitsparse) | _ |
|
||||
| [units.parse_bytes](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-units-unitsparse_bytes) | _ |
|
||||
|
||||
- [Types](https://www.openpolicyagent.org/docs/latest/policy-reference/#types)
|
||||
| Builtin | Feature |
|
||||
|------------------------------------------------------------------------------------------------------|---------|
|
||||
| [is_array](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_array) | _ |
|
||||
| [is_boolean](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_boolean) | _ |
|
||||
| [is_null](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_null) | _ |
|
||||
| [is_number](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_number) | _ |
|
||||
| [is_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_object) | _ |
|
||||
| [is_set](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_set) | _ |
|
||||
| [is_string](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_string) | _ |
|
||||
| [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` |
|
||||
|
||||
- [Time](https://www.openpolicyagent.org/docs/latest/policy-reference/#time)
|
||||
| Builtin | Feature |
|
||||
|----------------------------------------------------------------------------------------------------------------------------|---------|
|
||||
| ([time.add_date](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeadd_date) | `time` |
|
||||
| [time.add_date](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeadd_date) | `time` |
|
||||
| [time.clock](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeclock) | `time` |
|
||||
| [time.date](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timedate) | `time` |
|
||||
| [time.diff](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timediff) | `time` |
|
||||
| [time.format](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeformat) | `time` |
|
||||
| [time.now_ns](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timenow_ns) | `time` |
|
||||
| [time.parse_duration_ns](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeparse_duration_ns) | `time` |
|
||||
| [time.parse_ns](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeparse_ns) | `time` |
|
||||
| [time.parse_rfc3339_ns](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeparse_rfc3339_ns) | `time` |
|
||||
| [time.weekday](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeweekday) | `time` |
|
||||
|
||||
- [Cryptography](https://www.openpolicyagent.org/docs/latest/policy-reference/#crypto)
|
||||
| Builtin | Feature |
|
||||
|---------------------------------------------------------------------------------------------------------------------|----------|
|
||||
| [crypto.hmac.equal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacequal) | `crypto` |
|
||||
| [crypto.hmac.md5](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacmd5) | `crypto` |
|
||||
| [crypto.hmac.sha1](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacsha1) | `crypto` |
|
||||
| [crypto.hmac.sha256](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacsha256) | `crypto` |
|
||||
| [crypto.hmac.sha512](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacsha512) | `crypto` |
|
||||
| [crypto.md5](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptomd5) | `crypto` |
|
||||
| [crypto.sha1](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptosha1) | `crypto` |
|
||||
| [crypto.sha256](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptosha256) | `crypto` |
|
||||
|
||||
- [Graphs](https://www.openpolicyagent.org/docs/latest/policy-reference/#graph)
|
||||
| Builtin | Feature |
|
||||
|---------------------------------------------------------------------------------------------------------------|---------|
|
||||
| [graph.reachable](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-graph-graphreachable) | `graph` |
|
||||
| [walk](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-graph-walk) | `graph` |
|
||||
|
||||
- [UUID](https://www.openpolicyagent.org/docs/latest/policy-reference/#uuid)
|
||||
| Builtin | Feature |
|
||||
|--------------------------------------------------------------------------------------------------------|---------|
|
||||
| [uuid.parse](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-uuid-uuidparse) | `uuid` |
|
||||
| [uuid.rfc4122](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-uuid-uuidrfc4122) | `uuid` |
|
||||
|
||||
- [Semantic Versions](https://www.openpolicyagent.org/docs/latest/policy-reference/#semver)
|
||||
| Builtin | Feature |
|
||||
|----------------------------------------------------------------------------------------------------------------|----------|
|
||||
| [semver.compare](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-semver-semvercompare) | `semver` |
|
||||
| [semver.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-semver-semveris_valid) | `semver` |
|
||||
|
||||
- [OPA](https://www.openpolicyagent.org/docs/latest/policy-reference/#opa
|
||||
| Builtin | Feature |
|
||||
|-----------------------------------------------------------------------------------------------------|---------|
|
||||
| [opa.runtime](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-opa-oparuntime) | _ |
|
||||
|
||||
- [Debugging](https://www.openpolicyagent.org/docs/latest/policy-reference/#opa)
|
||||
| Builtin | Feature |
|
||||
|---------------------------------------------------------------------------------|---------|
|
||||
| [print(...)](https://www.openpolicyagent.org/docs/latest/policy-reference/#opa) | _ |
|
||||
|
||||
- [Tracing](https://www.openpolicyagent.org/docs/latest/policy-reference/#tracing)
|
||||
| Builtin | Feature |
|
||||
|----------------------------------------------------------------------------------------------|---------|
|
||||
| [trace](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-tracing-trace) | _ |
|
||||
BIN
docs/coverage.png
Normal file
BIN
docs/coverage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 607 KiB |
@@ -10,12 +10,16 @@ fn rego_eval(
|
||||
query: String,
|
||||
enable_tracing: bool,
|
||||
non_strict: bool,
|
||||
#[cfg(feature = "coverage")] coverage: bool,
|
||||
) -> Result<()> {
|
||||
// Create engine.
|
||||
let mut engine = regorus::Engine::new();
|
||||
|
||||
engine.set_strict_builtin_errors(!non_strict);
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
engine.set_enable_coverage(coverage);
|
||||
|
||||
// Load files from given bundles.
|
||||
for dir in bundles.iter() {
|
||||
let entries =
|
||||
@@ -68,8 +72,15 @@ fn rego_eval(
|
||||
|
||||
// Evaluate query.
|
||||
let results = engine.eval_query(query, enable_tracing)?;
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&results)?);
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
if coverage {
|
||||
let report = engine.get_coverage_report()?;
|
||||
println!("{}", report.to_colored_string()?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -137,9 +148,14 @@ enum RegorusCommand {
|
||||
#[arg(long, short)]
|
||||
trace: bool,
|
||||
|
||||
// Non strict execution
|
||||
/// Perform non-strict evaluation. (default behavior of OPA).
|
||||
#[arg(long, short)]
|
||||
non_strict: bool,
|
||||
|
||||
/// Display coverage information
|
||||
#[cfg(feature = "coverage")]
|
||||
#[arg(long, short)]
|
||||
coverage: bool,
|
||||
},
|
||||
|
||||
/// Tokenize a Rego policy.
|
||||
@@ -183,7 +199,18 @@ fn main() -> Result<()> {
|
||||
query,
|
||||
trace,
|
||||
non_strict,
|
||||
} => rego_eval(&bundles, &data, input, query, trace, non_strict),
|
||||
#[cfg(feature = "coverage")]
|
||||
coverage,
|
||||
} => rego_eval(
|
||||
&bundles,
|
||||
&data,
|
||||
input,
|
||||
query,
|
||||
trace,
|
||||
non_strict,
|
||||
#[cfg(feature = "coverage")]
|
||||
coverage,
|
||||
),
|
||||
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
|
||||
RegorusCommand::Parse { file } => rego_parse(file),
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
set -e
|
||||
|
||||
if ! command -v grcov > /dev/null; then
|
||||
cargo install grcov
|
||||
fi
|
||||
|
||||
if ! command -v llvm-profdata > /dev/null; then
|
||||
rustup component add llvm-tools-preview
|
||||
fi
|
||||
|
||||
#export LLVM_PROFILE_FILE='target/cargo-test-%p-%m.profraw'
|
||||
#export CARGO_INCREMENTAL=1
|
||||
#export RUSTFLAGS='-Cinstrument-coverage'
|
||||
|
||||
echo "Building with instrumentation"
|
||||
cargo build --all-targets
|
||||
|
||||
if [ "$1" == "--no-run" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Remove existing coverage information.
|
||||
rm -f target/*.profraw
|
||||
rm -rf target/coverage
|
||||
mkdir -p target/coverage
|
||||
|
||||
echo "Running tests"
|
||||
cargo test
|
||||
|
||||
# Generate html
|
||||
grcov target/ --binary-path ./target/debug/deps -s src/ -t html \
|
||||
--branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
|
||||
|
||||
if [ "$1" == "--show" ]; then
|
||||
echo "Opening report in browser"
|
||||
xdg-open target/coverage/html/index.html 2>/dev/null
|
||||
echo "Done"
|
||||
fi
|
||||
|
||||
# Generate markdown
|
||||
grcov target/ --binary-path ./target/debug/deps -s src/ -t markdown \
|
||||
--branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/markdown
|
||||
|
||||
cat target/coverage/markdown
|
||||
|
||||
# Print small-form table of files without 100% coverage.
|
||||
echo "Files without 100% coverage"
|
||||
while read p; do
|
||||
file=$(echo "$p" | cut -f 2 -d '|')
|
||||
percent=$(echo "$p" | cut -f 3 -d '|')
|
||||
missing=$(echo "$p" | cut -f 5 -d '|')
|
||||
|
||||
if [ -z "$file" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
# Trim percentage using xargs.
|
||||
case $(echo "$percent" | xargs) in
|
||||
"100%"|"100.00%")
|
||||
continue
|
||||
esac
|
||||
|
||||
|
||||
echo "| $file | $percent |"
|
||||
done < target/coverage/markdown
|
||||
|
||||
#TODO: Maybe use coveralls format (json) and query data to lockdown code coverage.
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
git stash
|
||||
cargo doc --no-deps
|
||||
git checkout docs
|
||||
rm -rf docs
|
||||
cp -r target/x86_64-unknown-linux-musl/doc ./docs
|
||||
echo "<meta http-equiv=\"refresh\" content=\"0; url=regorus/index.html\">" > docs/index.html
|
||||
git add docs
|
||||
git commit -s
|
||||
git push
|
||||
git checkout -
|
||||
git stash pop
|
||||
@@ -6,7 +6,7 @@ set -eo pipefail
|
||||
|
||||
if [ -f Cargo.toml ]; then
|
||||
# Ensure that all targets can be built.
|
||||
cargo build --all-targets
|
||||
cargo build -r --all-targets
|
||||
|
||||
#Ensure that code is correctly formatted.
|
||||
cargo fmt --check || (echo "Run cargo fmt to fix formatting" && exit 1)
|
||||
|
||||
@@ -9,12 +9,13 @@ if [ -f Cargo.toml ]; then
|
||||
dir=$(dirname "${BASH_SOURCE[0]}")
|
||||
"$dir/pre-commit"
|
||||
|
||||
# Ensure that the public API works
|
||||
cargo test -r --doc
|
||||
|
||||
# Ensure that all tests pass
|
||||
# Also generate coverage information.
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
scripts/coverage
|
||||
fi
|
||||
cargo test -r
|
||||
cargo test -r --test aci
|
||||
|
||||
# Ensure that OPA conformance tests don't regress.
|
||||
cargo test -r --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
cargo test -r --features opa-testutil --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
fi
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
set -e
|
||||
yaml=$(realpath -e $1)
|
||||
|
||||
RUST_BACKTRACE=1 cargo test interpreter::one_yaml -- --include-ignored --nocapture "$yaml"
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
set -e
|
||||
yaml=$(realpath -e $1)
|
||||
|
||||
RUST_BACKTRACE=1 cargo test parser::one_yaml -- --include-ignored --nocapture "$yaml"
|
||||
@@ -1,23 +0,0 @@
|
||||
Cpackage play
|
||||
|
||||
a := {4}
|
||||
|
||||
mydoc(x) := path {
|
||||
path := "data.play.a"
|
||||
}
|
||||
|
||||
x := [ y |
|
||||
y := data.play.a | data.play.b with data.play.a as {5} with data.play.b as {6}
|
||||
]
|
||||
|
||||
r := [ m | m := data.play.p with data.play.p as 5 + 6; true ]
|
||||
|
||||
|
||||
allow {
|
||||
input.x
|
||||
== 5
|
||||
|
||||
input.y == 5
|
||||
input.y
|
||||
== 5
|
||||
}
|
||||
12
src/ast.rs
12
src/ast.rs
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::lexer::*;
|
||||
use crate::Rc;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
@@ -37,7 +38,7 @@ pub enum AssignOp {
|
||||
}
|
||||
|
||||
pub struct NodeRef<T> {
|
||||
r: std::rc::Rc<T>,
|
||||
r: Rc<T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for NodeRef<T> {
|
||||
@@ -54,7 +55,7 @@ impl<T: std::fmt::Debug> std::fmt::Debug for NodeRef<T> {
|
||||
|
||||
impl<T> std::cmp::PartialEq for NodeRef<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::rc::Rc::as_ptr(&self.r).eq(&std::rc::Rc::as_ptr(&other.r))
|
||||
Rc::as_ptr(&self.r).eq(&Rc::as_ptr(&other.r))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ impl<T> std::cmp::Eq for NodeRef<T> {}
|
||||
|
||||
impl<T> std::cmp::Ord for NodeRef<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
std::rc::Rc::as_ptr(&self.r).cmp(&std::rc::Rc::as_ptr(&other.r))
|
||||
Rc::as_ptr(&self.r).cmp(&Rc::as_ptr(&other.r))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +89,7 @@ impl<T> AsRef<T> for NodeRef<T> {
|
||||
|
||||
impl<T> NodeRef<T> {
|
||||
pub fn new(t: T) -> Self {
|
||||
Self {
|
||||
r: std::rc::Rc::new(t),
|
||||
}
|
||||
Self { r: Rc::new(t) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,6 +352,7 @@ pub struct Module {
|
||||
pub package: Package,
|
||||
pub imports: Vec<Import>,
|
||||
pub policy: Vec<Ref<Rule>>,
|
||||
pub rego_v1: bool,
|
||||
}
|
||||
|
||||
pub type ExprRef = Ref<Expr>;
|
||||
|
||||
@@ -5,12 +5,12 @@ use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
use crate::builtins::utils::{ensure_args_count, ensure_array, ensure_numeric};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
use crate::Value;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("array.concat", (concat, 2));
|
||||
|
||||
@@ -17,10 +17,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("print", (print, MAX_ARGS));
|
||||
}
|
||||
|
||||
// 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> {
|
||||
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"));
|
||||
}
|
||||
@@ -28,11 +30,23 @@ fn print(span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> R
|
||||
let mut msg = String::default();
|
||||
for a in args {
|
||||
match a {
|
||||
Value::Undefined => msg += "<undefined>",
|
||||
_ => msg += format!("{a}").as_str(),
|
||||
Value::Undefined => msg += " <undefined>",
|
||||
Value::String(s) => msg += &format!(" {s}"),
|
||||
_ => msg += &format!(" {a}"),
|
||||
};
|
||||
}
|
||||
|
||||
span.message("print", msg.as_str());
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
#[allow(unused)]
|
||||
use crate::builtins::utils::{
|
||||
ensure_args_count, ensure_object, ensure_string, ensure_string_collection,
|
||||
};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[allow(unused)]
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
|
||||
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
@@ -41,11 +43,6 @@ 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.unmarshal", (json_unmarshal, 1));
|
||||
#[cfg(feature = "jsonschema")]
|
||||
{
|
||||
m.insert("json.match_schema", (json_match_schema, 2));
|
||||
m.insert("json.verify_schema", (json_verify_schema, 1));
|
||||
}
|
||||
|
||||
#[cfg(feature = "yaml")]
|
||||
{
|
||||
@@ -240,7 +237,7 @@ fn urlquery_decode_object(
|
||||
Err(_) => bail!(params[0].span().error("not a valid url query")),
|
||||
};
|
||||
|
||||
let mut map = BTreeMap::new();
|
||||
let mut map = std::collections::BTreeMap::new();
|
||||
for (k, v) in url.query_pairs() {
|
||||
let key = Value::String(k.clone().into());
|
||||
let value = Value::String(v.clone().into());
|
||||
@@ -382,72 +379,3 @@ fn json_unmarshal(
|
||||
let json_str = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
Value::from_json_str(&json_str).with_context(|| span.error("could not deserialize json."))
|
||||
}
|
||||
|
||||
#[cfg(feature = "jsonschema")]
|
||||
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::JSONSchema> {
|
||||
let schema_str = match arg {
|
||||
Value::String(schema_str) => schema_str.as_ref().to_string(),
|
||||
_ => arg.to_json_str()?,
|
||||
};
|
||||
|
||||
if let Ok(schema) = serde_json::from_str(&schema_str) {
|
||||
match jsonschema::JSONSchema::compile(&schema) {
|
||||
Ok(schema) => return Ok(schema),
|
||||
Err(e) => bail!(e.to_string()),
|
||||
}
|
||||
}
|
||||
bail!(param.span().error("not a valid json schema"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "jsonschema")]
|
||||
fn json_verify_schema(
|
||||
span: &Span,
|
||||
params: &[Ref<Expr>],
|
||||
args: &[Value],
|
||||
strict: bool,
|
||||
) -> Result<Value> {
|
||||
let name = "json.verify_schema";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
|
||||
Ok(Value::from_array(
|
||||
match compile_json_schema(¶ms[0], &args[0]) {
|
||||
Ok(_) => [Value::Bool(true), Value::Null],
|
||||
Err(e) if strict => bail!(params[0]
|
||||
.span()
|
||||
.error(format!("invalid schema: {e}").as_str())),
|
||||
Err(e) => [Value::Bool(false), Value::String(e.to_string().into())],
|
||||
}
|
||||
.to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "jsonschema")]
|
||||
fn json_match_schema(
|
||||
span: &Span,
|
||||
params: &[Ref<Expr>],
|
||||
args: &[Value],
|
||||
strict: bool,
|
||||
) -> Result<Value> {
|
||||
let name = "json.match_schema";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
|
||||
// The following is expected to succeed.
|
||||
let document: serde_json::Value = serde_json::from_str(&args[0].to_json_str()?)?;
|
||||
|
||||
Ok(Value::from_array(
|
||||
match compile_json_schema(¶ms[1], &args[1]) {
|
||||
Ok(schema) => match schema.validate(&document) {
|
||||
Ok(_) => [Value::Bool(true), Value::Null],
|
||||
Err(e) => [
|
||||
Value::Bool(false),
|
||||
Value::from_array(e.map(|e| Value::String(e.to_string().into())).collect()),
|
||||
],
|
||||
},
|
||||
Err(e) if strict => bail!(params[1]
|
||||
.span()
|
||||
.error(format!("invalid schema: {e}").as_str())),
|
||||
Err(e) => [Value::Bool(false), Value::String(e.to_string().into())],
|
||||
}
|
||||
.to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -3,19 +3,64 @@
|
||||
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
use crate::builtins::utils::ensure_args_count;
|
||||
use crate::builtins::utils::{ensure_args_count, ensure_string};
|
||||
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("io.jwt.decode", (jwt_decode, 1));
|
||||
m.insert("io.jwt.decode_verify", (jwt_decode_verify, 2));
|
||||
}
|
||||
|
||||
fn decode(span: &Span, jwt: String, strict: bool) -> Result<Value> {
|
||||
let Some((Ok(header), Ok(payload), Ok(signature))) = jwt
|
||||
.split('.')
|
||||
.map(|p| data_encoding::BASE64URL_NOPAD.decode(p.as_bytes()))
|
||||
.collect_tuple()
|
||||
else {
|
||||
if strict {
|
||||
bail!(span.error("invalid jwt token"));
|
||||
}
|
||||
return Ok(Value::Undefined);
|
||||
};
|
||||
|
||||
let header = String::from_utf8_lossy(&header).to_string();
|
||||
let payload = String::from_utf8_lossy(&payload).to_string();
|
||||
let signature = data_encoding::HEXLOWER_PERMISSIVE.encode(&signature);
|
||||
|
||||
let signature = Value::String(signature.into());
|
||||
let header = Value::from_json_str(&header)?;
|
||||
|
||||
if header["enc"] != Value::Undefined {
|
||||
bail!(span.error("JWT is a JWE object, which is not supported"));
|
||||
}
|
||||
|
||||
if header["cty"] == "JWT".into() {
|
||||
if payload.len() <= 2 || !payload.starts_with('"') || !payload.ends_with('"') {
|
||||
bail!(span.error("invalid nested JWT"));
|
||||
}
|
||||
// Ignore ""
|
||||
decode(span, payload[1..payload.len() - 1].to_string(), strict)
|
||||
} else {
|
||||
let payload = Value::from_json_str(&payload)?;
|
||||
Ok(Value::from_array([header, payload, signature].into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn jwt_decode(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
||||
let name = "io.jwt.decode";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
let jwt = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
|
||||
decode(span, jwt.to_string(), strict) //header, payload, signature, strict)
|
||||
}
|
||||
|
||||
fn jwt_decode_verify(
|
||||
span: &Span,
|
||||
params: &[Ref<Expr>],
|
||||
|
||||
@@ -54,6 +54,8 @@ 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;
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ use crate::ast::{Expr, Ref};
|
||||
use crate::builtins;
|
||||
use crate::builtins::utils::{ensure_args_count, ensure_array, ensure_object};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
use crate::Value;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::iter::Iterator;
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
@@ -23,6 +23,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("object.subset", (subset, 2));
|
||||
m.insert("object.union", (object_union, 2));
|
||||
m.insert("object.union_n", (object_union_n, 1));
|
||||
|
||||
#[cfg(feature = "jsonschema")]
|
||||
{
|
||||
m.insert("json.match_schema", (json_match_schema, 2));
|
||||
m.insert("json.verify_schema", (json_verify_schema, 1));
|
||||
}
|
||||
}
|
||||
|
||||
fn json_filter_impl(v: &Value, filter: &Value) -> Value {
|
||||
@@ -382,3 +388,72 @@ fn object_union_n(
|
||||
|
||||
Ok(u)
|
||||
}
|
||||
|
||||
#[cfg(feature = "jsonschema")]
|
||||
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::JSONSchema> {
|
||||
let schema_str = match arg {
|
||||
Value::String(schema_str) => schema_str.as_ref().to_string(),
|
||||
_ => arg.to_json_str()?,
|
||||
};
|
||||
|
||||
if let Ok(schema) = serde_json::from_str(&schema_str) {
|
||||
match jsonschema::JSONSchema::compile(&schema) {
|
||||
Ok(schema) => return Ok(schema),
|
||||
Err(e) => bail!(e.to_string()),
|
||||
}
|
||||
}
|
||||
bail!(param.span().error("not a valid json schema"))
|
||||
}
|
||||
|
||||
#[cfg(feature = "jsonschema")]
|
||||
fn json_verify_schema(
|
||||
span: &Span,
|
||||
params: &[Ref<Expr>],
|
||||
args: &[Value],
|
||||
strict: bool,
|
||||
) -> Result<Value> {
|
||||
let name = "json.verify_schema";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
|
||||
Ok(Value::from_array(
|
||||
match compile_json_schema(¶ms[0], &args[0]) {
|
||||
Ok(_) => [Value::Bool(true), Value::Null],
|
||||
Err(e) if strict => bail!(params[0]
|
||||
.span()
|
||||
.error(format!("invalid schema: {e}").as_str())),
|
||||
Err(e) => [Value::Bool(false), Value::String(e.to_string().into())],
|
||||
}
|
||||
.to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "jsonschema")]
|
||||
fn json_match_schema(
|
||||
span: &Span,
|
||||
params: &[Ref<Expr>],
|
||||
args: &[Value],
|
||||
strict: bool,
|
||||
) -> Result<Value> {
|
||||
let name = "json.match_schema";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
|
||||
// The following is expected to succeed.
|
||||
let document: serde_json::Value = serde_json::from_str(&args[0].to_json_str()?)?;
|
||||
|
||||
Ok(Value::from_array(
|
||||
match compile_json_schema(¶ms[1], &args[1]) {
|
||||
Ok(schema) => match schema.validate(&document) {
|
||||
Ok(_) => [Value::Bool(true), Value::Null],
|
||||
Err(e) => [
|
||||
Value::Bool(false),
|
||||
Value::from_array(e.map(|e| Value::String(e.to_string().into())).collect()),
|
||||
],
|
||||
},
|
||||
Err(e) if strict => bail!(params[1]
|
||||
.span()
|
||||
.error(format!("invalid schema: {e}").as_str())),
|
||||
Err(e) => [Value::Bool(false), Value::String(e.to_string().into())],
|
||||
}
|
||||
.to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -157,7 +157,9 @@ fn to_string(v: &Value, unescape: bool) -> String {
|
||||
match v {
|
||||
Value::Null => "null".to_owned(),
|
||||
Value::Bool(b) => b.to_string(),
|
||||
Value::String(s) if unescape => serde_json::to_string(&s).unwrap_or(s.as_ref().to_string()),
|
||||
Value::String(s) if unescape => {
|
||||
serde_json::to_string(s.as_ref()).unwrap_or(s.as_ref().to_string())
|
||||
}
|
||||
Value::String(s) => s.as_ref().to_string(),
|
||||
Value::Number(n) => n.format_decimal(),
|
||||
Value::Array(a) => {
|
||||
|
||||
@@ -12,8 +12,8 @@ use std::collections::HashMap;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
use chrono::{
|
||||
DateTime, Datelike, Days, FixedOffset, Local, Months, NaiveDateTime, SecondsFormat, TimeZone,
|
||||
Timelike, Utc, Weekday,
|
||||
DateTime, Datelike, Days, FixedOffset, Local, Months, SecondsFormat, TimeZone, Timelike, Utc,
|
||||
Weekday,
|
||||
};
|
||||
use chrono_tz::Tz;
|
||||
|
||||
@@ -124,7 +124,7 @@ fn format(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> R
|
||||
let (datetime, format) = parse_epoch(name, ¶ms[0], &args[0])?;
|
||||
|
||||
let result = match format {
|
||||
Some(format) => datetime.format(&format).to_string(),
|
||||
Some(format) => compat::format(datetime, layout_with_predefined_formats(&format)),
|
||||
None => datetime.to_rfc3339_opts(SecondsFormat::AutoSi, true),
|
||||
};
|
||||
|
||||
@@ -159,7 +159,7 @@ fn parse_ns(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) ->
|
||||
let layout = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
let value = ensure_string(name, ¶ms[1], &args[1])?;
|
||||
|
||||
let datetime = NaiveDateTime::parse_from_str(&value, &layout)?;
|
||||
let datetime = compat::parse(layout_with_predefined_formats(&layout), &value)?;
|
||||
safe_timestamp_nanos(span, strict, datetime.timestamp_nanos_opt())
|
||||
}
|
||||
|
||||
@@ -275,3 +275,21 @@ fn parse_epoch(
|
||||
"`{fcn}` expects `ns` to be a `number` or `array[number, string]`. Got `{val}` instead"
|
||||
)))
|
||||
}
|
||||
|
||||
fn layout_with_predefined_formats(format: &str) -> &str {
|
||||
match format {
|
||||
"ANSIC" => "Mon Jan _2 15:04:05 2006",
|
||||
"UnixDate" => "Mon Jan _2 15:04:05 MST 2006",
|
||||
"RubyDate" => "Mon Jan 02 15:04:05 -0700 2006",
|
||||
"RFC822" => "02 Jan 06 15:04 MST",
|
||||
// RFC822 with numeric zone
|
||||
"RFC822Z" => "02 Jan 06 15:04 -0700",
|
||||
"RFC850" => "Monday, 02-Jan-06 15:04:05 MST",
|
||||
"RFC1123" => "Mon, 02 Jan 2006 15:04:05 MST",
|
||||
// RFC1123 with numeric zone
|
||||
"RFC1123Z" => "Mon, 02 Jan 2006 15:04:05 -0700",
|
||||
"RFC3339" => "2006-01-02T15:04:05Z07:00",
|
||||
"RFC3339Nano" => "2006-01-02T15:04:05.999999999Z07:00",
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT and Apache 2.0 License.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{DateTime, Datelike, FixedOffset, TimeZone, Timelike, Utc};
|
||||
use chrono::{DateTime, Datelike, FixedOffset, NaiveDate, Timelike};
|
||||
|
||||
// Adapted from the official Go implementation:
|
||||
// https://github.com/open-policy-agent/opa/blob/eb17a716b97720a27c6569395ba7c4b7409aae87/topdown/time.go#L179-L243
|
||||
@@ -12,7 +12,6 @@ pub fn diff_between_datetimes(
|
||||
) -> Result<(i32, i32, i32, i32, i32, i32)> {
|
||||
// The following implementation of this function is taken
|
||||
// from https://github.com/icza/gox licensed under Apache 2.0.
|
||||
// The only modification made is to variable names.
|
||||
//
|
||||
// For details, see https://stackoverflow.com/a/36531443/1705598
|
||||
//
|
||||
@@ -50,12 +49,9 @@ pub fn diff_between_datetimes(
|
||||
day -= 1;
|
||||
}
|
||||
if day < 0 {
|
||||
// Days in month:
|
||||
let t = Utc
|
||||
.with_ymd_and_hms(datetime1.year(), datetime1.month(), 32, 0, 0, 0)
|
||||
.single()
|
||||
let days_in_month = days_in_month(datetime1.year(), datetime1.month())
|
||||
.ok_or(anyhow!("Could not convert `ns1` to datetime"))?;
|
||||
day += 32 - t.day() as i32;
|
||||
day += days_in_month as i32;
|
||||
month -= 1;
|
||||
}
|
||||
if month < 0 {
|
||||
@@ -67,3 +63,21 @@ pub fn diff_between_datetimes(
|
||||
|
||||
Ok((year, month, day, hour, min, sec))
|
||||
}
|
||||
|
||||
fn days_in_month(year: i32, month: u32) -> Option<i64> {
|
||||
Some(
|
||||
NaiveDate::from_ymd_opt(
|
||||
match month {
|
||||
12 => year + 1,
|
||||
_ => year,
|
||||
},
|
||||
match month {
|
||||
12 => 1,
|
||||
_ => month + 1,
|
||||
},
|
||||
1,
|
||||
)?
|
||||
.signed_duration_since(NaiveDate::from_ymd_opt(year, month, 1)?)
|
||||
.num_days(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
use crate::ast::{Expr, Ref};
|
||||
use crate::lexer::Span;
|
||||
use crate::number::Number;
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
use crate::Value;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
|
||||
525
src/engine.rs
525
src/engine.rs
@@ -8,19 +8,23 @@ use crate::parser::*;
|
||||
use crate::scheduler::*;
|
||||
use crate::utils::gather_functions;
|
||||
use crate::value::*;
|
||||
use crate::{Extension, QueryResults};
|
||||
|
||||
use std::convert::AsRef;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
#[derive(Clone)]
|
||||
/// The Rego evaluation engine.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Engine {
|
||||
modules: Vec<Ref<Module>>,
|
||||
interpreter: Interpreter,
|
||||
prepared: bool,
|
||||
}
|
||||
|
||||
/// Create a default engine.
|
||||
impl Default for Engine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -28,6 +32,7 @@ impl Default for Engine {
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Create an instance of [Engine].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
modules: vec![],
|
||||
@@ -36,6 +41,29 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a policy.
|
||||
///
|
||||
/// The policy file will be parsed and converted to AST representation.
|
||||
/// Multiple policy files may be added to the engine.
|
||||
///
|
||||
/// * `path`: A filename to be associated with the policy.
|
||||
/// * `rego`: The rego policy code.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.add_policy(
|
||||
/// "test.rego".to_string(),
|
||||
/// r#"
|
||||
/// package test
|
||||
/// allow = input.user == "root"
|
||||
/// "#.to_string())?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
pub fn add_policy(&mut self, path: String, rego: String) -> Result<()> {
|
||||
let source = Source::new(path, rego);
|
||||
let mut parser = Parser::new(&source)?;
|
||||
@@ -45,6 +73,22 @@ impl Engine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// * `path`: Path to the policy file (.rego).
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.add_policy_from_file("tests/aci/framework.rego")?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn add_policy_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
let source = Source::from_file(path)?;
|
||||
let mut parser = Parser::new(&source)?;
|
||||
@@ -53,28 +97,295 @@ impl Engine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the input document.
|
||||
///
|
||||
/// * `input`: Input documented. Typically this [Value] is constructed from JSON or YAML.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// let input = Value::from_json_str(r#"
|
||||
/// {
|
||||
/// "role" : "admin",
|
||||
/// "action": "delete"
|
||||
/// }"#)?;
|
||||
///
|
||||
/// engine.set_input(input);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_input(&mut self, input: Value) {
|
||||
self.interpreter.set_input(input);
|
||||
}
|
||||
|
||||
pub fn set_input_json(&mut self, input_json: &str) -> Result<()> {
|
||||
self.set_input(Value::from_json_str(input_json)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear the data document.
|
||||
///
|
||||
/// The data document will be reset to an empty object.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.clear_data();
|
||||
///
|
||||
/// // Evaluate data.
|
||||
/// let results = engine.eval_query("data".to_string(), false)?;
|
||||
///
|
||||
/// // Assert that it is empty object.
|
||||
/// assert_eq!(results.result.len(), 1);
|
||||
/// assert_eq!(results.result[0].expressions.len(), 1);
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::new_object());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn clear_data(&mut self) {
|
||||
self.interpreter.set_data(Value::new_object());
|
||||
self.prepared = false;
|
||||
}
|
||||
|
||||
/// Add data document.
|
||||
///
|
||||
/// The specified data document is merged into existing data document.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Only objects can be added.
|
||||
/// assert!(engine.add_data(Value::from_json_str("[]")?).is_err());
|
||||
///
|
||||
/// // Merge { "x" : 1, "y" : {} }
|
||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?).is_ok());
|
||||
///
|
||||
/// // Merge { "z" : 2 }
|
||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?).is_ok());
|
||||
///
|
||||
/// // Merge { "z" : 3 }. Conflict error.
|
||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 3 }"#)?).is_err());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// engine.eval_query("data".to_string(), false)?.result[0].expressions[0].value,
|
||||
/// Value::from_json_str(r#"{ "x": 1, "y": {}, "z": 2}"#)?
|
||||
/// );
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn add_data(&mut self, data: Value) -> Result<()> {
|
||||
if data.as_object().is_err() {
|
||||
bail!("data must be object");
|
||||
}
|
||||
self.prepared = false;
|
||||
self.interpreter.get_data_mut().merge(data)
|
||||
}
|
||||
|
||||
pub fn get_modules(&mut self) -> &Vec<Ref<Module>> {
|
||||
&self.modules
|
||||
pub fn add_data_json(&mut self, data_json: &str) -> Result<()> {
|
||||
self.add_data(Value::from_json_str(data_json)?)
|
||||
}
|
||||
|
||||
/// Set whether builtins should raise errors strictly or not.
|
||||
///
|
||||
/// Regorus differs from OPA in that by default builtins will
|
||||
/// raise errors instead of returning Undefined.
|
||||
///
|
||||
/// ----
|
||||
/// **_NOTE:_** Currently not all builtins honor this flag and will always strictly raise errors.
|
||||
/// ----
|
||||
pub fn set_strict_builtin_errors(&mut self, b: bool) {
|
||||
self.interpreter.set_strict_builtin_errors(b)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn get_modules(&mut self) -> &Vec<Ref<Module>> {
|
||||
&self.modules
|
||||
}
|
||||
|
||||
/// Evaluate a Rego query.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Add 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")?;
|
||||
///
|
||||
/// // Add data document (if any).
|
||||
/// // If multiple data documents can be added, they will be merged together.
|
||||
/// engine.add_data(Value::from_json_file("tests/aci/data.json")?)?;
|
||||
///
|
||||
/// // At this point the policies and data have been loaded.
|
||||
/// // Either the same engine can be used to make multiple queries or the engine
|
||||
/// // can be cloned to avoid having the reload the policies and data.
|
||||
/// let _clone = engine.clone();
|
||||
///
|
||||
/// // Evaluate a query.
|
||||
/// // Load input and make query.
|
||||
/// engine.set_input(Value::new_object());
|
||||
/// let results = engine.eval_query("data.framework.mount_overlay.allowed".to_string(), false)?;
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(false));
|
||||
///
|
||||
/// // Evaluate query with different inputs.
|
||||
/// engine.set_input(Value::from_json_file("tests/aci/input.json")?);
|
||||
/// let results = engine.eval_query("data.framework.mount_overlay.allowed".to_string(), false)?;
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(true));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_query(&mut self, query: String, enable_tracing: bool) -> Result<QueryResults> {
|
||||
self.prepare_for_eval(enable_tracing)?;
|
||||
self.interpreter.clean_internal_evaluation_state();
|
||||
|
||||
self.interpreter.create_rule_prefixes()?;
|
||||
let query_module = {
|
||||
let source = Source::new(
|
||||
"<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 mut parser = Parser::new(&query_source)?;
|
||||
let query_node = parser.parse_user_query()?;
|
||||
if query_node.span.text() == "data" {
|
||||
self.eval_modules(enable_tracing)?;
|
||||
}
|
||||
let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
|
||||
self.interpreter.eval_user_query(
|
||||
&query_module,
|
||||
&query_node,
|
||||
&query_schedule,
|
||||
enable_tracing,
|
||||
)
|
||||
}
|
||||
|
||||
/// Evaluate a Rego query that produces a boolean value.
|
||||
///
|
||||
///
|
||||
/// This function should be preferred over [`Engine::eval_query`] if just a `true`/`false`
|
||||
/// value is desired instead of [`QueryResults`].
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # let mut engine = Engine::new();
|
||||
///
|
||||
/// let enable_tracing = false;
|
||||
/// assert_eq!(engine.eval_bool_query("1 > 2".to_string(), enable_tracing)?, false);
|
||||
/// assert_eq!(engine.eval_bool_query("1 < 2".to_string(), enable_tracing)?, true);
|
||||
///
|
||||
/// // Non boolean queries will raise an error.
|
||||
/// assert!(engine.eval_bool_query("1+1".to_string(), enable_tracing).is_err());
|
||||
///
|
||||
/// // Queries producing multiple values will raise an error.
|
||||
/// assert!(engine.eval_bool_query("true; true".to_string(), enable_tracing).is_err());
|
||||
///
|
||||
/// // Queries producing no values will raise an error.
|
||||
/// assert!(engine.eval_bool_query("true; false; true".to_string(), enable_tracing).is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_bool_query(&mut self, query: String, enable_tracing: bool) -> Result<bool> {
|
||||
let results = self.eval_query(query, enable_tracing)?;
|
||||
match results.result.len() {
|
||||
0 => bail!("query did not produce any values"),
|
||||
1 if results.result[0].expressions.len() == 1 => {
|
||||
results.result[0].expressions[0].value.as_bool().copied()
|
||||
}
|
||||
_ => bail!("query produced more than one value"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate an `allow` query.
|
||||
///
|
||||
/// This is a wrapper over [`Engine::eval_bool_query`] that returns true only if the
|
||||
/// boolean query succeed and produced a `true` value.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # let mut engine = Engine::new();
|
||||
///
|
||||
/// let enable_tracing = false;
|
||||
/// assert_eq!(engine.eval_allow_query("1 > 2".to_string(), enable_tracing), false);
|
||||
/// assert_eq!(engine.eval_allow_query("1 < 2".to_string(), enable_tracing), true);
|
||||
///
|
||||
/// assert_eq!(engine.eval_allow_query("1+1".to_string(), enable_tracing), false);
|
||||
/// assert_eq!(engine.eval_allow_query("true; true".to_string(), enable_tracing), false);
|
||||
/// assert_eq!(engine.eval_allow_query("true; false; true".to_string(), enable_tracing), false);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn eval_allow_query(&mut self, query: String, enable_tracing: bool) -> bool {
|
||||
matches!(self.eval_bool_query(query, enable_tracing), Ok(true))
|
||||
}
|
||||
|
||||
/// Evaluate a `deny` query.
|
||||
///
|
||||
/// This is a wrapper over [`Engine::eval_bool_query`] that returns false only if the
|
||||
/// boolean query succeed and produced a `false` value.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # let mut engine = Engine::new();
|
||||
///
|
||||
/// let enable_tracing = false;
|
||||
/// assert_eq!(engine.eval_deny_query("1 > 2".to_string(), enable_tracing), false);
|
||||
/// assert_eq!(engine.eval_deny_query("1 < 2".to_string(), enable_tracing), true);
|
||||
///
|
||||
/// assert_eq!(engine.eval_deny_query("1+1".to_string(), enable_tracing), true);
|
||||
/// assert_eq!(engine.eval_deny_query("true; true".to_string(), enable_tracing), true);
|
||||
/// assert_eq!(engine.eval_deny_query("true; false; true".to_string(), enable_tracing), true);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn eval_deny_query(&mut self, query: String, enable_tracing: bool) -> bool {
|
||||
!matches!(self.eval_bool_query(query, enable_tracing), Ok(false))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Evaluate the given query and all the rules in the supplied policies.
|
||||
///
|
||||
/// This is mainly used for testing Regorus itself.
|
||||
pub fn eval_query_and_all_rules(
|
||||
&mut self,
|
||||
query: String,
|
||||
enable_tracing: bool,
|
||||
) -> Result<QueryResults> {
|
||||
self.eval_modules(enable_tracing)?;
|
||||
|
||||
let query_module = {
|
||||
let source = Source::new(
|
||||
"<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 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)?;
|
||||
self.interpreter.eval_user_query(
|
||||
&query_module,
|
||||
&query_node,
|
||||
&query_schedule,
|
||||
enable_tracing,
|
||||
)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn prepare_for_eval(&mut self, enable_tracing: bool) -> Result<()> {
|
||||
self.interpreter.set_traces(enable_tracing);
|
||||
|
||||
@@ -107,6 +418,7 @@ impl Engine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn eval_rule(
|
||||
&mut self,
|
||||
module: &Ref<Module>,
|
||||
@@ -121,6 +433,7 @@ impl Engine {
|
||||
Ok(self.interpreter.get_data_mut().clone())
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn eval_modules(&mut self, enable_tracing: bool) -> Result<Value> {
|
||||
self.prepare_for_eval(enable_tracing)?;
|
||||
self.interpreter.clean_internal_evaluation_state();
|
||||
@@ -165,29 +478,191 @@ impl Engine {
|
||||
Ok(self.interpreter.get_data_mut().clone())
|
||||
}
|
||||
|
||||
pub fn eval_query(&mut self, query: String, enable_tracing: bool) -> Result<QueryResults> {
|
||||
self.eval_modules(false)?;
|
||||
/// Add a custom builtin (extension).
|
||||
///
|
||||
/// * `path`: The fully qualified path of the builtin.
|
||||
/// * `nargs`: The number of arguments the builtin takes.
|
||||
/// * `extension`: The [`Extension`] instance.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use regorus::*;
|
||||
/// # use anyhow::{bail, Result};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Policy uses `do_magic` custom builtin.
|
||||
/// engine.add_policy(
|
||||
/// "test.rego".to_string(),
|
||||
/// r#"package test
|
||||
/// x = do_magic(1)
|
||||
/// "#.to_string(),
|
||||
/// )?;
|
||||
///
|
||||
/// // Evaluating fails since `do_magic` is not defined.
|
||||
/// assert!(engine.eval_query("data.test.x".to_string(), false).is_err());
|
||||
///
|
||||
/// // Add extension to implement `do_magic`. The extension can be stateful.
|
||||
/// let mut magic = 8;
|
||||
/// engine.add_extension("do_magic".to_string(), 1 , Box::new(move | mut params: Vec<Value> | {
|
||||
/// // params is mut and therefore individual values can be removed from it and modified.
|
||||
/// // The number of parameters (1) has already been validated.
|
||||
///
|
||||
/// match ¶ms[0].as_i64() {
|
||||
/// Ok(i) => {
|
||||
/// // Compute value
|
||||
/// let v = *i + magic;
|
||||
/// // Update extension state.
|
||||
/// magic += 1;
|
||||
/// Ok(Value::from(v))
|
||||
/// }
|
||||
/// // Extensions can raise errors. Regorus will add location information to
|
||||
/// // the error.
|
||||
/// _ => bail!("do_magic expects i64 value")
|
||||
/// }
|
||||
/// }))?;
|
||||
///
|
||||
/// // Evaluation will now succeed.
|
||||
/// let r = engine.eval_query("data.test.x".to_string(), false)?;
|
||||
/// assert_eq!(r.result[0].expressions[0].value.as_i64()?, 9);
|
||||
///
|
||||
/// // Cloning the engine will also clone the extension.
|
||||
/// let mut engine1 = engine.clone();
|
||||
///
|
||||
/// // Evaluating again will return a different value since the extension is stateful.
|
||||
/// let r = engine.eval_query("data.test.x".to_string(), false)?;
|
||||
/// assert_eq!(r.result[0].expressions[0].value.as_i64()?, 10);
|
||||
///
|
||||
/// // The second engine has a clone of the extension.
|
||||
/// let r = engine1.eval_query("data.test.x".to_string(), false)?;
|
||||
/// assert_eq!(r.result[0].expressions[0].value.as_i64()?, 10);
|
||||
///
|
||||
/// // Once added, the extension cannot be replaced or removed.
|
||||
/// assert!(engine.add_extension("do_magic".to_string(), 1, Box::new(|_:Vec<Value>| {
|
||||
/// Ok(Value::Undefined)
|
||||
/// })).is_err());
|
||||
///
|
||||
/// // Extensions don't support out-parameter syntax.
|
||||
/// engine.add_policy(
|
||||
/// "policy.rego".to_string(),
|
||||
/// r#"package invalid
|
||||
/// x = y {
|
||||
/// # y = do_magic(2)
|
||||
/// do_magic(2, y) # y is supplied as an out parameter.
|
||||
/// }
|
||||
/// "#.to_string()
|
||||
/// )?;
|
||||
///
|
||||
/// // Evaluation fails since rule x calls an extension with out parameter.
|
||||
/// assert!(engine.eval_query("data.invalid.x".to_string(), false).is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn add_extension(
|
||||
&mut self,
|
||||
path: String,
|
||||
nargs: u8,
|
||||
extension: Box<dyn Extension>,
|
||||
) -> Result<()> {
|
||||
self.interpreter.add_extension(path, nargs, extension)
|
||||
}
|
||||
|
||||
let query_module = {
|
||||
let source = Source::new(
|
||||
"<query_module.rego>".to_owned(),
|
||||
"package __internal_query_module".to_owned(),
|
||||
);
|
||||
Ref::new(Parser::new(&source)?.parse()?)
|
||||
};
|
||||
#[cfg(feature = "coverage")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "coverage")))]
|
||||
/// Get the coverage report.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use regorus::*;
|
||||
/// # use anyhow::{bail, Result};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.add_policy(
|
||||
/// "policy.rego".to_string(),
|
||||
/// r#"
|
||||
/// package test # Line 2
|
||||
///
|
||||
/// x = y { # Line 4
|
||||
/// input.a > 2 # Line 5
|
||||
/// y = 5 # Line 6
|
||||
/// }
|
||||
/// "#.to_string()
|
||||
/// )?;
|
||||
///
|
||||
/// // Enable coverage.
|
||||
/// engine.set_enable_coverage(true);
|
||||
///
|
||||
/// engine.eval_query("data".to_string(), false)?;
|
||||
///
|
||||
/// let report = engine.get_coverage_report()?;
|
||||
/// assert_eq!(report.files[0].path, "policy.rego");
|
||||
///
|
||||
/// // Only line 5 is evaluated.
|
||||
/// assert_eq!(report.files[0].covered.iter().cloned().collect::<Vec<u32>>(), vec![5]);
|
||||
///
|
||||
/// // Line 4 and 6 are not evaluated.
|
||||
/// assert_eq!(report.files[0].not_covered.iter().cloned().collect::<Vec<u32>>(), vec![4, 6]);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// See also [`crate::coverage::Report::to_colored_string`].
|
||||
pub fn get_coverage_report(&self) -> Result<crate::coverage::Report> {
|
||||
self.interpreter.get_coverage_report()
|
||||
}
|
||||
|
||||
// Parse the query.
|
||||
let query_source = Source::new("<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)?;
|
||||
#[cfg(feature = "coverage")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "coverage")))]
|
||||
/// Enable/disable policy coverage.
|
||||
///
|
||||
/// If `enable` is different from the current value, then any existing coverage
|
||||
/// information will be cleared.
|
||||
pub fn set_enable_coverage(&mut self, enable: bool) {
|
||||
self.interpreter.set_enable_coverage(enable)
|
||||
}
|
||||
|
||||
let results = self.interpreter.eval_user_query(
|
||||
&query_module,
|
||||
&query_node,
|
||||
&query_schedule,
|
||||
enable_tracing,
|
||||
)?;
|
||||
Ok(results)
|
||||
#[cfg(feature = "coverage")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "coverage")))]
|
||||
/// Clear the gathered policy coverage data.
|
||||
pub fn clear_coverage_data(&mut self) {
|
||||
self.interpreter.clear_coverage_data()
|
||||
}
|
||||
|
||||
/// Gather output from print statements instead of emiting to stderr.
|
||||
///
|
||||
/// See [`Engine::take_prints`].
|
||||
pub fn set_gather_prints(&mut self, b: bool) {
|
||||
self.interpreter.set_gather_prints(b);
|
||||
}
|
||||
|
||||
/// Take the gathered output of print statements.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use regorus::*;
|
||||
/// # use anyhow::{bail, Result};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Print to stderr.
|
||||
/// engine.eval_query("print(\"Hello\")".to_string(), false)?;
|
||||
///
|
||||
/// // Configure gathering print statements.
|
||||
/// engine.set_gather_prints(true);
|
||||
///
|
||||
/// // Execute query.
|
||||
/// engine.eval_query("print(\"Hello\")".to_string(), false)?;
|
||||
///
|
||||
/// // Take and clear prints.
|
||||
/// let prints = engine.take_prints()?;
|
||||
/// assert_eq!(prints.len(), 1);
|
||||
/// assert!(prints[0].contains("Hello"));
|
||||
///
|
||||
/// for p in prints {
|
||||
/// println!("{p}");
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn take_prints(&mut self) -> Result<Vec<String>> {
|
||||
self.interpreter.take_prints()
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
41
src/lexer.rs
41
src/lexer.rs
@@ -6,9 +6,12 @@ use core::iter::Peekable;
|
||||
use core::str::CharIndices;
|
||||
|
||||
use std::convert::AsRef;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::value::Value;
|
||||
use crate::Rc;
|
||||
use crate::Value;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -20,7 +23,39 @@ struct SourceInternal {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Source {
|
||||
src: std::rc::Rc<SourceInternal>,
|
||||
src: Rc<SourceInternal>,
|
||||
}
|
||||
|
||||
impl std::cmp::Ord for Source {
|
||||
fn cmp(&self, other: &Source) -> std::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> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::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 Hash for Source {
|
||||
fn hash<H: 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> {
|
||||
self.src.file.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -108,7 +143,7 @@ impl Source {
|
||||
lines.push((s, s));
|
||||
}
|
||||
Self {
|
||||
src: std::rc::Rc::new(SourceInternal {
|
||||
src: Rc::new(SourceInternal {
|
||||
file,
|
||||
contents,
|
||||
lines,
|
||||
|
||||
387
src/lib.rs
387
src/lib.rs
@@ -3,6 +3,9 @@
|
||||
|
||||
// Use README.md as crate documentation.
|
||||
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
mod ast;
|
||||
mod builtins;
|
||||
@@ -16,10 +19,392 @@ mod utils;
|
||||
mod value;
|
||||
|
||||
pub use engine::Engine;
|
||||
pub use interpreter::{QueryResult, QueryResults};
|
||||
pub use value::Value;
|
||||
|
||||
#[cfg(feature = "arc")]
|
||||
use std::sync::Arc as Rc;
|
||||
|
||||
#[cfg(not(feature = "arc"))]
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Location of an [`Expression`] in a Rego query.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::Engine;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate " \n 1 + 2".
|
||||
/// let results = Engine::new().eval_query(" \n 1 + 2".to_string(), false)?;
|
||||
///
|
||||
/// // Fetch the location for the expression.
|
||||
/// let loc = &results.result[0].expressions[0].location;
|
||||
///
|
||||
/// assert_eq!(loc.row, 2);
|
||||
/// assert_eq!(loc.col, 3);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ````
|
||||
/// See also [`QueryResult`].
|
||||
#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
|
||||
pub struct Location {
|
||||
/// Line number. Starts at 1.
|
||||
pub row: u16,
|
||||
/// Column number. Starts at 1.
|
||||
pub col: u16,
|
||||
}
|
||||
|
||||
/// An expression in a Rego query.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "1 + 2".
|
||||
/// let results = Engine::new().eval_query("1 + 2".to_string(), false)?;
|
||||
///
|
||||
/// // Fetch the expression from results.
|
||||
/// let expr = &results.result[0].expressions[0];
|
||||
///
|
||||
/// assert_eq!(expr.value, Value::from(3u64));
|
||||
/// assert_eq!(expr.text.as_ref(), "1 + 2");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
/// See also [`QueryResult`].
|
||||
#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
|
||||
pub struct Expression {
|
||||
/// Computed value of the expression.
|
||||
pub value: Value,
|
||||
|
||||
/// The Rego expression.
|
||||
pub text: Rc<str>,
|
||||
|
||||
/// Location of the expression in the query string.
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
/// Result of evaluating a Rego query.
|
||||
///
|
||||
/// A query containing single expression.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "1 + 2".
|
||||
/// let results = Engine::new().eval_query("1 + 2".to_string(), false)?;
|
||||
///
|
||||
/// // Fetch the first (sole) result.
|
||||
/// let result = &results.result[0];
|
||||
///
|
||||
/// assert_eq!(result.expressions[0].value, Value::from(3u64));
|
||||
/// assert_eq!(result.expressions[0].text.as_ref(), "1 + 2");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// A query containing multiple expressions.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "1 + 2; 3.5 * 4".
|
||||
/// let results = Engine::new().eval_query("1 + 2; 3.55 * 4".to_string(), false)?;
|
||||
///
|
||||
/// // Fetch the first (sole) result.
|
||||
/// let result = &results.result[0];
|
||||
///
|
||||
/// // First expression.
|
||||
/// assert_eq!(result.expressions[0].value, Value::from(3u64));
|
||||
/// assert_eq!(result.expressions[0].text.as_ref(), "1 + 2");
|
||||
///
|
||||
/// // Second expression.
|
||||
/// assert_eq!(result.expressions[1].value, Value::from(14.2));
|
||||
/// assert_eq!(result.expressions[1].text.as_ref(), "3.55 * 4");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Expressions that create bindings (i.e. associate names to values) evaluate to
|
||||
/// either true or false. The value of bindings are available in the `bindings` field.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "x = 1; y = x > 0".
|
||||
/// let results = Engine::new().eval_query("x = 1; y = x > 0".to_string(), false)?;
|
||||
///
|
||||
/// // Fetch the first (sole) result.
|
||||
/// let result = &results.result[0];
|
||||
///
|
||||
/// // First expression is true.
|
||||
/// assert_eq!(result.expressions[0].value, Value::from(true));
|
||||
/// assert_eq!(result.expressions[0].text.as_ref(), "x = 1");
|
||||
///
|
||||
/// // Second expression is true.
|
||||
/// assert_eq!(result.expressions[1].value, Value::from(true));
|
||||
/// assert_eq!(result.expressions[1].text.as_ref(), "y = x > 0");
|
||||
///
|
||||
/// // bindings contains the value for each named expession.
|
||||
/// assert_eq!(result.bindings[&Value::from("x")], Value::from(1u64));
|
||||
/// assert_eq!(result.bindings[&Value::from("y")], Value::from(true));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// If any expression evaluates to false, then no results are produced.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "true; true; false".
|
||||
/// let results = Engine::new().eval_query("true; true; false".to_string(), false)?;
|
||||
///
|
||||
/// assert!(results.result.is_empty());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
|
||||
pub struct QueryResult {
|
||||
/// Expressions in the query.
|
||||
///
|
||||
/// Each statement in the query is treated as a separte expression.
|
||||
///
|
||||
pub expressions: Vec<Expression>,
|
||||
|
||||
/// Bindings created in the query.
|
||||
#[serde(skip_serializing_if = "Value::is_empty_object")]
|
||||
pub bindings: Value,
|
||||
}
|
||||
|
||||
impl Default for QueryResult {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bindings: Value::new_object(),
|
||||
expressions: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Results of evaluating a Rego query.
|
||||
///
|
||||
/// Generates the same `json` representation as `opa eval`.
|
||||
///
|
||||
/// Queries typically produce a single result.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "1 + 1".
|
||||
/// let results = Engine::new().eval_query("1 + 1".to_string(), false)?;
|
||||
///
|
||||
/// assert_eq!(results.result.len(), 1);
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(2u64));
|
||||
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 + 1");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// If a query contains only one expression, and even if the expression evaluates
|
||||
/// to false, the value will be returned.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "1 > 2" which is false.
|
||||
/// let results = Engine::new().eval_query("1 > 2".to_string(), false)?;
|
||||
///
|
||||
/// assert_eq!(results.result.len(), 1);
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(false));
|
||||
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 > 2");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// In a query containing multiple expressions, if any expression evaluates to false,
|
||||
/// then no results are produced.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "true; true; false".
|
||||
/// let results = Engine::new().eval_query("true; true; false".to_string(), false)?;
|
||||
///
|
||||
/// assert!(results.result.is_empty());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Note that `=` is different from `==`. The former evaluates to undefined if the LHS and RHS
|
||||
/// are not equal. The latter evaluates to either true or false.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// // Create engine and evaluate "1 = 2" which is undefined and produces no resutl.
|
||||
/// let results = Engine::new().eval_query("1 = 2".to_string(), false)?;
|
||||
///
|
||||
/// assert_eq!(results.result.len(), 0);
|
||||
///
|
||||
/// // Create engine and evaluate "1 == 2" which evaluates to false.
|
||||
/// let results = Engine::new().eval_query("1 == 2".to_string(), false)?;
|
||||
///
|
||||
/// assert_eq!(results.result.len(), 1);
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::from(false));
|
||||
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 == 2");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Queries containing loops produce multiple results.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let results = Engine::new().eval_query("x = [1, 2, 3][_]".to_string(), false)?;
|
||||
///
|
||||
/// // Three results are produced, one of each value of x.
|
||||
/// assert_eq!(results.result.len(), 3);
|
||||
///
|
||||
/// // Assert expressions and bindings of results.
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::Bool(true));
|
||||
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
|
||||
/// assert_eq!(results.result[0].bindings[&Value::from("x")], Value::from(1u64));
|
||||
///
|
||||
/// assert_eq!(results.result[1].expressions[0].value, Value::Bool(true));
|
||||
/// assert_eq!(results.result[1].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
|
||||
/// assert_eq!(results.result[1].bindings[&Value::from("x")], Value::from(2u64));
|
||||
///
|
||||
/// assert_eq!(results.result[2].expressions[0].value, Value::Bool(true));
|
||||
/// assert_eq!(results.result[2].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
|
||||
/// assert_eq!(results.result[2].bindings[&Value::from("x")], Value::from(3u64));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Loop iterations that evaluate to false or undefined don't produce results.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let results = Engine::new().eval_query("x = [1, 2, 3][_]; x >= 2".to_string(), false)?;
|
||||
///
|
||||
/// // Two results are produced, one for x = 2 and another for x = 3.
|
||||
/// assert_eq!(results.result.len(), 2);
|
||||
///
|
||||
/// // Assert expressions and bindings of results.
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::Bool(true));
|
||||
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
|
||||
/// assert_eq!(results.result[0].expressions[0].value, Value::Bool(true));
|
||||
/// assert_eq!(results.result[0].expressions[1].text.as_ref(), "x >= 2");
|
||||
/// assert_eq!(results.result[0].bindings[&Value::from("x")], Value::from(2u64));
|
||||
///
|
||||
/// assert_eq!(results.result[1].expressions[0].value, Value::Bool(true));
|
||||
/// assert_eq!(results.result[1].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
|
||||
/// assert_eq!(results.result[1].expressions[0].value, Value::Bool(true));
|
||||
/// assert_eq!(results.result[1].expressions[1].text.as_ref(), "x >= 2");
|
||||
/// assert_eq!(results.result[1].bindings[&Value::from("x")], Value::from(3u64));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// See [QueryResult] for examples of different kinds of results.
|
||||
#[derive(Debug, Clone, Default, Serialize, Eq, PartialEq)]
|
||||
pub struct QueryResults {
|
||||
/// Collection of results of evaluting a query.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub result: Vec<QueryResult>,
|
||||
}
|
||||
|
||||
/// A user defined builtin function implementation.
|
||||
///
|
||||
/// It is not necessary to implement this trait directly.
|
||||
pub trait Extension: FnMut(Vec<Value>) -> anyhow::Result<Value> + Send + Sync {
|
||||
/// Fn, FnMut etc are not sized and cannot be cloned in their boxed form.
|
||||
/// clone_box exists to overcome that.
|
||||
fn clone_box<'a>(&self) -> Box<dyn 'a + Extension>
|
||||
where
|
||||
Self: 'a;
|
||||
}
|
||||
|
||||
/// Automatically make matching closures a valid [`Extension`].
|
||||
impl<F> Extension for F
|
||||
where
|
||||
F: FnMut(Vec<Value>) -> anyhow::Result<Value> + Clone + Send + Sync,
|
||||
{
|
||||
fn clone_box<'a>(&self) -> Box<dyn 'a + Extension>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement clone for a boxed extension using [`Extension::clone_box`].
|
||||
impl<'a> Clone for Box<dyn 'a + Extension> {
|
||||
fn clone(&self) -> Self {
|
||||
(**self).clone_box()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn Extension {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
f.write_fmt(format_args!("<extension>"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "coverage")))]
|
||||
pub mod coverage {
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
/// Coverage information about a rego policy file.
|
||||
pub struct File {
|
||||
/// Path of the policy file.
|
||||
pub path: String,
|
||||
|
||||
/// The rego policy.
|
||||
pub code: String,
|
||||
|
||||
/// Lines that were evaluated.
|
||||
pub covered: std::collections::BTreeSet<u32>,
|
||||
|
||||
/// Lines that were not evaluated.
|
||||
pub not_covered: std::collections::BTreeSet<u32>,
|
||||
}
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
/// Policy coverage report.
|
||||
pub struct Report {
|
||||
/// Coverage information for files.
|
||||
pub files: Vec<File>,
|
||||
}
|
||||
|
||||
impl Report {
|
||||
/// Produce an ANSI color encoded version of the report.
|
||||
///
|
||||
/// Covered lines are green.
|
||||
/// Lines that are not covered are red.
|
||||
///
|
||||
/// <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:")?;
|
||||
for file in self.files.iter() {
|
||||
if file.not_covered.is_empty() {
|
||||
writeln!(&mut s, "{} has full coverage", file.path)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
writeln!(&mut s, "{}:", 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")?;
|
||||
} else if file.covered.contains(&line) {
|
||||
writeln!(&mut s, "\x1b[32m {line:4} {code}\x1b[0m")?;
|
||||
} else {
|
||||
writeln!(&mut s, " {line:4} {code}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(&mut s)?;
|
||||
Ok(std::str::from_utf8(&s)?.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Items in `unstable` are likely to change.
|
||||
#[doc(hidden)]
|
||||
pub mod unstable {
|
||||
pub use crate::ast::*;
|
||||
pub use crate::lexer::*;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use core::fmt::{Debug, Formatter};
|
||||
use std::cmp::{Ord, Ordering};
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
@@ -11,6 +10,8 @@ use anyhow::{anyhow, bail, Result};
|
||||
use serde::ser::Serializer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::Rc;
|
||||
|
||||
pub type BigInt = i128;
|
||||
|
||||
type BigFloat = scientific::Scientific;
|
||||
@@ -132,6 +133,26 @@ impl From<f64> for Number {
|
||||
}
|
||||
|
||||
impl Number {
|
||||
pub fn as_u128(&self) -> Option<u128> {
|
||||
match self {
|
||||
Big(b) if b.is_integer() => match u128::try_from(&b.d) {
|
||||
Ok(v) => Some(v),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_i128(&self) -> Option<i128> {
|
||||
match self {
|
||||
Big(b) if b.is_integer() => match i128::try_from(&b.d) {
|
||||
Ok(v) => Some(v),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_u64(&self) -> Option<u64> {
|
||||
match self {
|
||||
Big(b) if b.is_integer() => match u64::try_from(&b.d) {
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct Parser<'source> {
|
||||
line: u16,
|
||||
end: u16,
|
||||
future_keywords: BTreeMap<String, Span>,
|
||||
rego_v1: bool,
|
||||
}
|
||||
|
||||
const FUTURE_KEYWORDS: [&str; 4] = ["contains", "every", "if", "in"];
|
||||
@@ -30,6 +31,7 @@ impl<'source> Parser<'source> {
|
||||
line: 0,
|
||||
end: 0,
|
||||
future_keywords: BTreeMap::new(),
|
||||
rego_v1: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -76,19 +78,19 @@ impl<'source> Parser<'source> {
|
||||
|
||||
pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> {
|
||||
match &self.future_keywords.get(kw) {
|
||||
Some(s) if false => Err(self.source.error(
|
||||
Some(s) if self.rego_v1 => Err(self.source.error(
|
||||
span.line,
|
||||
span.col,
|
||||
format!(
|
||||
"this import shadows previous import of `{kw}` defined at:{}",
|
||||
self.source
|
||||
.message(s.line, s.col, "", "this import is shadowed.")
|
||||
s.message("", "this import is shadowed.")
|
||||
)
|
||||
.as_str(),
|
||||
)),
|
||||
_ => {
|
||||
self.future_keywords.insert(kw.to_string(), span.clone());
|
||||
if kw == "every" {
|
||||
if kw == "every" && !self.rego_v1 {
|
||||
//rego.v1 explicitly adds each keyword.
|
||||
self.future_keywords.insert("in".to_string(), span.clone());
|
||||
}
|
||||
Ok(())
|
||||
@@ -782,6 +784,17 @@ impl<'source> Parser<'source> {
|
||||
span.start = start;
|
||||
let op = match self.token_text() {
|
||||
"=" => AssignOp::Eq,
|
||||
":=" if self.rego_v1 => {
|
||||
if let Expr::Var(v) = &expr {
|
||||
if v.text() == "input" {
|
||||
bail!(span.error("input cannot be shadowed"));
|
||||
}
|
||||
if v.text() == "data" {
|
||||
bail!(span.error("data cannot be shadowed"));
|
||||
}
|
||||
}
|
||||
AssignOp::ColEq
|
||||
}
|
||||
":=" => AssignOp::ColEq,
|
||||
_ => {
|
||||
*self = state;
|
||||
@@ -974,6 +987,7 @@ impl<'source> Parser<'source> {
|
||||
let stmt = match self.parse_literal_stmt() {
|
||||
Ok(stmt) => stmt,
|
||||
Err(e) if is_definite_query => return Err(e),
|
||||
Err(e) if matches!(self.token_text(), "=" | ":=") => return Err(e),
|
||||
Err(_) => {
|
||||
// There was error parsing the first literal
|
||||
// Restore the state and return.
|
||||
@@ -1117,7 +1131,16 @@ impl<'source> Parser<'source> {
|
||||
let span = self.tok.1.clone();
|
||||
|
||||
let mut term = if self.tok.0 == TokenKind::Ident {
|
||||
Expr::Var(self.parse_var()?)
|
||||
let v = self.parse_var()?;
|
||||
if self.rego_v1 {
|
||||
if v.text() == "input" {
|
||||
bail!(span.error("input cannot be shadowed"));
|
||||
}
|
||||
if v.text() == "data" {
|
||||
bail!(span.error("data cannot be shadowed"));
|
||||
}
|
||||
}
|
||||
Expr::Var(v)
|
||||
} else {
|
||||
return Err(self.source.error(
|
||||
span.line,
|
||||
@@ -1311,6 +1334,9 @@ impl<'source> Parser<'source> {
|
||||
false
|
||||
}
|
||||
"{" => {
|
||||
if self.rego_v1 {
|
||||
bail!(span.error("`if` keyword is required before rule body"));
|
||||
}
|
||||
self.next_token()?;
|
||||
let query = Ref::new(self.parse_query(span.clone(), "}")?);
|
||||
span.end = self.end;
|
||||
@@ -1378,6 +1404,9 @@ impl<'source> Parser<'source> {
|
||||
});
|
||||
}
|
||||
"{" => {
|
||||
if self.rego_v1 {
|
||||
bail!(span.error("`if` keyword is required before rule body"));
|
||||
}
|
||||
self.next_token()?;
|
||||
let query = Ref::new(self.parse_query(span.clone(), "}")?);
|
||||
span.end = self.end;
|
||||
@@ -1397,7 +1426,21 @@ impl<'source> Parser<'source> {
|
||||
"expected assignment or query after `else`",
|
||||
));
|
||||
}
|
||||
_ => break,
|
||||
_ => {
|
||||
let mut query_span = span.clone();
|
||||
query_span.end = query_span.start;
|
||||
let query = Ref::new(Query {
|
||||
span: query_span,
|
||||
stmts: vec![],
|
||||
});
|
||||
span.end = self.end;
|
||||
bodies.push(RuleBody {
|
||||
span,
|
||||
assign,
|
||||
query,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -1463,6 +1506,25 @@ impl<'source> Parser<'source> {
|
||||
let head = self.parse_rule_head()?;
|
||||
let bodies = self.parse_rule_bodies()?;
|
||||
span.end = self.end;
|
||||
|
||||
if self.rego_v1 && bodies.is_empty() {
|
||||
match &head {
|
||||
RuleHead::Compr { assign, .. } | RuleHead::Func { assign, .. }
|
||||
if assign.is_none() =>
|
||||
{
|
||||
bail!(span.error("rule must have a body or assignment"));
|
||||
}
|
||||
RuleHead::Set { refr, key, .. } if key.is_none() => {
|
||||
if Self::get_path_ref_components(refr)?.len() == 2 {
|
||||
bail!(span.error("`contains` keyword is required for partial set rules"));
|
||||
} else {
|
||||
bail!(span.error("rule must have a body or assignment"));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Rule::Spec { span, head, bodies })
|
||||
}
|
||||
|
||||
@@ -1526,15 +1588,25 @@ impl<'source> Parser<'source> {
|
||||
let refr = Ref::new(self.parse_path_ref()?);
|
||||
|
||||
let comps = Self::get_path_ref_components(&refr)?;
|
||||
if !matches!(comps[0].text(), "data" | "future" | "input") {
|
||||
span.end = self.end;
|
||||
if !matches!(comps[0].text(), "data" | "future" | "input" | "rego") {
|
||||
return Err(self.source.error(
|
||||
comps[0].line,
|
||||
comps[0].col,
|
||||
"import path must begin with one of: {data, future, input}",
|
||||
"import path must begin with one of: {data, future, input, rego}",
|
||||
));
|
||||
}
|
||||
|
||||
let is_future_kw = self.handle_import_future_keywords(&comps)?;
|
||||
let is_future_kw =
|
||||
if comps.len() == 2 && comps[0].text() == "rego" && comps[1].text() == "v1" {
|
||||
self.rego_v1 = true;
|
||||
for kw in FUTURE_KEYWORDS {
|
||||
self.set_future_keyword(kw, &span)?;
|
||||
}
|
||||
true
|
||||
} else {
|
||||
self.handle_import_future_keywords(&comps)?
|
||||
};
|
||||
|
||||
let var = if self.token_text() == "as" {
|
||||
if is_future_kw {
|
||||
@@ -1588,6 +1660,7 @@ impl<'source> Parser<'source> {
|
||||
package,
|
||||
imports,
|
||||
policy,
|
||||
rego_v1: self.rego_v1,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::ast::Expr::*;
|
||||
use crate::ast::*;
|
||||
use crate::builtins;
|
||||
use crate::lexer::*;
|
||||
use crate::utils::*;
|
||||
|
||||
@@ -381,7 +380,7 @@ pub struct Analyzer {
|
||||
current_module_path: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Schedule {
|
||||
pub scopes: BTreeMap<Ref<Query>, Scope>,
|
||||
pub order: BTreeMap<Ref<Query>, Vec<u16>>,
|
||||
@@ -629,6 +628,7 @@ impl Analyzer {
|
||||
let mut used_vars = vec![];
|
||||
let mut comprs = vec![];
|
||||
let full_expr = expr;
|
||||
std::convert::identity(&full_expr);
|
||||
traverse(expr, &mut |e| match e.as_ref() {
|
||||
Var(v) if !matches!(v.text(), "_" | "input" | "data") => {
|
||||
let name = v.source_str();
|
||||
@@ -645,15 +645,18 @@ impl Analyzer {
|
||||
first_use.entry(name).or_insert(v.clone());
|
||||
}
|
||||
} else if !scope.inputs.contains(&name) {
|
||||
match get_path_string(full_expr, None) {
|
||||
Ok(path)
|
||||
if builtins::BUILTINS.contains_key(path.as_str())
|
||||
|| builtins::deprecated::DEPRECATED.contains_key(path.as_str()) => {
|
||||
#[cfg(feature = "deprecated")]
|
||||
{
|
||||
if let Ok(path) = get_path_string(full_expr, None) {
|
||||
if crate::builtins::BUILTINS.contains_key(path.as_str())
|
||||
|| crate::builtins::deprecated::DEPRECATED
|
||||
.contains_key(path.as_str())
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
_ => bail!(v.error(
|
||||
format!("use of undefined variable `{name}` is unsafe").as_str()
|
||||
)),
|
||||
}
|
||||
bail!(v.error(format!("use of undefined variable `{name}` is unsafe").as_str()));
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ fn match_values(computed: &Value, expected: &Value) -> Result<()> {
|
||||
|
||||
pub fn check_output(computed_results: &[Value], expected_results: &[Value]) -> Result<()> {
|
||||
if computed_results.len() != expected_results.len() {
|
||||
dbg!((&computed_results, &expected_results));
|
||||
bail!(
|
||||
"the number of computed results ({}) and expected results ({}) is not equal",
|
||||
computed_results.len(),
|
||||
@@ -144,6 +143,9 @@ pub fn eval_file(
|
||||
let mut engine: Engine = Engine::new();
|
||||
engine.set_strict_builtin_errors(strict);
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
let mut results = vec![];
|
||||
let mut files = vec![];
|
||||
|
||||
@@ -160,31 +162,48 @@ pub fn eval_file(
|
||||
engine.add_data(data)?;
|
||||
}
|
||||
|
||||
if let Some(input) = input_opt {
|
||||
// all modules are evaluated for each input
|
||||
let mut inputs = vec![];
|
||||
match input {
|
||||
ValueOrVec::Single(single_input) => inputs.push(single_input),
|
||||
ValueOrVec::Many(mut many_input) => inputs.append(&mut many_input),
|
||||
let mut inputs = vec![];
|
||||
match input_opt {
|
||||
Some(ValueOrVec::Single(single_input)) => inputs.push(single_input),
|
||||
Some(ValueOrVec::Many(mut many_input)) => inputs.append(&mut many_input),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let mut engine_full = engine.clone();
|
||||
|
||||
if inputs.is_empty() {
|
||||
// Now eval the query.
|
||||
let r = engine.eval_query(query.to_string(), enable_tracing)?;
|
||||
let r_full = engine_full.eval_query_and_all_rules(query.to_string(), enable_tracing)?;
|
||||
if r != r_full {
|
||||
println!(
|
||||
"{}\n{}",
|
||||
serde_json::to_string_pretty(&r_full)?,
|
||||
serde_json::to_string_pretty(&r)?
|
||||
);
|
||||
assert_eq!(r_full, r);
|
||||
}
|
||||
|
||||
push_query_results(r, &mut results);
|
||||
} else {
|
||||
for input in inputs {
|
||||
engine.set_input(input);
|
||||
engine.eval_modules(enable_tracing)?;
|
||||
engine.set_input(input.clone());
|
||||
engine_full.set_input(input);
|
||||
|
||||
// Now eval the query.
|
||||
push_query_results(
|
||||
engine.eval_query(query.to_string(), enable_tracing)?,
|
||||
&mut results,
|
||||
);
|
||||
let r = engine.eval_query(query.to_string(), enable_tracing)?;
|
||||
let r_full = engine_full.eval_query_and_all_rules(query.to_string(), enable_tracing)?;
|
||||
if r != r_full {
|
||||
println!(
|
||||
"{}\n{}",
|
||||
serde_json::to_string_pretty(&r_full)?,
|
||||
serde_json::to_string_pretty(&r)?
|
||||
);
|
||||
assert_eq!(r_full, r);
|
||||
}
|
||||
|
||||
push_query_results(r, &mut results);
|
||||
}
|
||||
} else {
|
||||
// it no input is defined then one evaluation of all modules is performed
|
||||
// Now eval the query.
|
||||
push_query_results(
|
||||
engine.eval_query(query.to_string(), enable_tracing)?,
|
||||
&mut results,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
@@ -235,6 +254,7 @@ struct TestCase {
|
||||
query: String,
|
||||
sort_bindings: Option<bool>,
|
||||
want_result: Option<ValueOrVec>,
|
||||
no_result: Option<bool>,
|
||||
skip: Option<bool>,
|
||||
error: Option<String>,
|
||||
traces: Option<bool>,
|
||||
@@ -268,7 +288,10 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
|
||||
match (&case.want_result, &case.error) {
|
||||
(Some(_), None) | (None, Some(_)) => (),
|
||||
_ => panic!("either want_result or error must be specified in test case."),
|
||||
_ if case.no_result != Some(true) => {
|
||||
panic!("either want_result, error or no_result must be specified in test case.")
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let enable_tracing = case.traces.is_some() && case.traces.unwrap();
|
||||
@@ -293,6 +316,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
|
||||
check_output(&results, &expected_results)?;
|
||||
}
|
||||
_ if case.no_result == Some(true) => (),
|
||||
_ => bail!("eval succeeded and did not produce any errors"),
|
||||
},
|
||||
Err(actual) => match &case.error {
|
||||
|
||||
16
src/utils.rs
16
src/utils.rs
@@ -109,7 +109,7 @@ pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result<String> {
|
||||
Ok(comps.join("."))
|
||||
}
|
||||
|
||||
pub type FunctionTable = BTreeMap<String, (Vec<Ref<Rule>>, u8)>;
|
||||
pub type FunctionTable = BTreeMap<String, (Vec<Ref<Rule>>, u8, Ref<Module>)>;
|
||||
|
||||
fn get_extra_arg_impl(
|
||||
expr: &Expr,
|
||||
@@ -118,11 +118,11 @@ fn get_extra_arg_impl(
|
||||
) -> Result<Option<Ref<Expr>>> {
|
||||
if let Expr::Call { fcn, params, .. } = expr {
|
||||
let full_path = get_path_string(fcn, module)?;
|
||||
let n_args = if let Some((_, n_args)) = functions.get(&full_path) {
|
||||
let n_args = if let Some((_, n_args, _)) = functions.get(&full_path) {
|
||||
*n_args
|
||||
} else {
|
||||
let path = get_path_string(fcn, None)?;
|
||||
if let Some((_, n_args)) = functions.get(&path) {
|
||||
if let Some((_, n_args, _)) = functions.get(&path) {
|
||||
*n_args
|
||||
} else if let Some((_, n_args)) = BUILTINS.get(path.as_str()) {
|
||||
*n_args
|
||||
@@ -169,7 +169,7 @@ pub fn gather_functions(modules: &[Ref<Module>]) -> Result<FunctionTable> {
|
||||
{
|
||||
let full_path = get_path_string(refr, Some(module_path.as_str()))?;
|
||||
|
||||
if let Some((functions, arity)) = table.get_mut(&full_path) {
|
||||
if let Some((functions, arity, _)) = table.get_mut(&full_path) {
|
||||
if args.len() as u8 != *arity {
|
||||
bail!(span.error(
|
||||
format!("{full_path} was previously defined with {arity} arguments.")
|
||||
@@ -178,7 +178,10 @@ pub fn gather_functions(modules: &[Ref<Module>]) -> Result<FunctionTable> {
|
||||
}
|
||||
functions.push(rule.clone());
|
||||
} else {
|
||||
table.insert(full_path, (vec![rule.clone()], args.len() as u8));
|
||||
table.insert(
|
||||
full_path,
|
||||
(vec![rule.clone()], args.len() as u8, module.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,11 +190,12 @@ pub fn gather_functions(modules: &[Ref<Module>]) -> Result<FunctionTable> {
|
||||
}
|
||||
|
||||
pub fn get_root_var(mut expr: &Expr) -> Result<SourceStr> {
|
||||
let empty = expr.span().source_str().clone_empty();
|
||||
loop {
|
||||
match expr {
|
||||
Expr::Var(v) => return Ok(v.source_str()),
|
||||
Expr::RefDot { refr, .. } | Expr::RefBrack { refr, .. } => expr = refr,
|
||||
_ => bail!("internal error: analyzer: could not get rule prefix"),
|
||||
_ => return Ok(empty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
807
src/value.rs
807
src/value.rs
File diff suppressed because it is too large
Load Diff
@@ -41,11 +41,16 @@ fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine_full = engine.clone();
|
||||
let query_results = engine.eval_query(case.query.clone(), true)?;
|
||||
|
||||
// Ensure that full evaluation produces the same results.
|
||||
let query_results_full = engine_full.eval_query_and_all_rules(case.query.clone(), true)?;
|
||||
assert_eq!(query_results, query_results_full);
|
||||
|
||||
let mut values = vec![];
|
||||
for qr in query_results.result {
|
||||
values.push(if !qr.bindings.is_empty_object() {
|
||||
values.push(if !qr.bindings.as_object()?.is_empty() {
|
||||
qr.bindings.clone()
|
||||
} else if let Some(v) = qr.expressions.last() {
|
||||
v.value.clone()
|
||||
@@ -53,7 +58,7 @@ fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> {
|
||||
Value::Undefined
|
||||
});
|
||||
}
|
||||
let result = Value::from_array(values);
|
||||
let result = Value::from(values);
|
||||
// Make result json compatible. (E.g: avoid sets).
|
||||
Value::from_json_str(&result.to_string())
|
||||
}
|
||||
@@ -108,6 +113,55 @@ fn run_aci_tests(dir: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
fn run_aci_tests_coverage(dir: &Path) -> Result<()> {
|
||||
let mut engine = Engine::new();
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
let mut added = std::collections::BTreeSet::new();
|
||||
|
||||
for entry in WalkDir::new(dir)
|
||||
.sort_by_file_name()
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
{
|
||||
let path = entry.path();
|
||||
if !path.to_string_lossy().ends_with(".yaml") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let yaml = std::fs::read(&path)?;
|
||||
let yaml = String::from_utf8_lossy(&yaml);
|
||||
let test: YamlTest = serde_yaml::from_str(&yaml)?;
|
||||
|
||||
for case in &test.cases {
|
||||
for (idx, rego) in case.modules.iter().enumerate() {
|
||||
if rego.ends_with(".rego") {
|
||||
let path = dir.join(rego);
|
||||
let path = path.to_str().expect("not a valid path");
|
||||
let path = path.to_string();
|
||||
if !added.contains(&path) {
|
||||
engine.add_policy_from_file(path.to_string())?;
|
||||
added.insert(path);
|
||||
}
|
||||
} else {
|
||||
engine.add_policy(format!("rego{idx}.rego"), rego.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
engine.clear_data();
|
||||
engine.add_data(case.data.clone())?;
|
||||
engine.set_input(case.input.clone());
|
||||
let _query_results = engine.eval_query(case.query.clone(), true)?;
|
||||
}
|
||||
}
|
||||
|
||||
let report = engine.get_coverage_report()?;
|
||||
println!("{}", report.to_colored_string()?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(clap::Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
@@ -119,5 +173,9 @@ struct Cli {
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
run_aci_tests_coverage(&Path::new(&cli.test_dir))?;
|
||||
|
||||
run_aci_tests(&Path::new(&cli.test_dir))
|
||||
}
|
||||
|
||||
33
tests/arc.rs
Normal file
33
tests/arc.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use regorus::*;
|
||||
|
||||
// Ensure that types can be s
|
||||
lazy_static! {
|
||||
static ref VALUE: Value = Value::Null;
|
||||
static ref ENGINE: Mutex<Engine> = Mutex::new(Engine::new());
|
||||
// static ref ENGINE: Engine = Engine::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_engine() -> anyhow::Result<()> {
|
||||
let e_guard = ENGINE.lock();
|
||||
let mut engine = e_guard.expect("failed to lock engine");
|
||||
|
||||
engine.add_policy(
|
||||
"hello.rego".to_string(),
|
||||
r#"
|
||||
package test
|
||||
allow = true
|
||||
"#
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
let results = engine.eval_query("data.test.allow".to_string(), false)?;
|
||||
assert_eq!(results.result[0].expressions[0].value, Value::from(true));
|
||||
Ok(())
|
||||
}
|
||||
89
tests/coverage/mod.rs
Normal file
89
tests/coverage/mod.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use regorus::*;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_generator::test_resources;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct File {
|
||||
covered: BTreeSet<u32>,
|
||||
not_covered: BTreeSet<u32>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct TestCase {
|
||||
data: Option<Value>,
|
||||
input: Option<Value>,
|
||||
modules: Vec<String>,
|
||||
note: String,
|
||||
query: String,
|
||||
skip: Option<bool>,
|
||||
report: Vec<File>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct YamlTest {
|
||||
cases: Vec<TestCase>,
|
||||
}
|
||||
|
||||
fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
let yaml_str = std::fs::read_to_string(file)?;
|
||||
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
||||
|
||||
println!("running {file}");
|
||||
|
||||
for case in test.cases.into_iter() {
|
||||
print!("case {} ", case.note);
|
||||
if case.skip == Some(true) {
|
||||
println!("skipped");
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
for (idx, rego) in case.modules.iter().enumerate() {
|
||||
engine.add_policy(format!("rego_{idx}"), rego.clone())?;
|
||||
}
|
||||
|
||||
if let Some(data) = case.data {
|
||||
engine.add_data(data)?;
|
||||
}
|
||||
|
||||
if let Some(input) = case.input {
|
||||
engine.set_input(input);
|
||||
}
|
||||
|
||||
let _ = engine.eval_query(case.query.clone(), false)?;
|
||||
let report = engine.get_coverage_report()?;
|
||||
|
||||
for (idx, file) in case.report.into_iter().enumerate() {
|
||||
assert_eq!(file.not_covered, report.files[idx].not_covered);
|
||||
assert_eq!(file.covered, report.files[idx].covered);
|
||||
}
|
||||
|
||||
println!("passed");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn yaml_test(file: &str) -> Result<()> {
|
||||
match yaml_test_impl(file) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
// If Err is returned, it doesn't always get printed by cargo test.
|
||||
// Therefore, panic with the error.
|
||||
panic!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test_resources("tests/coverage/*.yaml")]
|
||||
fn run(path: &str) {
|
||||
yaml_test(path).unwrap()
|
||||
}
|
||||
21
tests/coverage/tests.yaml
Normal file
21
tests/coverage/tests.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: basic
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x = 1
|
||||
|
||||
y = k {
|
||||
input.x == 5
|
||||
k = input.k
|
||||
}
|
||||
query: data.test
|
||||
report:
|
||||
- covered: [3, 6]
|
||||
not_covered: [5, 7]
|
||||
|
||||
|
||||
103
tests/engine/mod.rs
Normal file
103
tests/engine/mod.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use regorus::*;
|
||||
|
||||
#[test]
|
||||
fn extension() -> Result<()> {
|
||||
fn repeat(mut params: Vec<Value>) -> Result<Value> {
|
||||
match params.remove(0) {
|
||||
Value::String(s) => {
|
||||
let s = s.as_ref().to_owned();
|
||||
Ok(Value::from(s.clone() + &s))
|
||||
}
|
||||
_ => bail!("param must be string"),
|
||||
}
|
||||
}
|
||||
let mut engine = Engine::new();
|
||||
engine.add_policy(
|
||||
"test.rego".to_string(),
|
||||
r#"package test
|
||||
x = repeat("hello")
|
||||
"#
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
// Raises error since repeat is not defined.
|
||||
assert!(engine.eval_query("data.test.x".to_string(), false).is_err());
|
||||
|
||||
// Register extension.
|
||||
engine.add_extension("repeat".to_string(), 1, Box::new(repeat))?;
|
||||
|
||||
// Adding extension twice is error.
|
||||
assert!(engine
|
||||
.add_extension(
|
||||
"repeat".to_string(),
|
||||
1,
|
||||
Box::new(|_| { Ok(Value::Undefined) })
|
||||
)
|
||||
.is_err());
|
||||
|
||||
let r = engine.eval_query("data.test.x".to_string(), false)?;
|
||||
assert_eq!(
|
||||
r.result[0].expressions[0].value.as_string()?.as_ref(),
|
||||
"hellohello"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_with_state() -> Result<()> {
|
||||
#[derive(Clone)]
|
||||
struct Gen {
|
||||
n: i64,
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.add_policy(
|
||||
"test.rego".to_string(),
|
||||
r#"package test
|
||||
x = gen()
|
||||
"#
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
let mut g = Box::new(Gen { n: 5 });
|
||||
engine.add_extension(
|
||||
"gen".to_string(),
|
||||
0,
|
||||
Box::new(move |_: Vec<Value>| {
|
||||
let v = Value::from(g.n);
|
||||
g.n += 1;
|
||||
Ok(v)
|
||||
}),
|
||||
)?;
|
||||
|
||||
// First eval.
|
||||
let r = engine.eval_query("data.test.x".to_string(), false)?;
|
||||
assert_eq!(r.result[0].expressions[0].value.as_i64()?, 5);
|
||||
|
||||
// Second eval will produce a new value since for each query, the
|
||||
// internal evaluation state of the interpreter is cleared.
|
||||
// This might change in the future.
|
||||
let r = engine.eval_query("data.test.x".to_string(), false)?;
|
||||
assert_eq!(r.result[0].expressions[0].value.as_i64()?, 6);
|
||||
|
||||
// Clone the engine.
|
||||
// This should also clone the stateful extension.
|
||||
let mut engine1 = engine.clone();
|
||||
|
||||
// Both the engines should produce the same value.
|
||||
let r = engine.eval_query("data.test.x".to_string(), false)?;
|
||||
let r1 = engine1.eval_query("data.test.x".to_string(), false)?;
|
||||
assert_eq!(
|
||||
r.result[0].expressions[0].value,
|
||||
r1.result[0].expressions[0].value
|
||||
);
|
||||
|
||||
assert_eq!(r.result[0].expressions[0].value.as_i64()?, 7);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -53,6 +53,23 @@ cases:
|
||||
- 58
|
||||
- 45
|
||||
|
||||
- note: leap-year
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.diff(time.parse_ns("2006-01-02", "2020-02-02"), time.parse_ns("2006-01-02", "2020-03-01"))
|
||||
query: data.test
|
||||
want_result:
|
||||
a:
|
||||
- 0
|
||||
- 0
|
||||
- 28
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
|
||||
- note: invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
|
||||
@@ -34,8 +34,8 @@ cases:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.format([1703444325734390000, "UTC", "%Y-%m-%dT%H:%M:%S"])
|
||||
b := time.format([1257894000000000000, "", "%d/%m/%Y %H:%M"])
|
||||
a := time.format([1703444325734390000, "UTC", "2006-01-02T15:04:05"])
|
||||
b := time.format([1257894000000000000, "", "02/01/2006 15:04"])
|
||||
query: data.test
|
||||
want_result:
|
||||
a: "2023-12-24T18:58:45"
|
||||
|
||||
@@ -8,11 +8,11 @@ cases:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.parse_ns("%Y-%m-%dT%H:%M:%S", "2006-01-02T15:04:05")
|
||||
b := time.parse_ns("%Y-%m-%d %H:%M:%S", "2015-09-05 23:56:04")
|
||||
a := time.parse_ns("2006-01-02T15:04:05", "2016-05-10T19:06:42")
|
||||
b := time.parse_ns("2006-01-02 15:04:05", "2015-09-05 23:56:04")
|
||||
query: data.test
|
||||
want_result:
|
||||
a: 1136214245000000000
|
||||
a: 1462907202000000000
|
||||
b: 1441497364000000000
|
||||
|
||||
- note: format-and-parse-back
|
||||
@@ -22,8 +22,8 @@ cases:
|
||||
package test
|
||||
|
||||
a := res {
|
||||
date := time.format([1703444325734390000, "UTC", "%Y-%m-%dT%H:%M:%S%.f"])
|
||||
res := time.parse_ns("%Y-%m-%dT%H:%M:%S%.f", date)
|
||||
date := time.format([1703444325734390000, "UTC", "2006-01-02T15:04:05.999999999"])
|
||||
res := time.parse_ns("2006-01-02T15:04:05.999999999", date)
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
@@ -35,6 +35,6 @@ cases:
|
||||
- |
|
||||
package test
|
||||
|
||||
a := time.parse_ns("%Y-%m-%dT%H:%M:%S%.f", 1703444325734390000)
|
||||
a := time.parse_ns("2006-01-02T15:04:05.999999999", 1703444325734390000)
|
||||
query: data.test
|
||||
error: '`time.parse_ns` expects string argument. Got `1703444325734390000` instead'
|
||||
|
||||
@@ -71,3 +71,11 @@ cases:
|
||||
import foo
|
||||
query: data
|
||||
error: "import path must begin with one of"
|
||||
|
||||
- note: redundant import input
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import input
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
54
tests/interpreter/cases/nonstrict/tests.yaml
Normal file
54
tests/interpreter/cases/nonstrict/tests.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: builtin error gobbled up in non strict mode
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
a = to_number("abc")
|
||||
query: data.test
|
||||
want_result: {}
|
||||
strict: false
|
||||
|
||||
- note: builtin error in strict mode
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
a = to_number("abc")
|
||||
query: data.test
|
||||
error: "could not parse string as number"
|
||||
|
||||
- note: count of null error gobbled up in non strict mode
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
foo := input.a
|
||||
|
||||
some_id := {
|
||||
"count_value": count(foo) > 2,
|
||||
}
|
||||
input:
|
||||
a: null
|
||||
query: data.test
|
||||
strict: false
|
||||
want_result:
|
||||
foo: null
|
||||
|
||||
- note: count of null error in strict mode
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
foo := input.a
|
||||
|
||||
some_id := {
|
||||
"count_value": count(foo) > 2,
|
||||
(count(foo) > 2): "count_value",
|
||||
}
|
||||
input:
|
||||
a: null
|
||||
query: data.test
|
||||
error: "`count` requires array/object/set/string argument"
|
||||
26
tests/interpreter/cases/query/tests.yaml
Normal file
26
tests/interpreter/cases/query/tests.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: single-expression query producing false
|
||||
data: {}
|
||||
modules: []
|
||||
query: 1 == 2
|
||||
want_result: false
|
||||
|
||||
- note: single-expression query producing no results (due to undefined)
|
||||
data: {}
|
||||
modules: []
|
||||
query: 1 = 2
|
||||
no_result: true
|
||||
|
||||
- note: multi-expression query in which one expression is false (1)
|
||||
data: {}
|
||||
modules: []
|
||||
query: "1 == 1; 1 == 2"
|
||||
no_result: true
|
||||
|
||||
- note: multi-expression query in which one expression is false (2)
|
||||
data: {}
|
||||
modules: []
|
||||
query: "1 == 2; 1 == 1"
|
||||
no_result: true
|
||||
14
tests/interpreter/cases/refr/tests.yaml
Normal file
14
tests/interpreter/cases/refr/tests.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: ref
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = [k |
|
||||
[1, 2, 3][[1, 2, 3][k]]
|
||||
]
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
x: [0, 1]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user