mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
13 Commits
v0.1.0-alp
...
regorus-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ab27b253b | ||
|
|
761d11ef48 | ||
|
|
beea2274d3 | ||
|
|
5799a3e6c4 | ||
|
|
ca91c0ea20 | ||
|
|
bf75813c43 | ||
|
|
35ec9c03ad | ||
|
|
8ca863c661 | ||
|
|
055bdd295f | ||
|
|
0af97840f7 | ||
|
|
6eca85b497 | ||
|
|
f3884e87e5 | ||
|
|
d39200a52c |
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 }}
|
||||
|
||||
16
.github/workflows/rust.yml
vendored
16
.github/workflows/rust.yml
vendored
@@ -27,11 +27,21 @@ jobs:
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
run: cargo test -r --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
|
||||
run: cargo test -r --verbose --target x86_64-unknown-linux-musl
|
||||
- name: Run tests (ACI)
|
||||
run: cargo test -r --test aci
|
||||
- name: Run tests (OPA Conformance)
|
||||
run: >-
|
||||
cargo test --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
cargo test -r --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Run wasm binding tests
|
||||
run: |
|
||||
cd bindings/wasm
|
||||
wasm-pack test --node -r
|
||||
|
||||
130
CHANGELOG.md
130
CHANGELOG.md
@@ -6,6 +6,136 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"bindings/python",
|
||||
"bindings/wasm"
|
||||
]
|
||||
|
||||
[package]
|
||||
name = "regorus"
|
||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||
version = "0.1.0-alpha.1"
|
||||
version = "0.1.0-alpha.3"
|
||||
edition = "2021"
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
|
||||
60
README.md
60
README.md
@@ -2,11 +2,14 @@
|
||||
|
||||
**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 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;
|
||||
use regorus::*;
|
||||
@@ -21,11 +24,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 +39,39 @@ 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
|
||||
$ 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:
|
||||
|
||||
- Javascript: To compile Regorus to WASM and use it in Javascript, see [bindings/wasm](bindings/wasm)
|
||||
- Python: To use Regorus from Python, see [bindings/python](bindings/python)
|
||||
|
||||
## 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 +124,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](examples/example.rego) and [input](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
|
||||
@@ -168,7 +199,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 +209,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`
|
||||
@@ -219,7 +251,9 @@ They are captured in the following [github issues](https://github.com/microsoft/
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
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
|
||||
\\ }
|
||||
\\ }
|
||||
\\ ]
|
||||
\\ }
|
||||
\\ ]
|
||||
\\}
|
||||
```
|
||||
144
bindings/wasm/src/lib.rs
Normal file
144
bindings/wasm/src/lib.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
// 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.
|
||||
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) | _ |
|
||||
@@ -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
|
||||
}
|
||||
@@ -353,6 +353,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>;
|
||||
|
||||
@@ -28,11 +28,14 @@ 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());
|
||||
if !msg.is_empty() {
|
||||
println!("{}", &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(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
))
|
||||
}
|
||||
|
||||
220
src/engine.rs
220
src/engine.rs
@@ -8,12 +8,15 @@ use crate::parser::*;
|
||||
use crate::scheduler::*;
|
||||
use crate::utils::gather_functions;
|
||||
use crate::value::*;
|
||||
use crate::QueryResults;
|
||||
|
||||
use std::convert::AsRef;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
/// The Rego evaluation engine.
|
||||
///
|
||||
#[derive(Clone)]
|
||||
pub struct Engine {
|
||||
modules: Vec<Ref<Module>>,
|
||||
@@ -21,6 +24,7 @@ pub struct Engine {
|
||||
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,166 @@ 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);
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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!(results.result.is_empty());
|
||||
///
|
||||
/// // 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.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 +289,7 @@ impl Engine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn eval_rule(
|
||||
&mut self,
|
||||
module: &Ref<Module>,
|
||||
@@ -121,6 +304,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();
|
||||
@@ -164,30 +348,4 @@ impl Engine {
|
||||
self.interpreter.create_rule_prefixes()?;
|
||||
Ok(self.interpreter.get_data_mut().clone())
|
||||
}
|
||||
|
||||
pub fn eval_query(&mut self, query: String, enable_tracing: bool) -> Result<QueryResults> {
|
||||
self.eval_modules(false)?;
|
||||
|
||||
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)?;
|
||||
|
||||
let results = self.interpreter.eval_user_query(
|
||||
&query_module,
|
||||
&query_node,
|
||||
&query_schedule,
|
||||
enable_tracing,
|
||||
)?;
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ use crate::parser::Parser;
|
||||
use crate::scheduler::*;
|
||||
use crate::utils::*;
|
||||
use crate::value::*;
|
||||
use crate::{Expression, Location, QueryResult, QueryResults};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use log::info;
|
||||
use serde::Serialize;
|
||||
use std::collections::btree_map::Entry as BTreeMapEntry;
|
||||
use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap};
|
||||
use std::ops::Bound::*;
|
||||
@@ -73,42 +73,6 @@ impl Default for Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Location {
|
||||
pub row: u16,
|
||||
pub col: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Expression {
|
||||
pub value: Value,
|
||||
pub text: Rc<str>,
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct QueryResult {
|
||||
// Expressions is shown first to match OPA.
|
||||
pub expressions: Vec<Expression>,
|
||||
#[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![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
pub struct QueryResults {
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub result: Vec<QueryResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Context {
|
||||
key_expr: Option<ExprRef>,
|
||||
@@ -271,6 +235,7 @@ impl Interpreter {
|
||||
self.loop_var_values.clear();
|
||||
self.scopes = vec![Scope::new()];
|
||||
self.contexts = vec![];
|
||||
self.rule_values.clear();
|
||||
}
|
||||
|
||||
fn current_module(&self) -> Result<Ref<Module>> {
|
||||
@@ -383,7 +348,7 @@ impl Interpreter {
|
||||
&& get_root_var(refr)?.text() == "data"
|
||||
{
|
||||
let index = index.to_string();
|
||||
v = obj[&index].clone();
|
||||
v = obj[index].clone();
|
||||
}
|
||||
return Ok(Self::get_value_chained(v, &path[..]));
|
||||
}
|
||||
@@ -1346,6 +1311,13 @@ impl Interpreter {
|
||||
r
|
||||
}
|
||||
|
||||
fn clear_scope(scope: &mut Scope) {
|
||||
// Set each value to undefined. This is equivalent to removing the key.
|
||||
for (_, v) in scope.iter_mut() {
|
||||
*v = Value::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_stmts_in_loop(&mut self, stmts: &[&LiteralStmt], loops: &[LoopExpr]) -> Result<bool> {
|
||||
if loops.is_empty() {
|
||||
if !stmts.is_empty() {
|
||||
@@ -1409,9 +1381,8 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
// Save the current scope and restore it after evaluating the statements so
|
||||
// that the effects of the current loop iteration are cleared.
|
||||
let scope_saved = self.current_scope()?.clone();
|
||||
// Create a new scope.
|
||||
self.scopes.push(Scope::default());
|
||||
|
||||
let query_result = self.get_current_context()?.result.clone();
|
||||
match loop_expr_value {
|
||||
@@ -1437,12 +1408,13 @@ impl Interpreter {
|
||||
result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result;
|
||||
}
|
||||
|
||||
self.loop_var_values.remove(&loop_expr.expr());
|
||||
*self.current_scope_mut()? = scope_saved.clone();
|
||||
Self::clear_scope(self.current_scope_mut()?);
|
||||
if let Some(ctx) = self.contexts.last_mut() {
|
||||
ctx.result = query_result.clone();
|
||||
}
|
||||
}
|
||||
|
||||
self.loop_var_values.remove(&loop_expr.expr());
|
||||
}
|
||||
Value::Set(items) => {
|
||||
for v in items.iter() {
|
||||
@@ -1460,12 +1432,12 @@ impl Interpreter {
|
||||
result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result;
|
||||
}
|
||||
|
||||
self.loop_var_values.remove(&loop_expr.expr());
|
||||
*self.current_scope_mut()? = scope_saved.clone();
|
||||
Self::clear_scope(self.current_scope_mut()?);
|
||||
if let Some(ctx) = self.contexts.last_mut() {
|
||||
ctx.result = query_result.clone();
|
||||
}
|
||||
}
|
||||
self.loop_var_values.remove(&loop_expr.expr());
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
for (k, v) in obj.iter() {
|
||||
@@ -1481,12 +1453,13 @@ impl Interpreter {
|
||||
if exec {
|
||||
result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result;
|
||||
}
|
||||
self.loop_var_values.remove(&loop_expr.expr());
|
||||
*self.current_scope_mut()? = scope_saved.clone();
|
||||
|
||||
Self::clear_scope(self.current_scope_mut()?);
|
||||
if let Some(ctx) = self.contexts.last_mut() {
|
||||
ctx.result = query_result.clone();
|
||||
}
|
||||
}
|
||||
self.loop_var_values.remove(&loop_expr.expr());
|
||||
}
|
||||
Value::Undefined => {
|
||||
result = false;
|
||||
@@ -1497,6 +1470,8 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
self.scopes.pop();
|
||||
|
||||
// Return true if at least on iteration returned true
|
||||
Ok(result)
|
||||
}
|
||||
@@ -1728,7 +1703,7 @@ impl Interpreter {
|
||||
if result
|
||||
.expressions
|
||||
.iter()
|
||||
.all(|v| v.value != Value::Undefined)
|
||||
.all(|v| v.value != Value::Undefined && v.value != Value::Bool(false))
|
||||
&& !result.expressions.is_empty()
|
||||
{
|
||||
ctx.results.result.push(result);
|
||||
@@ -1847,7 +1822,7 @@ impl Interpreter {
|
||||
if result
|
||||
.expressions
|
||||
.iter()
|
||||
.all(|v| v.value != Value::Undefined)
|
||||
.all(|v| v.value != Value::Undefined && v.value != Value::Bool(false))
|
||||
&& !result.expressions.is_empty()
|
||||
{
|
||||
ctx.results.result.push(result);
|
||||
@@ -2023,14 +1998,14 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_function_by_name(&self, path: &str) -> Option<&Vec<Ref<Rule>>> {
|
||||
fn lookup_function_by_name(&self, path: &str) -> Option<(&Vec<Ref<Rule>>, &Ref<Module>)> {
|
||||
let mut path = path.to_owned();
|
||||
if !path.starts_with("data.") {
|
||||
path = self.current_module_path.clone() + "." + &path;
|
||||
}
|
||||
|
||||
match self.functions.get(&path) {
|
||||
Some((f, _)) => Some(f),
|
||||
Some((f, _, m)) => Some((f, m)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -2063,10 +2038,12 @@ impl Interpreter {
|
||||
|
||||
// Handle trace function.
|
||||
// TODO: with modifier.
|
||||
if let (Some(traces), Value::String(msg)) = (&mut self.traces, &v) {
|
||||
traces.push(msg.clone());
|
||||
return Ok(Value::Bool(true));
|
||||
};
|
||||
if name == "trace" {
|
||||
if let (Some(traces), Value::String(msg)) = (&mut self.traces, &v) {
|
||||
traces.push(msg.clone());
|
||||
return Ok(Value::Bool(true));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(name) = cache {
|
||||
self.builtins_cache.insert((name, args), v.clone());
|
||||
@@ -2081,7 +2058,8 @@ impl Interpreter {
|
||||
|
||||
#[cfg(feature = "deprecated")]
|
||||
if let Some(builtin) = builtins::DEPRECATED.get(path) {
|
||||
if !self.allow_deprecated {
|
||||
let allow = self.allow_deprecated && !self.current_module()?.rego_v1;
|
||||
if !allow {
|
||||
bail!(span.error(format!("{path} is deprecated").as_str()))
|
||||
}
|
||||
return Ok(Some(builtin));
|
||||
@@ -2143,9 +2121,9 @@ impl Interpreter {
|
||||
_ => orig_fcn_path.clone(),
|
||||
};
|
||||
|
||||
let empty = vec![];
|
||||
let fcns_rules = match self.lookup_function_by_name(&fcn_path) {
|
||||
Some(r) => r,
|
||||
let empty: Vec<Ref<Rule>> = vec![];
|
||||
let (fcns_rules, fcn_module) = match self.lookup_function_by_name(&fcn_path) {
|
||||
Some((fcns, m)) => (fcns, Some(m.clone())),
|
||||
_ => {
|
||||
if self.default_rules.get(&fcn_path).is_some()
|
||||
|| self
|
||||
@@ -2154,10 +2132,10 @@ impl Interpreter {
|
||||
.is_some()
|
||||
{
|
||||
// process default functions later.
|
||||
&empty
|
||||
(&empty, self.module.clone())
|
||||
}
|
||||
// Look up builtin function.
|
||||
else if let Ok(Some(builtin)) = self.lookup_builtin(span, &fcn_path) {
|
||||
else if let Some(builtin) = self.lookup_builtin(span, &fcn_path)? {
|
||||
let r = self.eval_builtin_call(span, &fcn_path.clone(), *builtin, params);
|
||||
if let Some(with_functions) = with_functions_saved {
|
||||
self.with_functions = with_functions;
|
||||
@@ -2236,6 +2214,7 @@ impl Interpreter {
|
||||
..Context::default()
|
||||
};
|
||||
|
||||
let prev_module = self.set_current_module(fcn_module.clone())?;
|
||||
let value = match self.eval_rule_bodies(ctx, span, bodies) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
@@ -2245,6 +2224,7 @@ impl Interpreter {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
self.set_current_module(prev_module)?;
|
||||
|
||||
let result = match &value {
|
||||
Value::Set(s) if s.len() == 1 => s.iter().next().unwrap().clone(),
|
||||
@@ -2410,6 +2390,14 @@ impl Interpreter {
|
||||
self.eval_rule(&module, rule)?;
|
||||
}
|
||||
}
|
||||
|
||||
let prev_module = self.set_current_module(Some(module.clone()))?;
|
||||
for rule in &module.policy {
|
||||
if !self.processed.contains(rule) {
|
||||
self.eval_default_rule(rule)?;
|
||||
}
|
||||
}
|
||||
self.set_current_module(prev_module)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -3222,7 +3210,7 @@ impl Interpreter {
|
||||
self.set_current_module(prev_module)?;
|
||||
|
||||
if let Some(r) = results.result.last() {
|
||||
if r.bindings.is_empty_object()
|
||||
if matches!(&r.bindings, Value::Object(obj) if obj.is_empty())
|
||||
&& r.expressions.iter().any(|e| e.value == Value::Bool(false))
|
||||
{
|
||||
results = QueryResults::default();
|
||||
|
||||
244
src/lib.rs
244
src/lib.rs
@@ -4,6 +4,8 @@
|
||||
// Use README.md as crate documentation.
|
||||
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
mod ast;
|
||||
mod builtins;
|
||||
mod engine;
|
||||
@@ -16,10 +18,250 @@ mod utils;
|
||||
mod value;
|
||||
|
||||
pub use engine::Engine;
|
||||
pub use interpreter::{QueryResult, QueryResults};
|
||||
pub use value::Value;
|
||||
|
||||
/// 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)]
|
||||
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)]
|
||||
pub struct Expression {
|
||||
/// Computed value of the expression.
|
||||
pub value: Value,
|
||||
|
||||
/// The Rego expression.
|
||||
pub text: std::rc::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)]
|
||||
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 "true; true; false".
|
||||
/// let results = Engine::new().eval_query("1 + 1".to_string(), false)?;
|
||||
///
|
||||
/// assert!(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 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(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// 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)]
|
||||
pub struct QueryResults {
|
||||
/// Collection of results of evaluting a query.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub result: Vec<QueryResult>,
|
||||
}
|
||||
|
||||
/// Items in `unstable` are likely to change.
|
||||
#[doc(hidden)]
|
||||
pub mod unstable {
|
||||
pub use crate::ast::*;
|
||||
pub use crate::lexer::*;
|
||||
|
||||
@@ -132,6 +132,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;
|
||||
@@ -1463,6 +1492,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 +1574,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 +1646,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::*;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
13
src/utils.rs
13
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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
804
src/value.rs
804
src/value.rs
File diff suppressed because it is too large
Load Diff
@@ -45,7 +45,7 @@ fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> {
|
||||
|
||||
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 +53,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())
|
||||
}
|
||||
|
||||
481
tests/interpreter/cases/rego.v1/tests.yaml
Normal file
481
tests/interpreter/cases/rego.v1/tests.yaml
Normal file
@@ -0,0 +1,481 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: conflict future after rego.v1
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
import rego.v1
|
||||
import future.keywords.in
|
||||
|
||||
query: data
|
||||
error: "this import shadows previous import"
|
||||
|
||||
- note: conflict rego after future
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
import future.keywords.in
|
||||
import rego.v1
|
||||
|
||||
query: data
|
||||
error: "this import shadows previous import"
|
||||
|
||||
- note: conflict rego after rego
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
import rego.v1
|
||||
import rego.v1
|
||||
|
||||
query: data
|
||||
error: "this import shadows previous import"
|
||||
|
||||
- note: allowed future after future
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
import future.keywords
|
||||
import future.keywords
|
||||
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: if-required-before-body
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
allow {
|
||||
1 > 2
|
||||
}
|
||||
query: data
|
||||
error: "`if` keyword is required before rule body"
|
||||
|
||||
- note: ok-if-before-body
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
allow if {
|
||||
1 < 2
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
allow: true
|
||||
|
||||
- note: if-required-before-else-body
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
allow if {
|
||||
1 > 2
|
||||
} else = 5 {
|
||||
1 < 2
|
||||
}
|
||||
query: data
|
||||
error: "`if` keyword is required before rule body"
|
||||
|
||||
- note: ok-if-before-else-body
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
allow if {
|
||||
1 > 2
|
||||
} else = 5 if {
|
||||
1 < 2
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
allow: 5
|
||||
|
||||
# cases from https://www.openpolicyagent.org/docs/latest/opa-1/#backwards-compatibility-in-opa-v10
|
||||
- note: invalid1
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p { true }
|
||||
query: data
|
||||
error: "`if` keyword is required before rule body"
|
||||
|
||||
- note: invalid2
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a { true }
|
||||
query: data
|
||||
error: "`if` keyword is required before rule body"
|
||||
|
||||
- note: invalid3
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a.b { true }
|
||||
query: data
|
||||
error: "`if` keyword is required before rule body"
|
||||
|
||||
- note: valid1
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p if true
|
||||
query: data.test
|
||||
want_result:
|
||||
p: true
|
||||
|
||||
- note: valid2
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a if true
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
a: true
|
||||
|
||||
- note: valid3
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a.b if true
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
a:
|
||||
b: true
|
||||
|
||||
- note: valid4
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p contains "a"
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
set!: ["a"]
|
||||
|
||||
- note: valid5
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p := 1
|
||||
query: data.test
|
||||
want_result:
|
||||
p: 1
|
||||
|
||||
- note: valid6
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a := 1
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
a: 1
|
||||
|
||||
- note: valid6
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a.b := 1
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
a:
|
||||
b: 1
|
||||
|
||||
- note: invalid11
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p
|
||||
query: data.test
|
||||
error: rule must have a body
|
||||
|
||||
- note: invalid12
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a
|
||||
query: data.test
|
||||
error: "`contains` keyword is required for partial set rules"
|
||||
|
||||
- note: invalid13
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a.b
|
||||
query: data.test
|
||||
error: rule must have a body
|
||||
|
||||
- note: invalid21
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p { true }
|
||||
query: data.test
|
||||
error: "`if` keyword is required before rule body"
|
||||
|
||||
- note: valid21
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p if { true }
|
||||
query: data.test
|
||||
want_result:
|
||||
p: true
|
||||
|
||||
- note: invalid22
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a
|
||||
query: data.test
|
||||
error: "`contains` keyword is required for partial set rules"
|
||||
|
||||
- note: valid22
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p contains "a"
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
set!: ["a"]
|
||||
|
||||
- note: invalid23
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a { true }
|
||||
query: data.test
|
||||
error: "`if` keyword is required before rule body"
|
||||
|
||||
- note: valid22
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p contains "a" if { true }
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
set!: ["a"]
|
||||
|
||||
- note: invalid24
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a.b
|
||||
query: data.test
|
||||
error: "rule must have a body or assignment"
|
||||
|
||||
- note: valid22
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a.b := true
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
a:
|
||||
b: true
|
||||
|
||||
- note: invalid25
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a.b { true }
|
||||
query: data.test
|
||||
error: "`if` keyword is required before rule body"
|
||||
|
||||
- note: valid22
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
p.a.b if { true }
|
||||
query: data.test
|
||||
want_result:
|
||||
p:
|
||||
a:
|
||||
b: true
|
||||
|
||||
- note: data-shadowed-by-rule
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
data = 1
|
||||
query: data.test
|
||||
want_result:
|
||||
data: 1
|
||||
|
||||
- note: invalid-data-shadowed-by-rule
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
data = 1
|
||||
query: data.test
|
||||
error: data cannot be shadowed
|
||||
|
||||
- note: input-shadowed-by-rule
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
input = 1
|
||||
query: data.test
|
||||
want_result:
|
||||
input: 1
|
||||
|
||||
- note: invalid-input-shadowed-by-rule
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
input = 1
|
||||
query: data.test
|
||||
error: input cannot be shadowed
|
||||
|
||||
- note: input-shadowed-by-local-var
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x {
|
||||
input := 1
|
||||
input > 0
|
||||
}
|
||||
y {
|
||||
# This evaluates to false
|
||||
input = 1
|
||||
input > 0
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
x: true
|
||||
|
||||
- note: invalid-input-shadowed-by-local-var
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
x if {
|
||||
input := 1
|
||||
input > 0
|
||||
}
|
||||
query: data.test
|
||||
error: input cannot be shadowed
|
||||
|
||||
- note: data-shadowed-by-local-var
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x {
|
||||
data := 1
|
||||
data > 0
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
x: true
|
||||
|
||||
- note: invalid-data-shadowed-by-local-var
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
x if {
|
||||
data := 1
|
||||
data > 0
|
||||
}
|
||||
query: data.test
|
||||
error: data cannot be shadowed
|
||||
|
||||
- note: deprecated-function
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x {
|
||||
cast_array([1])
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
x: true
|
||||
|
||||
- note: invalid-deprecated-function
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
x if {
|
||||
cast_array([1])
|
||||
}
|
||||
query: data.test
|
||||
error: is deprecated
|
||||
10
tests/opa.rs
10
tests/opa.rs
@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
const OPA_REPO: &str = "https://github.com/open-policy-agent/opa";
|
||||
const OPA_BRANCH: &str = "v0.60.0";
|
||||
const OPA_BRANCH: &str = "v0.61.0";
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -84,7 +84,7 @@ fn eval_test_case(case: &TestCase) -> Result<Value> {
|
||||
|
||||
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() {
|
||||
if case.sort_bindings == Some(true) {
|
||||
let mut v = qr.bindings.clone();
|
||||
let bindings = v.as_object_mut()?;
|
||||
@@ -105,15 +105,15 @@ fn eval_test_case(case: &TestCase) -> Result<Value> {
|
||||
});
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
fn json_schema_tests_check(actual: &Value, expected: &Value) -> bool {
|
||||
// Fetch `x` binding.
|
||||
let actual = &actual[0][&Value::String("x".into())];
|
||||
let expected = &expected[0][&Value::String("x".into())];
|
||||
let actual = &actual[0]["x"];
|
||||
let expected = &expected[0]["x"];
|
||||
|
||||
match (actual, expected) {
|
||||
(Value::Array(actual), Value::Array(expected))
|
||||
|
||||
@@ -224,13 +224,13 @@ cases:
|
||||
rego: |
|
||||
package test
|
||||
import foo
|
||||
error: "import path must begin with one of: {data, future, input}"
|
||||
error: "import path must begin with one of: {data, future, input, rego}"
|
||||
|
||||
- note: invalid-beginning-1
|
||||
rego: |
|
||||
package test
|
||||
import foo.bar
|
||||
error: "import path must begin with one of: {data, future, input}"
|
||||
error: "import path must begin with one of: {data, future, input, rego}"
|
||||
|
||||
- note: missing-field-1
|
||||
rego: |
|
||||
|
||||
@@ -15,7 +15,7 @@ fn non_string_key() -> Result<()> {
|
||||
obj.as_object_mut()?
|
||||
.insert(Value::from(std::f64::consts::PI), Value::Null);
|
||||
obj.as_object_mut()?.insert(
|
||||
Value::from_array(vec![
|
||||
Value::from(vec![
|
||||
Value::Bool(true),
|
||||
Value::Null,
|
||||
Value::from(std::f64::consts::PI),
|
||||
@@ -115,14 +115,14 @@ fn value_as_index() -> Result<()> {
|
||||
fn string_as_index() -> Result<()> {
|
||||
let obj = Value::from_json_str(r#"{ "a" : 5, "b" : 6 }"#)?;
|
||||
assert_eq!(&obj["a"], &Value::from(5.0));
|
||||
assert_eq!(&obj[&"b".to_owned()], &Value::from(6.0));
|
||||
assert_eq!(&obj["b".to_owned()], &Value::from(6.0));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usize_as_index() -> Result<()> {
|
||||
assert_eq!(&Value::from_json_str("[1, 2, 3]")?[0], &Value::from(1.0));
|
||||
assert_eq!(&Value::from_json_str("[1, 2, 3]")?[5], &Value::Undefined);
|
||||
assert_eq!(&Value::from_json_str("[1, 2, 3]")?[0u64], &Value::from(1.0));
|
||||
assert_eq!(&Value::from_json_str("[1, 2, 3]")?[5u64], &Value::Undefined);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -135,9 +135,6 @@ fn api() -> Result<()> {
|
||||
assert_eq!(v["a"], Value::from(3.145));
|
||||
assert_eq!(v.as_object()?.len(), 1);
|
||||
|
||||
// Null
|
||||
assert!(Value::Null.is_null());
|
||||
|
||||
let v = Value::new_set();
|
||||
assert_eq!(v.as_set()?.len(), 0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user