mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
43 Commits
regorus-v0
...
regorus-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2901481c51 | ||
|
|
c963e477a3 | ||
|
|
cbd772623a | ||
|
|
a07beca983 | ||
|
|
a3edb6c88c | ||
|
|
a1777fb7d3 | ||
|
|
11aaa555aa | ||
|
|
f1580a55a3 | ||
|
|
6174af1781 | ||
|
|
5fa55d7274 | ||
|
|
748c11cfa1 | ||
|
|
fb035d3d93 | ||
|
|
ba3a128e84 | ||
|
|
d955ae10a5 | ||
|
|
cabd086619 | ||
|
|
4ec25f37a1 | ||
|
|
c281d28474 | ||
|
|
1bfe38f9af | ||
|
|
5bf7cd7cc8 | ||
|
|
c56da34843 | ||
|
|
61f82d1b34 | ||
|
|
00f45c70fe | ||
|
|
df73b20192 | ||
|
|
992b202f60 | ||
|
|
ce6ecd6fd6 | ||
|
|
37262ccf8f | ||
|
|
dcd040cf40 | ||
|
|
f0a3cf26a0 | ||
|
|
13d8289a58 | ||
|
|
d2b27ee512 | ||
|
|
adb9da0c0c | ||
|
|
dc0f48f6bc | ||
|
|
334db3d6ce | ||
|
|
7565ec3ecf | ||
|
|
8498274356 | ||
|
|
ecd341bbcc | ||
|
|
b6935d1add | ||
|
|
502b830c19 | ||
|
|
8003cfc5b7 | ||
|
|
a4a80d7fc6 | ||
|
|
af5071446b | ||
|
|
edd0ccca5b | ||
|
|
fec6f8f8b4 |
33
.github/workflows/pr-extensions.yml
vendored
Normal file
33
.github/workflows/pr-extensions.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: tests/release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build only std
|
||||
run: cargo build -r --example regorus --no-default-features --features "std,rego-extensions"
|
||||
- name: Doc Tests
|
||||
run: cargo test -r --doc --features rego-extensions
|
||||
- name: Run tests
|
||||
run: cargo test -r --features rego-extensions
|
||||
- name: Run example
|
||||
run: cargo run --example regorus --features rego-extensions -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
|
||||
- name: Run tests (ACI)
|
||||
run: cargo test -r --test aci --features rego-extensions
|
||||
- name: Run tests (KATA)
|
||||
run: cargo test -r --test kata --features rego-extensions
|
||||
- name: Run tests (OPA Conformance)
|
||||
run: >-
|
||||
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision,rego-extensions -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
2
.github/workflows/pr.yml
vendored
2
.github/workflows/pr.yml
vendored
@@ -30,6 +30,8 @@ jobs:
|
||||
run: cargo test -r --doc
|
||||
- name: Run tests
|
||||
run: cargo test -r
|
||||
- name: Run example
|
||||
run: cargo run --example regorus -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
|
||||
- name: Run tests (ACI)
|
||||
run: cargo test -r --test aci
|
||||
- name: Run tests (KATA)
|
||||
|
||||
6
.github/workflows/test-ruby.yml
vendored
6
.github/workflows/test-ruby.yml
vendored
@@ -18,7 +18,9 @@ jobs:
|
||||
- name: Setup Ruby and Rust
|
||||
uses: oxidize-rb/actions/setup-ruby-and-rust@7ca44a16e287e5ff7dd72ab53f4bd41cbf34a571 #v1.26
|
||||
with:
|
||||
ruby-version: "3.3.1"
|
||||
bundler: 2.6.5
|
||||
rubygems: 3.6.5
|
||||
ruby-version: "3.4.2"
|
||||
rustup-toolchain: "stable"
|
||||
bundler-cache: true
|
||||
cargo-cache: true
|
||||
@@ -27,5 +29,7 @@ jobs:
|
||||
- name: Run ruby tests
|
||||
run: |
|
||||
cd bindings/ruby
|
||||
gem install bundler
|
||||
bundle install
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
bundle exec rake
|
||||
|
||||
4
.github/workflows/test-wasm.yml
vendored
4
.github/workflows/test-wasm.yml
vendored
@@ -28,5 +28,7 @@ jobs:
|
||||
cd bindings/wasm
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
wasm-pack build --target nodejs --release
|
||||
wasm-pack test --release --node
|
||||
# Enable when upstream issue is fixed.
|
||||
# https://github.com/microsoft/regorus/issues/371
|
||||
# wasm-pack test --release --node
|
||||
node test.js
|
||||
|
||||
70
CHANGELOG.md
70
CHANGELOG.md
@@ -6,6 +6,76 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.3.0](https://github.com/microsoft/regorus/compare/regorus-v0.2.8...regorus-v0.3.0) - 2025-03-10
|
||||
|
||||
### Added
|
||||
- [**breaking**] Update to OPA v1.2.0 ([#373](https://github.com/microsoft/regorus/pull/373))
|
||||
|
||||
### Other
|
||||
- *(deps)* update pyo3 requirement from 0.23.5 to 0.24.0 ([#375](https://github.com/microsoft/regorus/pull/375))
|
||||
- Update ruby binding deps, ruby gem version 0.2.3 ([#374](https://github.com/microsoft/regorus/pull/374))
|
||||
- *(deps)* update pyo3 requirement from 0.22.0 to 0.23.5 ([#372](https://github.com/microsoft/regorus/pull/372))
|
||||
- *(deps)* update rand requirement from 0.8.5 to 0.9.0 ([#370](https://github.com/microsoft/regorus/pull/370))
|
||||
- *(deps)* update cbindgen requirement from 0.27.0 to 0.28.0 ([#361](https://github.com/microsoft/regorus/pull/361))
|
||||
- Fix typo in README.md ([#366](https://github.com/microsoft/regorus/pull/366))
|
||||
- Update dependencies ([#369](https://github.com/microsoft/regorus/pull/369))
|
||||
- Fix clippy warning for result? ([#362](https://github.com/microsoft/regorus/pull/362))
|
||||
- *(deps)* update itertools requirement from 0.13.0 to 0.14.0 ([#357](https://github.com/microsoft/regorus/pull/357))
|
||||
- *(deps)* update jsonschema requirement from 0.26.1 to 0.28.1 ([#356](https://github.com/microsoft/regorus/pull/356))
|
||||
- resolve anyhow compile errors ([#355](https://github.com/microsoft/regorus/pull/355))
|
||||
- *(deps)* update prettydiff requirement from 0.7.0 to 0.8.0 ([#348](https://github.com/microsoft/regorus/pull/348))
|
||||
|
||||
## [0.2.8](https://github.com/microsoft/regorus/compare/regorus-v0.2.7...regorus-v0.2.8) - 2024-11-06
|
||||
|
||||
### Other
|
||||
- *(deps)* update jsonschema requirement from 0.24.0 to 0.26.1 ([#343](https://github.com/microsoft/regorus/pull/343))
|
||||
- Update to OPA v0.70.0 ([#341](https://github.com/microsoft/regorus/pull/341))
|
||||
|
||||
## [0.2.7](https://github.com/microsoft/regorus/compare/regorus-v0.2.6...regorus-v0.2.7) - 2024-10-22
|
||||
|
||||
### Fixed
|
||||
- docs failing to build ([#334](https://github.com/microsoft/regorus/pull/334))
|
||||
|
||||
### Other
|
||||
- *(deps)* update jsonschema requirement from 0.23.0 to 0.24.0 ([#332](https://github.com/microsoft/regorus/pull/332))
|
||||
- *(deps)* update jsonschema requirement from 0.22.3 to 0.23.0 ([#331](https://github.com/microsoft/regorus/pull/331))
|
||||
|
||||
## [0.2.6](https://github.com/microsoft/regorus/compare/regorus-v0.2.5...regorus-v0.2.6) - 2024-10-09
|
||||
|
||||
### Added
|
||||
- integer conversion functions for Value ([#328](https://github.com/microsoft/regorus/pull/328))
|
||||
|
||||
### Other
|
||||
- update to OPA v0.69.0 ([#327](https://github.com/microsoft/regorus/pull/327))
|
||||
- *(deps)* update jsonschema requirement from 0.21.0 to 0.22.3 ([#326](https://github.com/microsoft/regorus/pull/326))
|
||||
- *(deps)* update jsonschema requirement from 0.20.0 to 0.21.0 ([#325](https://github.com/microsoft/regorus/pull/325))
|
||||
- update to jsonschema 0.20.0 ([#323](https://github.com/microsoft/regorus/pull/323))
|
||||
|
||||
## [0.2.5](https://github.com/microsoft/regorus/compare/regorus-v0.2.4...regorus-v0.2.5) - 2024-09-18
|
||||
|
||||
### Added
|
||||
- or keyword ([#315](https://github.com/microsoft/regorus/pull/315))
|
||||
|
||||
### Fixed
|
||||
- Null terminate C# strings in Rust boundary ([#318](https://github.com/microsoft/regorus/pull/318))
|
||||
- Update readme with correct path to example policy ([#312](https://github.com/microsoft/regorus/pull/312))
|
||||
|
||||
### Other
|
||||
- Update jsonschema requirement from 0.18.0 to 0.19.1 ([#317](https://github.com/microsoft/regorus/pull/317))
|
||||
- Update chrono-tz requirement from 0.8.5 to 0.10.0 ([#316](https://github.com/microsoft/regorus/pull/316))
|
||||
- Add tests for builtin strings::lower method ([#313](https://github.com/microsoft/regorus/pull/313))
|
||||
- Add tests for builtin strings::indexof method ([#311](https://github.com/microsoft/regorus/pull/311))
|
||||
|
||||
## [0.2.4](https://github.com/microsoft/regorus/compare/regorus-v0.2.3...regorus-v0.2.4) - 2024-09-04
|
||||
|
||||
### Added
|
||||
- OPA v0.68.0. Engine::set_rego_v1 ([#305](https://github.com/microsoft/regorus/pull/305))
|
||||
|
||||
### Fixed
|
||||
- Handle parsing corner cases ([#309](https://github.com/microsoft/regorus/pull/309))
|
||||
- Propagate errors encountered in argument evaluation ([#308](https://github.com/microsoft/regorus/pull/308))
|
||||
- Issues [#302](https://github.com/microsoft/regorus/pull/302), [#303](https://github.com/microsoft/regorus/pull/303) ([#304](https://github.com/microsoft/regorus/pull/304))
|
||||
|
||||
## [0.2.3](https://github.com/microsoft/regorus/compare/regorus-v0.2.2...regorus-v0.2.3) - 2024-08-16
|
||||
|
||||
### Fixed
|
||||
|
||||
38
Cargo.toml
38
Cargo.toml
@@ -12,7 +12,7 @@ members = [
|
||||
[package]
|
||||
name = "regorus"
|
||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
@@ -67,7 +67,9 @@ full-opa = [
|
||||
"time",
|
||||
"uuid",
|
||||
"urlquery",
|
||||
"yaml"
|
||||
"yaml",
|
||||
|
||||
#"rego-extensions"
|
||||
]
|
||||
|
||||
# Features that can be used in no_std environments.
|
||||
@@ -89,6 +91,9 @@ opa-no-std = [
|
||||
"lazy_static/spin_no_std"
|
||||
]
|
||||
|
||||
# Rego language extensions
|
||||
rego-extensions = []
|
||||
|
||||
# This feature enables some testing utils for OPA tests.
|
||||
opa-testutil = []
|
||||
rand = ["dep:rand"]
|
||||
@@ -100,35 +105,36 @@ serde_json = { version = "1.0.89", default-features = false, features = ["alloc"
|
||||
lazy_static = { version = "1.4.0", default-features = false }
|
||||
|
||||
# Crypto
|
||||
constant_time_eq = {version = "0.3.0", optional = true, default-features = false }
|
||||
constant_time_eq = {version = "0.4.0", optional = true, default-features = false }
|
||||
hmac = {version = "0.12.1", optional = true, default-features = false}
|
||||
sha2 = {version= "0.10.8", optional = true, default-features = false }
|
||||
hex = {version = "0.4.3", optional = true, default-features = false, features = ["alloc"] }
|
||||
sha1 = {version = "0.10.6", optional = true, default-features = false }
|
||||
md-5 = {version = "0.10.6", optional = true, default-features = false }
|
||||
|
||||
data-encoding = { version = "2.4.0", optional = true, default-features=false, features = ["alloc"] }
|
||||
scientific = { version = "0.5.2" }
|
||||
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
|
||||
scientific = { version = "0.5.3" }
|
||||
|
||||
regex = {version = "1.10.2", optional = true, default-features = false }
|
||||
semver = {version = "1.0.20", optional = true, default-features = false }
|
||||
regex = {version = "1.11.1", optional = true, default-features = false }
|
||||
semver = {version = "1.0.25", optional = true, default-features = false }
|
||||
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
|
||||
url = { version = "2.5.0", optional = true }
|
||||
uuid = { version = "1.6.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
|
||||
jsonschema = { version = "0.18.0", default-features = false, optional = true }
|
||||
chrono = { version = "0.4.31", optional = true }
|
||||
chrono-tz = { version = "0.8.5", optional = true }
|
||||
jsonwebtoken = { version = "9.2.0", optional = true }
|
||||
itertools = { version = "0.13.0", default-features = false, optional = true }
|
||||
url = { version = "2.5.4", optional = true }
|
||||
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
|
||||
jsonschema = { version = "0.29.0", default-features = false, optional = true }
|
||||
chrono = { version = "0.4.40", optional = true }
|
||||
chrono-tz = { version = "0.10.1", optional = true }
|
||||
jsonwebtoken = { version = "9.3.1", optional = true }
|
||||
itertools = { version = "0.14.0", default-features = false, optional = true }
|
||||
|
||||
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
|
||||
rand = { version = "0.8.5", default-features = false, optional = true }
|
||||
# Specify thread_rng for in order to use random_range
|
||||
rand = { version = "0.9.0", default-features = false, features = ["thread_rng"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.45"
|
||||
cfg-if = "1.0.0"
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
prettydiff = { version = "0.7.0", default-features = false }
|
||||
prettydiff = { version = "0.8.0", default-features = false }
|
||||
serde_yaml = "0.9.16"
|
||||
test-generator = "0.3.1"
|
||||
walkdir = "2.3.2"
|
||||
|
||||
17
README.md
17
README.md
@@ -9,8 +9,8 @@
|
||||
Regorus is also
|
||||
- *cross-platform* - Written in platform-agnostic Rust.
|
||||
- *no_std compatible* - Regorus can be used in `no_std` environments too. Most of the builtins are supported.
|
||||
- *current* - We strive to keep Regorus up to date with latest OPA release. Regorus supports `import rego.v1`.
|
||||
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.67.0](https://github.com/open-policy-agent/opa/releases/tag/v0.67.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
|
||||
- *current* - We strive to keep Regorus up to date with latest OPA release. Regorus defaults to `v1` of the Rego language.
|
||||
- *compliant* - Regorus is mostly compliant with the latest [OPA release v1.2.0](https://github.com/open-policy-agent/opa/releases/tag/v1.2.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
|
||||
- *extensible* - Extend the Rego language by implementing custom stateful builtins in Rust.
|
||||
See [add_extension](https://github.com/microsoft/regorus/blob/fc68bf9c8bea36427dae9401a7d1f6ada771f7ab/src/engine.rs#L352).
|
||||
Support for extensibility using other languages coming soon.
|
||||
@@ -32,7 +32,6 @@ fn main() -> anyhow::Result<()> {
|
||||
let policy = String::from(
|
||||
r#"
|
||||
package example
|
||||
import rego.v1
|
||||
|
||||
allow if {
|
||||
## All actions are allowed for admins.
|
||||
@@ -99,7 +98,7 @@ $ cargo build -r --example regorus --no-default-features; strip target/release/e
|
||||
-rwxr-xr-x 1 anand staff 1.9M May 11 22:04 target/release/examples/regorus*
|
||||
```
|
||||
|
||||
Regorus passes the [OPA v0.67.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
||||
Regorus passes the [OPA v1.2.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
||||
builtins. See [OPA Conformance](#opa-conformance) below.
|
||||
|
||||
## Bindings
|
||||
@@ -184,11 +183,11 @@ This produces the following output
|
||||
}
|
||||
```
|
||||
|
||||
Next, evaluate a sample [policy](https://github.com/microsoft/regorus/blob/main/examples/example.rego) and [input](https://github.com/microsoft/regorus/blob/main/examples/input.json)
|
||||
Next, evaluate a sample [policy](https://github.com/microsoft/regorus/blob/main/examples/server/allowed_server.rego) and [input](https://github.com/microsoft/regorus/blob/main/examples/server/input.json)
|
||||
(borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)):
|
||||
|
||||
```bash
|
||||
$ regorus eval -d examples/example.rego -i examples/input.json data.example
|
||||
$ regorus eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
|
||||
```
|
||||
|
||||
Finally, evaluate real-world [policies](tests/aci/) used in Azure Container Instances (ACI)
|
||||
@@ -204,7 +203,7 @@ Regorus allows determining which lines of a policy have been executed using the
|
||||
We can try it out using the `regorus` example program by passing in the `--coverage` flag.
|
||||
|
||||
```shell
|
||||
$ regorus eval -d examples/example.rego -i examples/input.json data.example --coverage
|
||||
$ regorus eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example --coverage
|
||||
```
|
||||
|
||||
It produces the following coverage report which shows that all lines are executed except the line that sets `allow` to true.
|
||||
@@ -276,7 +275,7 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
|
||||
```
|
||||
## OPA Conformance
|
||||
|
||||
Regorus has been verified to be compliant with [OPA v0.67.0](https://github.com/open-policy-agent/opa/releases/tag/v0.67.0)
|
||||
Regorus has been verified to be compliant with [OPA v1.2.0](https://github.com/open-policy-agent/opa/releases/tag/v1.2.0)
|
||||
using a [test driver](https://github.com/microsoft/regorus/blob/main/tests/opa.rs) that loads and runs the OPA testsuite using Regorus, and verifies that expected outputs are produced.
|
||||
|
||||
The test driver can be invoked by running:
|
||||
@@ -288,7 +287,7 @@ $ cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precisio
|
||||
Currently, Regorus passes all the non-builtin specific tests.
|
||||
See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing).
|
||||
|
||||
The following test suites don't pass fully due to mising builtins:
|
||||
The following test suites don't pass fully due to missing builtins:
|
||||
- `cryptoparsersaprivatekeys`
|
||||
- `cryptox509parseandverifycertificates`
|
||||
- `cryptox509parsecertificaterequest`
|
||||
|
||||
@@ -27,7 +27,7 @@ char* file_to_string(const char* file) {
|
||||
|
||||
// If regorus is built with custom-allocator, then provide implementation.
|
||||
uint8_t* regorus_aligned_alloc(size_t alignment, size_t size) {
|
||||
return aligned_alloc(alignment, size);
|
||||
return (uint8_t*) aligned_alloc(alignment, size);
|
||||
}
|
||||
|
||||
void regorus_free(uint8_t* ptr) {
|
||||
@@ -41,6 +41,11 @@ int main() {
|
||||
RegorusResult r;
|
||||
char* buffer = NULL;
|
||||
|
||||
// Turn on rego v0 since policy uses v0.
|
||||
r = regorus_engine_set_rego_v0(engine, true);
|
||||
if (r.status != RegorusStatusOk)
|
||||
goto error;
|
||||
|
||||
// Load policies.
|
||||
r = regorus_engine_add_policy(engine, "framework.rego", (buffer = file_to_string("../../../tests/aci/framework.rego")));
|
||||
free(buffer);
|
||||
|
||||
@@ -6,6 +6,11 @@ int main() {
|
||||
RegorusEngine* engine = regorus_engine_new();
|
||||
RegorusResult r;
|
||||
|
||||
// Turn on rego v0 since policy uses v0.
|
||||
r = regorus_engine_set_rego_v0(engine, true);
|
||||
if (r.status != RegorusStatusOk)
|
||||
goto error;
|
||||
|
||||
// Load policies.
|
||||
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
|
||||
if (r.status != RegorusStatusOk)
|
||||
|
||||
@@ -6,6 +6,7 @@ void example()
|
||||
// Create engine
|
||||
regorus::Engine engine;
|
||||
|
||||
engine.set_rego_v0(true);
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
// Add policies.
|
||||
@@ -83,6 +84,7 @@ int main() {
|
||||
|
||||
// Create engine.
|
||||
regorus::Engine engine;
|
||||
engine.set_rego_v0(true);
|
||||
|
||||
|
||||
// Load policies.
|
||||
|
||||
@@ -54,6 +54,9 @@ namespace regorus {
|
||||
return std::unique_ptr<Engine>(new Engine(regorus_engine_clone(engine)));
|
||||
}
|
||||
|
||||
Result set_rego_v0(bool enable) {
|
||||
return Result(regorus_engine_set_rego_v0(engine, enable));
|
||||
}
|
||||
|
||||
Result add_policy(const char* path, const char* policy) {
|
||||
return Result(regorus_engine_add_policy(engine, path, policy));
|
||||
|
||||
@@ -19,185 +19,202 @@ using System.Threading;
|
||||
namespace Microsoft.WindowsAzure.Regorus.IaaS
|
||||
{
|
||||
|
||||
public class RegorusPolicyEngine : ICloneable, IDisposable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
|
||||
public RegorusPolicyEngine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
public class RegorusPolicyEngine : ICloneable, IDisposable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
|
||||
public RegorusPolicyEngine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (E != null)
|
||||
{
|
||||
RegorusFFI.API.regorus_engine_drop(E);
|
||||
// to avoid Dispose() being called multiple times by mistake.
|
||||
E = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var clone = (RegorusPolicyEngine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
byte[] NullTerminatedUTF8Bytes(string s)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(s + char.MinValue);
|
||||
}
|
||||
public void SetRegoV0(bool enable)
|
||||
{
|
||||
if (E != null)
|
||||
unsafe
|
||||
{
|
||||
RegorusFFI.API.regorus_engine_drop(E);
|
||||
// to avoid Dispose() being called multiple times by mistake.
|
||||
E = null;
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_rego_v0(E, enable));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
public void AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
var regoBytes = NullTerminatedUTF8Bytes(rego);
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var clone = (RegorusPolicyEngine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed (byte* regoPtr = regoBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public void AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
public void AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
var regoBytes = Encoding.UTF8.GetBytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed(byte* regoPtr = regoBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPolicyFromPath(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
public void AddPolicyFromPath(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories);
|
||||
foreach (string file in regoFiles)
|
||||
{
|
||||
AddPolicyFromFile(file);
|
||||
}
|
||||
}
|
||||
string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories);
|
||||
foreach (string file in regoFiles)
|
||||
{
|
||||
AddPolicyFromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = Encoding.UTF8.GetBytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = NullTerminatedUTF8Bytes(data);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = Encoding.UTF8.GetBytes(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resultJson = "";
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
{
|
||||
var result = RegorusFFI.API.regorus_engine_eval_query(E, queryPtr);
|
||||
if (result.status == RegorusFFI.RegorusStatus.RegorusStatusOk) {
|
||||
if (result.output != null) {
|
||||
resultJson = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.output);
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
} else {
|
||||
CheckAndDropResult(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (resultJson != null) {
|
||||
return resultJson;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void CheckAndDropResult(RegorusFFI.RegorusResult result)
|
||||
{
|
||||
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
|
||||
unsafe {
|
||||
var message = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.error_message);
|
||||
var ex = new Exception(message);
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
}
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
var inputBytes = NullTerminatedUTF8Bytes(input);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = NullTerminatedUTF8Bytes(query);
|
||||
|
||||
var resultJson = "";
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
{
|
||||
var result = RegorusFFI.API.regorus_engine_eval_query(E, queryPtr);
|
||||
if (result.status == RegorusFFI.RegorusStatus.RegorusStatusOk)
|
||||
{
|
||||
if (result.output != null)
|
||||
{
|
||||
resultJson = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.output);
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckAndDropResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resultJson != null)
|
||||
{
|
||||
return resultJson;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void CheckAndDropResult(RegorusFFI.RegorusResult result)
|
||||
{
|
||||
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var message = System.Runtime.InteropServices.Marshal.PtrToStringAnsi((IntPtr)result.error_message);
|
||||
var ex = new Exception(message);
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ var w = new Stopwatch();
|
||||
w.Restart();
|
||||
|
||||
var engine = new Regorus.Engine();
|
||||
engine.SetRegoV0(true);
|
||||
|
||||
w.Stop();
|
||||
var newEngineTicks = w.ElapsedTicks;
|
||||
|
||||
@@ -2,219 +2,235 @@ using System.Text;
|
||||
|
||||
namespace Regorus
|
||||
{
|
||||
public class Exception : System.Exception
|
||||
{
|
||||
public Exception(string? message) : base(message) {}
|
||||
}
|
||||
|
||||
public class Engine : ICloneable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
public Engine()
|
||||
public class Exception : System.Exception
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
public Exception(string? message) : base(message) { }
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
public class Engine : ICloneable
|
||||
{
|
||||
var clone = (Engine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
public string AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
var regoBytes = Encoding.UTF8.GetBytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
public Engine()
|
||||
{
|
||||
fixed(byte* regoPtr = regoBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
public object Clone()
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
var clone = (Engine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = Encoding.UTF8.GetBytes(data);
|
||||
byte[] NullTerminatedUTF8Bytes(string s)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(s + char.MinValue);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
public string AddPolicy(string path, string rego)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
var regoBytes = NullTerminatedUTF8Bytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed (byte* regoPtr = regoBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
public void SetRegoV0(bool enable)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_rego_v0(E, enable));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
public string AddPolicyFromFile(string path)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
|
||||
var dataBytes = NullTerminatedUTF8Bytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = Encoding.UTF8.GetBytes(query);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalRule(string rule)
|
||||
{
|
||||
var ruleBytes = Encoding.UTF8.GetBytes(rule);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* rulePtr = ruleBytes)
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr));
|
||||
var inputBytes = NullTerminatedUTF8Bytes(input);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEnableCoverage(bool enable)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_enable_coverage(E, enable));
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCoverageData()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_clear_coverage_data(E));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverageReport()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report(E));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverageReportPretty()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report_pretty(E));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGatherPrints(bool enable)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_gather_prints(E, enable));
|
||||
}
|
||||
}
|
||||
|
||||
public string TakePrints()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_take_prints(E));
|
||||
}
|
||||
}
|
||||
|
||||
~Engine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
RegorusFFI.API.regorus_engine_drop(E);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
string CheckAndDropResult(RegorusFFI.RegorusResult result)
|
||||
{
|
||||
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
|
||||
unsafe {
|
||||
var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message);
|
||||
var ex = new Exception(message);
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
throw ex;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resultString = "";
|
||||
unsafe
|
||||
{
|
||||
if (result.output is not null) {
|
||||
resultString = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output);
|
||||
public string EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = NullTerminatedUTF8Bytes(query);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
}
|
||||
return resultString;
|
||||
}
|
||||
|
||||
}
|
||||
public string EvalRule(string rule)
|
||||
{
|
||||
var ruleBytes = NullTerminatedUTF8Bytes(rule);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* rulePtr = ruleBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEnableCoverage(bool enable)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_enable_coverage(E, enable));
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCoverageData()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_clear_coverage_data(E));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverageReport()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report(E));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverageReportPretty()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report_pretty(E));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGatherPrints(bool enable)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_gather_prints(E, enable));
|
||||
}
|
||||
}
|
||||
|
||||
public string TakePrints()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_take_prints(E));
|
||||
}
|
||||
}
|
||||
|
||||
~Engine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
RegorusFFI.API.regorus_engine_drop(E);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string CheckAndDropResult(RegorusFFI.RegorusResult result)
|
||||
{
|
||||
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message);
|
||||
var ex = new Exception(message);
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
var resultString = "";
|
||||
unsafe
|
||||
{
|
||||
if (result.output is not null)
|
||||
{
|
||||
resultString = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output);
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
}
|
||||
return resultString;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ crate-type = ["cdylib", "staticlib"]
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
regorus = { path = "../..", default-features = false }
|
||||
serde_json = "1.0.113"
|
||||
serde_json = "1.0.140"
|
||||
|
||||
[features]
|
||||
default = ["ast", "std", "coverage", "regorus/arc", "regorus/full-opa"]
|
||||
@@ -20,5 +20,5 @@ coverage = ["regorus/coverage"]
|
||||
custom_allocator = []
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.27.0"
|
||||
cbindgen = "0.28.0"
|
||||
csbindgen = "=1.9.3"
|
||||
|
||||
@@ -222,7 +222,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
|
||||
|
||||
/// Clear policy data.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_result(|| -> Result<()> {
|
||||
@@ -233,7 +233,7 @@ pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> Regor
|
||||
|
||||
/// Set input.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_input
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_input
|
||||
/// * `input`: JSON encoded value to be used as input to query.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_set_input_json(
|
||||
@@ -264,7 +264,7 @@ pub extern "C" fn regorus_engine_set_input_from_json_file(
|
||||
|
||||
/// Evaluate query.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_query
|
||||
/// * `query`: Rego expression to be evaluate.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_eval_query(
|
||||
@@ -289,7 +289,7 @@ pub extern "C" fn regorus_engine_eval_query(
|
||||
|
||||
/// Evaluate specified rule.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_rule
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule
|
||||
/// * `rule`: Path to the rule.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_eval_rule(
|
||||
@@ -314,7 +314,7 @@ pub extern "C" fn regorus_engine_eval_rule(
|
||||
|
||||
/// Enable/disable coverage.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_enable_coverage
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage
|
||||
/// * `enable`: Whether to enable or disable coverage.
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
@@ -330,7 +330,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
|
||||
|
||||
/// Get coverage report.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.get_coverage_report
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
@@ -375,7 +375,7 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty(
|
||||
|
||||
/// Clear coverage data.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_coverage_data
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
@@ -387,7 +387,7 @@ pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine)
|
||||
|
||||
/// Whether to gather output of print statements.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_gather_prints
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints
|
||||
/// * `enable`: Whether to enable or disable gathering print statements.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_set_gather_prints(
|
||||
@@ -402,7 +402,7 @@ pub extern "C" fn regorus_engine_set_gather_prints(
|
||||
|
||||
/// Take all the gathered print statements.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.take_prints
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let output = || -> Result<String> {
|
||||
@@ -437,6 +437,28 @@ pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) ->
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable/disable rego v1.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_rego_v0
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_set_rego_v0(
|
||||
engine: *mut RegorusEngine,
|
||||
enable: bool,
|
||||
) -> RegorusResult {
|
||||
let output = || -> Result<()> {
|
||||
to_ref(&engine)?.engine.set_rego_v0(enable);
|
||||
Ok(())
|
||||
}();
|
||||
match output {
|
||||
Ok(()) => RegorusResult {
|
||||
status: RegorusStatus::RegorusStatusOk,
|
||||
output: std::ptr::null_mut(),
|
||||
error_message: std::ptr::null_mut(),
|
||||
},
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "custom_allocator")]
|
||||
extern "C" {
|
||||
fn regorus_aligned_alloc(alignment: usize, size: usize) -> *mut u8;
|
||||
|
||||
@@ -16,8 +16,11 @@ func main() {
|
||||
// Create new engine
|
||||
engine := regorus.NewEngine()
|
||||
defer engine.Close()
|
||||
elapsed1 := time.Since(t)
|
||||
|
||||
engine.SetRegoV0(true)
|
||||
elapsed1 := time.Since(t)
|
||||
|
||||
|
||||
t = time.Now()
|
||||
// Add policies and data.
|
||||
policies := []string{
|
||||
|
||||
@@ -28,6 +28,17 @@ func (e *Engine) Clone() *Engine {
|
||||
return c
|
||||
}
|
||||
|
||||
func (e *Engine) SetRegoV0(enable bool) (error) {
|
||||
result := C.regorus_engine_set_rego_v0(e.e, C.bool(enable))
|
||||
defer C.regorus_result_drop(result)
|
||||
|
||||
if result.status != C.RegorusStatusOk {
|
||||
return fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) AddPolicy(path string, rego string) (string, error) {
|
||||
path_c := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(path_c))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorus-java"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/java"
|
||||
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
|
||||
@@ -4,39 +4,45 @@
|
||||
import com.microsoft.regorus.Engine;
|
||||
|
||||
public class Test {
|
||||
|
||||
public static void main(String[] args) {
|
||||
try (Engine engine = new Engine()) {
|
||||
String pkg = engine.addPolicy(
|
||||
"hello.rego",
|
||||
"package test\nx=1\nmessage = concat(\", \", [input.message, data.message])"
|
||||
"hello.rego",
|
||||
"package test\nx=1\nmessage = concat(\", \", [input.message, data.message])"
|
||||
);
|
||||
System.out.println("Loaded package " + pkg);
|
||||
System.out.println("Loaded package " + pkg);
|
||||
|
||||
|
||||
engine.addDataJson("{\"message\":\"World!\"}");
|
||||
engine.setInputJson("{\"message\":\"Hello\"}");
|
||||
|
||||
// Evaluate query.
|
||||
String resJson = engine.evalQuery("data.test.message");
|
||||
// Evaluate query.
|
||||
String resJson = engine.evalQuery("data.test.message");
|
||||
System.out.println(resJson);
|
||||
|
||||
// Enable coverage.
|
||||
engine.setEnableCoverage(true);
|
||||
// Enable coverage.
|
||||
engine.setEnableCoverage(true);
|
||||
|
||||
// Evaluate rule.
|
||||
String valueJson = engine.evalRule("data.test.message");
|
||||
// Evaluate rule.
|
||||
String valueJson = engine.evalRule("data.test.message");
|
||||
System.out.println(valueJson);
|
||||
|
||||
String coverageJson = engine.getCoverageReport();
|
||||
System.out.println(coverageJson);
|
||||
String coverageJson = engine.getCoverageReport();
|
||||
System.out.println(coverageJson);
|
||||
|
||||
System.out.println(engine.getCoverageReportPretty());
|
||||
System.out.println(engine.getCoverageReportPretty());
|
||||
|
||||
String packagesJson = engine.getPackages();
|
||||
System.out.println(packagesJson);
|
||||
String packagesJson = engine.getPackages();
|
||||
System.out.println(packagesJson);
|
||||
|
||||
String policiesJson = engine.getPolicies();
|
||||
System.out.println(policiesJson);
|
||||
String policiesJson = engine.getPolicies();
|
||||
System.out.println(policiesJson);
|
||||
|
||||
engine.setRegoV0(true);
|
||||
engine.addPolicy(
|
||||
"world.rego",
|
||||
"package world\nx { true }"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,20 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
|
||||
Box::into_raw(Box::new(c)) as jlong
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetRegoV0(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
enable: bool,
|
||||
) {
|
||||
let _ = throw_err(env, |_env| {
|
||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||
engine.set_rego_v0(enable);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
|
||||
env: JNIEnv,
|
||||
|
||||
@@ -8,10 +8,8 @@ package com.microsoft.regorus;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Regorus Engine.
|
||||
@@ -23,6 +21,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
||||
// if you update the native API.
|
||||
private static native long nativeNewEngine();
|
||||
private static native long nativeClone(long enginePtr);
|
||||
private static native void nativeSetRegoV0(long enginePtr, boolean enable);
|
||||
private static native String nativeAddPolicy(long enginePtr, String path, String rego);
|
||||
private static native String nativeAddPolicyFromFile(long enginePtr, String path);
|
||||
private static native String nativeGetPackages(long enginePtr);
|
||||
@@ -55,7 +54,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
||||
|
||||
|
||||
Engine(long ptr) {
|
||||
enginePtr = ptr;
|
||||
enginePtr = ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,6 +64,16 @@ public class Engine implements AutoCloseable, Cloneable {
|
||||
return new Engine(nativeClone(enginePtr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable Rego v0.
|
||||
*
|
||||
* @param enable Whether to enable v0 or not.
|
||||
*
|
||||
*/
|
||||
public void setRegoV0(boolean enable) {
|
||||
nativeSetRegoV0(enginePtr, enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an inline Rego policy.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regoruspy"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/python"
|
||||
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
@@ -18,8 +18,8 @@ coverage = ["regorus/coverage"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
ordered-float = "4.2.0"
|
||||
pyo3 = {version = "0.22.0", features = ["anyhow", "extension-module"] }
|
||||
ordered-float = "5.0.0"
|
||||
pyo3 = {version = "0.24.0", features = ["anyhow", "extension-module"] }
|
||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
||||
serde_json = "1.0.112"
|
||||
serde_json = "1.0.140"
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use anyhow::{anyhow, Result};
|
||||
use pyo3::exceptions::PyTypeError;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::*;
|
||||
use pyo3::IntoPyObjectExt;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
@@ -94,7 +95,7 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
|
||||
let mut map = BTreeMap::new();
|
||||
let keys = pmap.keys()?;
|
||||
let values = pmap.values()?;
|
||||
for i in 0..keys.len()? {
|
||||
for i in 0..keys.len() {
|
||||
let key = keys.get_item(i)?;
|
||||
let value = values.get_item(i)?;
|
||||
map.insert(from(&key)?, from(&value)?);
|
||||
@@ -108,49 +109,53 @@ fn from(ob: &Bound<'_, PyAny>) -> Result<Value, PyErr> {
|
||||
}
|
||||
|
||||
fn to(mut v: Value, py: Python<'_>) -> Result<PyObject> {
|
||||
Ok(match v {
|
||||
Value::Null => None::<u64>.to_object(py),
|
||||
let obj = match v {
|
||||
Value::Null => None::<u64>.into_bound_py_any(py),
|
||||
|
||||
// TODO: Revisit this mapping
|
||||
Value::Undefined => None::<u64>.to_object(py),
|
||||
Value::Undefined => None::<u64>.into_bound_py_any(py),
|
||||
|
||||
Value::Bool(b) => b.to_object(py),
|
||||
Value::String(s) => s.to_object(py),
|
||||
Value::Bool(b) => b.into_bound_py_any(py),
|
||||
Value::String(s) => s.into_bound_py_any(py),
|
||||
|
||||
Value::Number(_) => {
|
||||
if let Ok(f) = v.as_f64() {
|
||||
f.to_object(py)
|
||||
f.into_bound_py_any(py)
|
||||
} else if let Ok(u) = v.as_u64() {
|
||||
u.to_object(py)
|
||||
u.into_bound_py_any(py)
|
||||
} else {
|
||||
v.as_i64()?.to_object(py)
|
||||
v.as_i64()?.into_bound_py_any(py)
|
||||
}
|
||||
}
|
||||
|
||||
Value::Array(_) => {
|
||||
let list = PyList::empty_bound(py);
|
||||
let list = PyList::empty(py);
|
||||
for v in std::mem::take(v.as_array_mut()?) {
|
||||
list.append(to(v, py)?)?;
|
||||
}
|
||||
list.into()
|
||||
list.into_bound_py_any(py)
|
||||
}
|
||||
|
||||
Value::Set(_) => {
|
||||
let set = PySet::empty_bound(py)?;
|
||||
let set = PySet::empty(py)?;
|
||||
for v in std::mem::take(v.as_set_mut()?) {
|
||||
set.add(to(v, py)?)?;
|
||||
}
|
||||
set.into()
|
||||
set.into_bound_py_any(py)
|
||||
}
|
||||
|
||||
Value::Object(_) => {
|
||||
let dict = PyDict::new_bound(py);
|
||||
let dict = PyDict::new(py);
|
||||
for (k, v) in std::mem::take(v.as_object_mut()?) {
|
||||
dict.set_item(to(k, py)?, to(v, py)?)?;
|
||||
}
|
||||
dict.into()
|
||||
dict.into_bound_py_any(py)
|
||||
}
|
||||
})
|
||||
};
|
||||
match obj {
|
||||
Ok(v) => Ok(v.into()),
|
||||
Err(e) => Err(anyhow!("{e}")),
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
@@ -163,6 +168,15 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn on rego v0.
|
||||
///
|
||||
/// Regorus now defaults to v1.
|
||||
///
|
||||
/// * `enable`: Whether to enable/disable v0.
|
||||
pub fn set_rego_v0(&mut self, enable: bool) {
|
||||
self.engine.set_rego_v0(enable)
|
||||
}
|
||||
|
||||
/// Add a policy
|
||||
///
|
||||
/// The policy is parsed into AST.
|
||||
@@ -261,30 +275,30 @@ impl Engine {
|
||||
pub fn eval_query(&mut self, query: String, py: Python<'_>) -> Result<PyObject> {
|
||||
let results = self.engine.eval_query(query, false)?;
|
||||
|
||||
let rlist = PyList::empty_bound(py);
|
||||
let rlist = PyList::empty(py);
|
||||
for result in results.result.into_iter() {
|
||||
let rdict = PyDict::new_bound(py);
|
||||
let rdict = PyDict::new(py);
|
||||
|
||||
let elist = PyList::empty_bound(py);
|
||||
let elist = PyList::empty(py);
|
||||
for expr in result.expressions.into_iter() {
|
||||
let edict = PyDict::new_bound(py);
|
||||
edict.set_item("value".to_object(py), to(expr.value, py)?)?;
|
||||
edict.set_item("text".to_object(py), expr.text.as_ref().to_object(py))?;
|
||||
let edict = PyDict::new(py);
|
||||
edict.set_item("value", to(expr.value, py)?)?;
|
||||
edict.set_item("text", expr.text.as_ref())?;
|
||||
|
||||
let ldict = PyDict::new_bound(py);
|
||||
ldict.set_item("row".to_object(py), expr.location.row.to_object(py))?;
|
||||
ldict.set_item("col".to_object(py), expr.location.col.to_object(py))?;
|
||||
let ldict = PyDict::new(py);
|
||||
ldict.set_item("row", expr.location.row)?;
|
||||
ldict.set_item("col", expr.location.col)?;
|
||||
|
||||
edict.set_item("location".to_object(py), ldict)?;
|
||||
edict.set_item("location", ldict)?;
|
||||
elist.append(edict)?;
|
||||
}
|
||||
|
||||
rdict.set_item("expressions".to_object(py), elist)?;
|
||||
rdict.set_item("bindings".to_object(py), to(result.bindings, py)?)?;
|
||||
rdict.set_item("expressions", elist)?;
|
||||
rdict.set_item("bindings", to(result.bindings, py)?)?;
|
||||
rlist.append(rdict)?;
|
||||
}
|
||||
let dict = PyDict::new_bound(py);
|
||||
dict.set_item("result".to_object(py), rlist)?;
|
||||
let dict = PyDict::new(py);
|
||||
dict.set_item("result", rlist)?;
|
||||
Ok(dict.into())
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import regorus
|
||||
# Create engine
|
||||
engine = regorus.Engine()
|
||||
|
||||
engine.set_rego_v0(True)
|
||||
|
||||
# Load policies
|
||||
pkg = engine.add_policy_from_file('../../tests/aci/framework.rego')
|
||||
print(' Loaded package %s' % pkg)
|
||||
|
||||
@@ -3,7 +3,7 @@ require:
|
||||
- rubocop-rake
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 3.0
|
||||
TargetRubyVersion: 3.4
|
||||
NewCops: enable
|
||||
|
||||
Layout/LineLength:
|
||||
|
||||
@@ -1 +1 @@
|
||||
ruby 3.3.1
|
||||
ruby 3.4.2
|
||||
|
||||
@@ -7,10 +7,10 @@ gemspec
|
||||
|
||||
# These gems are required for local development and testing,
|
||||
# but won't be included in the published gem
|
||||
gem "minitest", "~> 5.23"
|
||||
gem "minitest", "~> 5.25"
|
||||
gem "rake", "~> 13.2"
|
||||
gem "rake-compiler"
|
||||
gem "rake-compiler-dock"
|
||||
gem "rubocop", "~> 1.64", require: false
|
||||
gem "rubocop-minitest", require: false
|
||||
gem "rubocop-rake", require: false
|
||||
gem "rake-compiler", "~> 1.2"
|
||||
gem "rake-compiler-dock", "~> 1.9"
|
||||
gem "rubocop", "~> 1.73", require: false
|
||||
gem "rubocop-minitest", "~> 0.37.1", require: false
|
||||
gem "rubocop-rake", "~> 0.7.1", require: false
|
||||
|
||||
@@ -1,65 +1,68 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
regorusrb (0.2.1)
|
||||
rb_sys (~> 0.9.97)
|
||||
regorusrb (0.3.0)
|
||||
rb_sys (~> 0.9.111)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
ast (2.4.2)
|
||||
json (2.7.2)
|
||||
language_server-protocol (3.17.0.3)
|
||||
minitest (5.24.1)
|
||||
parallel (1.25.1)
|
||||
parser (3.3.4.0)
|
||||
json (2.10.1)
|
||||
language_server-protocol (3.17.0.4)
|
||||
lint_roller (1.1.0)
|
||||
minitest (5.25.4)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.7.1)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
racc (1.8.0)
|
||||
racc (1.8.1)
|
||||
rainbow (3.1.1)
|
||||
rake (13.2.1)
|
||||
rake-compiler (1.2.7)
|
||||
rake-compiler (1.2.9)
|
||||
rake
|
||||
rake-compiler-dock (1.5.1)
|
||||
rb_sys (0.9.99)
|
||||
regexp_parser (2.9.2)
|
||||
rexml (3.3.3)
|
||||
strscan
|
||||
rubocop (1.65.0)
|
||||
rake-compiler-dock (1.9.1)
|
||||
rb_sys (0.9.111)
|
||||
rake-compiler-dock (= 1.9.1)
|
||||
regexp_parser (2.10.0)
|
||||
rubocop (1.73.2)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.4, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.38.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.31.3)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.38.1)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-minitest (0.35.1)
|
||||
rubocop (>= 1.61, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rake (0.6.0)
|
||||
rubocop (~> 1.0)
|
||||
rubocop-minitest (0.37.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.72.1, < 2.0)
|
||||
rubocop-ast (>= 1.38.0, < 2.0)
|
||||
rubocop-rake (0.7.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.72.1)
|
||||
ruby-progressbar (1.13.0)
|
||||
strscan (3.1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
unicode-display_width (3.1.4)
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
minitest (~> 5.23)
|
||||
minitest (~> 5.25)
|
||||
rake (~> 13.2)
|
||||
rake-compiler
|
||||
rake-compiler-dock
|
||||
rake-compiler (~> 1.2)
|
||||
rake-compiler-dock (~> 1.9)
|
||||
regorusrb!
|
||||
rubocop (~> 1.64)
|
||||
rubocop-minitest
|
||||
rubocop-rake
|
||||
rubocop (~> 1.73)
|
||||
rubocop-minitest (~> 0.37.1)
|
||||
rubocop-rake (~> 0.7.1)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.13
|
||||
2.6.5
|
||||
|
||||
@@ -43,6 +43,9 @@ require "regorus"
|
||||
|
||||
engine = Regorus::Engine.new
|
||||
|
||||
# Policy is old-style.
|
||||
engine.set_rego_v0(true)
|
||||
|
||||
engine.add_policy_from_file('../../tests/aci/framework.rego')
|
||||
engine.add_policy_from_file('../../tests/aci/api.rego')
|
||||
engine.add_policy_from_file('../../tests/aci/policy.rego')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "regorusrb"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
version = "0.3.0"
|
||||
edition = "2024"
|
||||
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
publish = false
|
||||
|
||||
@@ -15,7 +15,7 @@ ast = ["regorus/ast"]
|
||||
coverage = ["regorus/coverage"]
|
||||
|
||||
[dependencies]
|
||||
magnus = { version = "0.6.4" }
|
||||
magnus = { version = "0.7.1" }
|
||||
regorus = { path = "../../../..", default-features = false, features = ["arc"] }
|
||||
serde_json = "1.0.117"
|
||||
serde_magnus = "0.8.1"
|
||||
serde_json = "1.0.140"
|
||||
serde_magnus = "0.9.0"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use magnus::{exception::runtime_error, method, module, prelude::*, Error, Ruby};
|
||||
use magnus::{Error, Ruby, exception::runtime_error, method, module, prelude::*};
|
||||
use regorus::Engine as RegorusEngine;
|
||||
use std::cell::RefCell;
|
||||
use std::cmp::Ordering;
|
||||
@@ -36,6 +36,11 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_rego_v0(&self, enable: bool) -> Result<(), Error> {
|
||||
self.engine.borrow_mut().set_rego_v0(enable);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_policy(&self, path: String, rego: String) -> Result<String, Error> {
|
||||
self.engine
|
||||
.borrow_mut()
|
||||
@@ -297,6 +302,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
||||
// defines <, <=, >, >=, and == based on <=>
|
||||
engine_class.include_module(module::comparable())?;
|
||||
|
||||
// rego language configuration
|
||||
engine_class.define_method("set_rego_v0", method!(Engine::set_rego_v0, 1))?;
|
||||
|
||||
// policy operations
|
||||
engine_class.define_method("add_policy", method!(Engine::add_policy, 2))?;
|
||||
engine_class.define_method(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Regorus
|
||||
VERSION = "0.2.1"
|
||||
VERSION = "0.3.0"
|
||||
end
|
||||
|
||||
@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
|
||||
spec.summary = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
spec.homepage = "https://github.com/microsoft/regorus/blob/main/bindings/ruby"
|
||||
spec.license = "MIT"
|
||||
spec.required_ruby_version = ">= 3.0.0"
|
||||
spec.required_rubygems_version = ">= 3.3.11"
|
||||
spec.required_ruby_version = ">= 3.4.2"
|
||||
spec.required_rubygems_version = ">= 3.6.5"
|
||||
|
||||
spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
||||
|
||||
@@ -26,5 +26,5 @@ Gem::Specification.new do |spec|
|
||||
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
spec.extensions = ["ext/regorusrb/extconf.rb"]
|
||||
spec.add_dependency "rb_sys", "~> 0.9.97"
|
||||
spec.add_dependency "rb_sys", "~> 0.9.111"
|
||||
end
|
||||
|
||||
@@ -17,11 +17,11 @@ class TestRegorus < Minitest::Test
|
||||
def example_policy
|
||||
<<~REGO
|
||||
package regorus_test
|
||||
is_manager {
|
||||
is_manager if {
|
||||
input.name == data.managers[_]
|
||||
}
|
||||
|
||||
is_employee {
|
||||
is_employee if {
|
||||
input.name == data.employees[_]
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ class TestRegorus < Minitest::Test
|
||||
default is_manager_bool = false
|
||||
default is_employee_bool = false
|
||||
|
||||
is_manager_bool {
|
||||
is_manager_bool if {
|
||||
is_manager
|
||||
}
|
||||
|
||||
is_employee_bool {
|
||||
is_employee_bool if {
|
||||
is_employee
|
||||
}
|
||||
REGO
|
||||
|
||||
2
bindings/wasm/.cargo/config.toml
Normal file
2
bindings/wasm/.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""]
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorusjs"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/wasm"
|
||||
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
@@ -17,8 +17,16 @@ coverage = ["regorus/coverage"]
|
||||
|
||||
[dependencies]
|
||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
||||
serde_json = "1.0.111"
|
||||
wasm-bindgen = "0.2.90"
|
||||
serde_json = "1.0.140"
|
||||
wasm-bindgen = "0.2.100"
|
||||
# Specify uuid as a mandatory dependency so as to enable `js` feature which is now required
|
||||
# when targeting wasm32-unknown-unknown.
|
||||
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng", "js"]}
|
||||
# Enable wasm_js. See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
|
||||
getrandom = { version = "0.3", features = ["std", "wasm_js"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.40"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }
|
||||
|
||||
@@ -45,6 +45,15 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn on rego v0.
|
||||
///
|
||||
/// Regorus defaults to rego v1.
|
||||
///
|
||||
/// * `enable`: Whether to enable or disable rego v0.
|
||||
pub fn setRegoV0(&mut self, enable: bool) {
|
||||
self.engine.set_rego_v0(enable)
|
||||
}
|
||||
|
||||
/// Add a policy
|
||||
///
|
||||
/// The policy is parsed into AST.
|
||||
@@ -191,6 +200,7 @@ mod tests {
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
#[allow(dead_code)]
|
||||
pub fn basic() -> Result<(), JsValue> {
|
||||
let mut engine = crate::Engine::new();
|
||||
engine.setEnableCoverage(true);
|
||||
|
||||
@@ -61,9 +61,9 @@ in-expr ::= in-expr 'in' bool-expr
|
||||
bool-expr ::= bool-expr bool-op or-expr
|
||||
| or-expr
|
||||
bool-op ::= '<' | '<=' | '==' | '>=' | '>' | '!='
|
||||
or-expr ::= or-expr '|' and-expr
|
||||
| and-expr
|
||||
and-expr ::= and-expr '&' arith-expr
|
||||
set-union-expr ::= set-union-expr '|' set-intersection-expr
|
||||
| set-intersection-expr
|
||||
set-intersection-expr ::= set-intersection-expr '&' arith-expr
|
||||
| arith-expr
|
||||
arith-expr ::= arith-expr ('+' | '-') mul-div-expr
|
||||
| mul-div-expr
|
||||
|
||||
@@ -33,6 +33,7 @@ fn add_policy_from_file(engine: &mut regorus::Engine, path: String) -> Result<St
|
||||
engine.add_policy(path.clone(), read_file(&path)?)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn rego_eval(
|
||||
bundles: &[String],
|
||||
files: &[String],
|
||||
@@ -41,6 +42,7 @@ fn rego_eval(
|
||||
enable_tracing: bool,
|
||||
non_strict: bool,
|
||||
#[cfg(feature = "coverage")] coverage: bool,
|
||||
v0: bool,
|
||||
) -> Result<()> {
|
||||
// Create engine.
|
||||
let mut engine = regorus::Engine::new();
|
||||
@@ -50,6 +52,8 @@ fn rego_eval(
|
||||
#[cfg(feature = "coverage")]
|
||||
engine.set_enable_coverage(coverage);
|
||||
|
||||
engine.set_rego_v0(v0);
|
||||
|
||||
// Load files from given bundles.
|
||||
for dir in bundles.iter() {
|
||||
let entries =
|
||||
@@ -233,6 +237,10 @@ enum RegorusCommand {
|
||||
#[cfg(feature = "coverage")]
|
||||
#[arg(long, short)]
|
||||
coverage: bool,
|
||||
|
||||
/// Turn on Rego language v0.
|
||||
#[arg(long)]
|
||||
v0: bool,
|
||||
},
|
||||
|
||||
/// Tokenize a Rego policy.
|
||||
@@ -274,6 +282,7 @@ fn main() -> Result<()> {
|
||||
non_strict,
|
||||
#[cfg(feature = "coverage")]
|
||||
coverage,
|
||||
v0,
|
||||
} => rego_eval(
|
||||
&bundles,
|
||||
&data,
|
||||
@@ -283,6 +292,7 @@ fn main() -> Result<()> {
|
||||
non_strict,
|
||||
#[cfg(feature = "coverage")]
|
||||
coverage,
|
||||
v0,
|
||||
),
|
||||
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
|
||||
RegorusCommand::Parse { file } => rego_parse(file),
|
||||
|
||||
@@ -2,22 +2,22 @@ package example
|
||||
|
||||
default allow := false # unless otherwise defined, allow is false
|
||||
|
||||
allow := true { # allow is true if...
|
||||
allow := true if { # allow is true if...
|
||||
count(violation) == 0 # there are zero violations.
|
||||
}
|
||||
|
||||
violation[server.id] { # a server is in the violation set if...
|
||||
violation[server.id] if { # a server is in the violation set if...
|
||||
some server
|
||||
public_server[server] # it exists in the 'public_server' set and...
|
||||
server.protocols[_] == "http" # it contains the insecure "http" protocol.
|
||||
}
|
||||
|
||||
violation[server.id] { # a server is in the violation set if...
|
||||
violation[server.id] if { # a server is in the violation set if...
|
||||
server := input.servers[_] # it exists in the input.servers collection and...
|
||||
server.protocols[_] == "telnet" # it contains the "telnet" protocol.
|
||||
}
|
||||
|
||||
public_server[server] { # a server exists in the public_server set if...
|
||||
public_server[server]if { # a server exists in the public_server set if...
|
||||
some i, j
|
||||
server := input.servers[_] # it exists in the input.servers collection and...
|
||||
server.ports[_] == input.ports[i].id # it references a port in the input.ports collection and...
|
||||
|
||||
@@ -30,6 +30,11 @@ if [ -f Cargo.toml ]; then
|
||||
cargo test -r --test aci
|
||||
cargo test -r --test kata
|
||||
|
||||
# Ensure that all tests pass with extensions
|
||||
cargo test -r --features rego-extensions
|
||||
cargo test -r --test aci --features rego-extensions
|
||||
cargo test -r --test kata --features rego-extensions
|
||||
|
||||
# Ensure that OPA conformance tests don't regress.
|
||||
cargo test -r --features opa-testutil,serde_json/arbitrary_precision --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
cargo test -r --features opa-testutil,serde_json/arbitrary_precision,rego-extensions --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
fi
|
||||
|
||||
13
src/ast.rs
13
src/ast.rs
@@ -10,8 +10,8 @@ use core::{cmp, fmt, ops::Deref};
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub enum BinOp {
|
||||
And,
|
||||
Or,
|
||||
Intersection,
|
||||
Union,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
@@ -209,6 +209,13 @@ pub enum Expr {
|
||||
value: Ref<Expr>,
|
||||
collection: Ref<Expr>,
|
||||
},
|
||||
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
OrExpr {
|
||||
span: Span,
|
||||
lhs: Ref<Expr>,
|
||||
rhs: Ref<Expr>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
@@ -232,6 +239,8 @@ impl Expr {
|
||||
| ArithExpr { span, .. }
|
||||
| AssignExpr { span, .. }
|
||||
| Membership { span, .. } => span,
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
OrExpr { span, .. } => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,11 +337,11 @@ fn yaml_is_valid(
|
||||
fn yaml_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "yaml.marshal";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
Ok(Value::String(
|
||||
serde_yaml::to_string(&args[0])
|
||||
.with_context(|| span.error("could not serialize to yaml"))?
|
||||
.into(),
|
||||
))
|
||||
|
||||
let serialized = serde_yaml::to_string(&args[0])
|
||||
.map_err(|err| span.error(&format!("could not serialize to yaml: {}", err)))?;
|
||||
|
||||
Ok(Value::String(serialized.into()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "yaml")]
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::*;
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use rand::{thread_rng, Rng};
|
||||
use rand::Rng;
|
||||
|
||||
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("abs", (abs, 1));
|
||||
@@ -169,8 +169,7 @@ fn intn(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Res
|
||||
Some(0) => Value::from(0u64),
|
||||
Some(n) => {
|
||||
// TODO: bounds checking; arbitrary precision
|
||||
let mut rng = thread_rng();
|
||||
let v = rng.gen_range(0..n);
|
||||
let v = rand::rng().random_range(0..n);
|
||||
Value::from(v)
|
||||
}
|
||||
_ => Value::Undefined,
|
||||
|
||||
@@ -391,14 +391,14 @@ fn object_union_n(
|
||||
}
|
||||
|
||||
#[cfg(feature = "jsonschema")]
|
||||
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::JSONSchema> {
|
||||
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::Validator> {
|
||||
let schema_str = match arg {
|
||||
Value::String(schema_str) => schema_str.as_ref().to_string(),
|
||||
_ => arg.to_json_str()?,
|
||||
};
|
||||
|
||||
if let Ok(schema) = serde_json::from_str(&schema_str) {
|
||||
match jsonschema::JSONSchema::compile(&schema) {
|
||||
match jsonschema::validator_for(&schema) {
|
||||
Ok(schema) => return Ok(schema),
|
||||
Err(e) => bail!(e.to_string()),
|
||||
}
|
||||
@@ -439,16 +439,14 @@ fn json_match_schema(
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
|
||||
// The following is expected to succeed.
|
||||
let document: serde_json::Value = serde_json::from_str(&args[0].to_json_str()?)?;
|
||||
let document: serde_json::Value = serde_json::from_str(&args[0].to_json_str()?)
|
||||
.map_err(|err| span.error(&format!("Failed to parse JSON: {}", err)))?;
|
||||
|
||||
Ok(Value::from_array(
|
||||
match compile_json_schema(¶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) => [Value::Bool(false), Value::from(e.to_string())],
|
||||
},
|
||||
Err(e) if strict => bail!(params[1]
|
||||
.span()
|
||||
|
||||
@@ -25,7 +25,11 @@ fn sleep(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
|
||||
let dur = time::compat::parse_duration(val.as_ref())
|
||||
.map_err(|e| params[0].span().error(&format!("{e}")))?;
|
||||
|
||||
thread::sleep(dur.to_std()?);
|
||||
let std_dur = dur
|
||||
.to_std()
|
||||
.map_err(|err| anyhow::anyhow!("Failed to convert to std::time::Duration: {err}"))?;
|
||||
|
||||
thread::sleep(std_dur);
|
||||
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
@@ -158,7 +158,8 @@ fn parse_ns(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) ->
|
||||
let layout = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
let value = ensure_string(name, ¶ms[1], &args[1])?;
|
||||
|
||||
let datetime = compat::parse(layout_with_predefined_formats(&layout), &value)?;
|
||||
let datetime = compat::parse(layout_with_predefined_formats(&layout), &value)
|
||||
.map_err(|err| anyhow::anyhow!("Failed to parse datetime: {}", err))?;
|
||||
safe_timestamp_nanos(span, strict, datetime.timestamp_nanos_opt())
|
||||
}
|
||||
|
||||
@@ -173,7 +174,8 @@ fn parse_rfc3339_ns(
|
||||
|
||||
let value = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
|
||||
let datetime = DateTime::parse_from_rfc3339(&value)?;
|
||||
let datetime = DateTime::parse_from_rfc3339(&value)
|
||||
.map_err(|err| anyhow::anyhow!("Failed to parse datetime: {}", err))?;
|
||||
safe_timestamp_nanos(span, strict, datetime.timestamp_nanos_opt())
|
||||
}
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ struct GoTimeFormatItems<'a> {
|
||||
mode: GoTimeFormatItemsMode,
|
||||
}
|
||||
|
||||
impl<'a> GoTimeFormatItems<'a> {
|
||||
impl GoTimeFormatItems<'_> {
|
||||
fn parse(reminder: &str) -> GoTimeFormatItems {
|
||||
GoTimeFormatItems {
|
||||
reminder,
|
||||
|
||||
@@ -83,8 +83,8 @@ fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
|
||||
bail!(span.error("spaces not allowed in resource strings"));
|
||||
}
|
||||
|
||||
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) {
|
||||
Some(p) => (&string[0..p], &string[p..]),
|
||||
let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) {
|
||||
Some(p) => (&string[0..p + 1], &string[p + 1..]),
|
||||
_ => (string, ""),
|
||||
};
|
||||
|
||||
@@ -161,8 +161,8 @@ fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool)
|
||||
bail!(span.error("spaces not allowed in resource strings"));
|
||||
}
|
||||
|
||||
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) {
|
||||
Some(p) => (&string[0..p], &string[p..]),
|
||||
let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) {
|
||||
Some(p) => (&string[0..p + 1], &string[p + 1..]),
|
||||
_ => (string, ""),
|
||||
};
|
||||
|
||||
|
||||
@@ -132,16 +132,16 @@ fn timestamp(uuid: &Uuid) -> Option<Timestamp> {
|
||||
const fn decode_rfc4122_timestamp(uuid: &Uuid) -> (u64, u16) {
|
||||
let bytes = uuid.as_bytes();
|
||||
|
||||
let ticks: u64 = ((bytes[6] & 0x0F) as u64) << 56
|
||||
| (bytes[7] as u64) << 48
|
||||
| (bytes[4] as u64) << 40
|
||||
| (bytes[5] as u64) << 32
|
||||
| (bytes[0] as u64) << 24
|
||||
| (bytes[1] as u64) << 16
|
||||
| (bytes[2] as u64) << 8
|
||||
let ticks: u64 = (((bytes[6] & 0x0F) as u64) << 56)
|
||||
| ((bytes[7] as u64) << 48)
|
||||
| ((bytes[4] as u64) << 40)
|
||||
| ((bytes[5] as u64) << 32)
|
||||
| ((bytes[0] as u64) << 24)
|
||||
| ((bytes[1] as u64) << 16)
|
||||
| ((bytes[2] as u64) << 8)
|
||||
| (bytes[3] as u64);
|
||||
|
||||
let counter: u16 = ((bytes[8] & 0x3F) as u16) << 8 | (bytes[9] as u16);
|
||||
let counter: u16 = (((bytes[8] & 0x3F) as u16) << 8) | (bytes[9] as u16);
|
||||
|
||||
(ticks, counter)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ pub struct Engine {
|
||||
modules: Vec<Ref<Module>>,
|
||||
interpreter: Interpreter,
|
||||
prepared: bool,
|
||||
rego_v1: bool,
|
||||
}
|
||||
|
||||
/// Create a default engine.
|
||||
@@ -36,9 +37,39 @@ impl Engine {
|
||||
modules: vec![],
|
||||
interpreter: Interpreter::new(),
|
||||
prepared: false,
|
||||
rego_v1: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable rego v0.
|
||||
///
|
||||
/// Note that regorus now defaults to v1.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Enable v0 for old style policies.
|
||||
/// engine.set_rego_v0(true);
|
||||
///
|
||||
/// engine.add_policy(
|
||||
/// "test.rego".to_string(),
|
||||
/// r#"
|
||||
/// package test
|
||||
///
|
||||
/// allow { # v0 syntax does not require if keyword
|
||||
/// 1 < 2
|
||||
/// }
|
||||
/// "#.to_string())?;
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
pub fn set_rego_v0(&mut self, rego_v0: bool) {
|
||||
self.rego_v1 = !rego_v0;
|
||||
}
|
||||
|
||||
/// Add a policy.
|
||||
///
|
||||
/// The policy file will be parsed and converted to AST representation.
|
||||
@@ -67,7 +98,7 @@ impl Engine {
|
||||
///
|
||||
pub fn add_policy(&mut self, path: String, rego: String) -> Result<String> {
|
||||
let source = Source::from_contents(path, rego)?;
|
||||
let mut parser = Parser::new(&source)?;
|
||||
let mut parser = self.make_parser(&source)?;
|
||||
let module = Ref::new(parser.parse()?);
|
||||
self.modules.push(module.clone());
|
||||
// if policies change, interpreter needs to be prepared again
|
||||
@@ -87,6 +118,8 @@ impl Engine {
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
/// // framework.rego does not conform to v1.
|
||||
/// engine.set_rego_v0(true);
|
||||
///
|
||||
/// let package = engine.add_policy_from_file("tests/aci/framework.rego")?;
|
||||
///
|
||||
@@ -98,7 +131,7 @@ impl Engine {
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
pub fn add_policy_from_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<String> {
|
||||
let source = Source::from_file(path)?;
|
||||
let mut parser = Parser::new(&source)?;
|
||||
let mut parser = self.make_parser(&source)?;
|
||||
let module = Ref::new(parser.parse()?);
|
||||
self.modules.push(module.clone());
|
||||
// if policies change, interpreter needs to be prepared again
|
||||
@@ -112,6 +145,8 @@ impl Engine {
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
/// // framework.rego does not conform to v1.
|
||||
/// engine.set_rego_v0(true);
|
||||
///
|
||||
/// let _ = engine.add_policy_from_file("tests/aci/framework.rego")?;
|
||||
///
|
||||
@@ -387,6 +422,7 @@ impl Engine {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Add policies
|
||||
/// engine.set_rego_v0(true);
|
||||
/// engine.add_policy_from_file("tests/aci/framework.rego")?;
|
||||
/// engine.add_policy_from_file("tests/aci/api.rego")?;
|
||||
/// engine.add_policy_from_file("tests/aci/policy.rego")?;
|
||||
@@ -428,7 +464,7 @@ impl Engine {
|
||||
|
||||
// Parse the query.
|
||||
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
|
||||
let mut parser = Parser::new(&query_source)?;
|
||||
let mut parser = self.make_parser(&query_source)?;
|
||||
let query_node = parser.parse_user_query()?;
|
||||
if query_node.span.text() == "data" {
|
||||
self.eval_modules(enable_tracing)?;
|
||||
@@ -545,7 +581,7 @@ impl Engine {
|
||||
|
||||
// Parse the query.
|
||||
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
|
||||
let mut parser = Parser::new(&query_source)?;
|
||||
let mut parser = self.make_parser(&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(
|
||||
@@ -712,7 +748,7 @@ impl Engine {
|
||||
/// engine.add_policy(
|
||||
/// "policy.rego".to_string(),
|
||||
/// r#"package invalid
|
||||
/// x = y {
|
||||
/// x = y if {
|
||||
/// # y = do_magic(2)
|
||||
/// do_magic(2, y) # y is supplied as an out parameter.
|
||||
/// }
|
||||
@@ -748,7 +784,7 @@ impl Engine {
|
||||
/// r#"
|
||||
/// package test # Line 2
|
||||
///
|
||||
/// x = y { # Line 4
|
||||
/// x = y if { # Line 4
|
||||
/// input.a > 2 # Line 5
|
||||
/// y = 5 # Line 6
|
||||
/// }
|
||||
@@ -870,4 +906,12 @@ impl Engine {
|
||||
|
||||
serde_json::to_string_pretty(&ast).map_err(anyhow::Error::msg)
|
||||
}
|
||||
|
||||
fn make_parser<'a>(&self, source: &'a Source) -> Result<Parser<'a>> {
|
||||
let mut parser = Parser::new(source)?;
|
||||
if self.rego_v1 {
|
||||
parser.enable_rego_v1()?;
|
||||
}
|
||||
Ok(parser)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +484,12 @@ impl Interpreter {
|
||||
self.hoist_loops_impl(rhs, loops);
|
||||
}
|
||||
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
OrExpr { lhs, rhs, .. } => {
|
||||
self.hoist_loops_impl(lhs, loops);
|
||||
self.hoist_loops_impl(rhs, loops);
|
||||
}
|
||||
|
||||
Membership {
|
||||
key,
|
||||
value,
|
||||
@@ -554,8 +560,8 @@ impl Interpreter {
|
||||
}
|
||||
|
||||
match op {
|
||||
BinOp::Or => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
|
||||
BinOp::And => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
|
||||
BinOp::Union => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
|
||||
BinOp::Intersection => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1558,6 +1564,10 @@ impl Interpreter {
|
||||
let mut obj = &mut self.data;
|
||||
let len = path.len();
|
||||
for (idx, p) in path.into_iter().enumerate() {
|
||||
// Stop at the first undefined component in the path
|
||||
if p == Value::Undefined {
|
||||
break;
|
||||
}
|
||||
if idx == len - 1 {
|
||||
// last key.
|
||||
if is_set {
|
||||
@@ -1692,6 +1702,7 @@ impl Interpreter {
|
||||
}
|
||||
|
||||
if output == Value::Undefined || !comps_defined {
|
||||
ctx.rule_value = Value::Undefined;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -1871,14 +1882,14 @@ impl Interpreter {
|
||||
self.hoist_loops_impl(oe, &mut loops);
|
||||
}
|
||||
|
||||
self.eval_output_expr_in_loop(&loops[..])?;
|
||||
let r = self.eval_output_expr_in_loop(&loops[..])?;
|
||||
|
||||
let ctx = self.get_current_context()?;
|
||||
if let Some(_oe) = &ctx.output_expr {
|
||||
// Ensure that at least one output was generated.
|
||||
Ok(ctx.value != Value::Undefined)
|
||||
Ok(ctx.rule_value != Value::Undefined)
|
||||
} else {
|
||||
Ok(true)
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2278,15 +2289,8 @@ impl Interpreter {
|
||||
};
|
||||
|
||||
let mut param_values = Vec::with_capacity(params.len());
|
||||
let mut error = None;
|
||||
for p in params {
|
||||
match self.eval_expr(p) {
|
||||
Ok(v) => param_values.push(v),
|
||||
Err(e) => {
|
||||
error = Some(Err(e));
|
||||
break;
|
||||
}
|
||||
}
|
||||
param_values.push(self.eval_expr(p)?);
|
||||
}
|
||||
|
||||
let orig_fcn_path = fcn_path;
|
||||
@@ -2303,9 +2307,6 @@ impl Interpreter {
|
||||
if param_values.iter().any(|v| v == &Value::Undefined) {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
if let Some(err) = error {
|
||||
err?;
|
||||
};
|
||||
return Ok(v.clone());
|
||||
}
|
||||
_ => orig_fcn_path.clone(),
|
||||
@@ -2836,6 +2837,15 @@ impl Interpreter {
|
||||
..
|
||||
} => self.eval_membership(key, value, collection),
|
||||
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
Expr::OrExpr { lhs, rhs, .. } => {
|
||||
let lhs = self.eval_expr(lhs)?;
|
||||
match lhs {
|
||||
Value::Bool(false) | Value::Null | Value::Undefined => self.eval_expr(rhs),
|
||||
_ => Ok(lhs),
|
||||
}
|
||||
}
|
||||
|
||||
// Creation expression
|
||||
Expr::Array { items, .. } => self.eval_array(items),
|
||||
Expr::Object { fields, .. } => self.eval_object(fields),
|
||||
@@ -2948,7 +2958,6 @@ impl Interpreter {
|
||||
});
|
||||
}
|
||||
result = self.eval_query(&body.query);
|
||||
|
||||
if matches!(&result, Ok(true) | Err(_)) {
|
||||
break;
|
||||
}
|
||||
@@ -2961,10 +2970,7 @@ impl Interpreter {
|
||||
_ => bail!("internal error: rule's context already popped"),
|
||||
};
|
||||
|
||||
let result = match result {
|
||||
Ok(r) => r,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
let result = result?;
|
||||
|
||||
assert_eq!(self.scopes.len(), n_scopes);
|
||||
|
||||
@@ -3132,6 +3138,8 @@ impl Interpreter {
|
||||
ArithExpr { span, .. } => ("arithexpr", span),
|
||||
AssignExpr { span, .. } => ("assignexpr", span),
|
||||
Membership { span, .. } => ("membership", span),
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
OrExpr { span, .. } => ("orexpr", span),
|
||||
};
|
||||
|
||||
Err(span.error(format!("invalid `{kind}` in default value").as_str()))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::doc_lazy_continuation)]
|
||||
// Use README.md as crate documentation.
|
||||
@@ -357,7 +358,7 @@ where
|
||||
}
|
||||
|
||||
/// Implement clone for a boxed extension using [`Extension::clone_box`].
|
||||
impl<'a> Clone for Box<dyn 'a + Extension> {
|
||||
impl Clone for Box<dyn '_ + Extension> {
|
||||
fn clone(&self) -> Self {
|
||||
(**self).clone_box()
|
||||
}
|
||||
@@ -404,7 +405,6 @@ pub mod coverage {
|
||||
/// Lines that are not covered are red.
|
||||
///
|
||||
/// <img src="https://github.com/microsoft/regorus/blob/main/docs/coverage.png?raw=true">
|
||||
|
||||
pub fn to_string_pretty(&self) -> anyhow::Result<String> {
|
||||
let mut s = String::default();
|
||||
s.push_str("COVERAGE REPORT:\n");
|
||||
|
||||
106
src/parser.rs
106
src/parser.rs
@@ -19,7 +19,7 @@ pub struct Parser<'source> {
|
||||
tok: Token,
|
||||
line: u32,
|
||||
end: u32,
|
||||
future_keywords: BTreeMap<String, Span>,
|
||||
future_keywords: BTreeMap<String, Option<Span>>,
|
||||
rego_v1: bool,
|
||||
}
|
||||
|
||||
@@ -40,6 +40,18 @@ impl<'source> Parser<'source> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enable_rego_v1(&mut self) -> Result<()> {
|
||||
self.turn_on_rego_v1(&None)
|
||||
}
|
||||
|
||||
fn turn_on_rego_v1(&mut self, span: &Option<Span>) -> Result<()> {
|
||||
self.rego_v1 = true;
|
||||
for kw in FUTURE_KEYWORDS {
|
||||
self.set_future_keyword(kw, span)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn token_text(&self) -> &str {
|
||||
match self.tok.0 {
|
||||
TokenKind::Symbol | TokenKind::Number | TokenKind::Ident | TokenKind::Eof => {
|
||||
@@ -85,9 +97,9 @@ impl<'source> Parser<'source> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> {
|
||||
match &self.future_keywords.get(kw) {
|
||||
Some(s) if self.rego_v1 => Err(self.source.error(
|
||||
pub fn set_future_keyword(&mut self, kw: &str, span: &Option<Span>) -> Result<()> {
|
||||
match (span, self.future_keywords.get(kw)) {
|
||||
(Some(span), Some(Some(s))) if self.rego_v1 => Err(self.source.error(
|
||||
span.line,
|
||||
span.col,
|
||||
format!(
|
||||
@@ -143,11 +155,11 @@ impl<'source> Parser<'source> {
|
||||
fn handle_import_future_keywords(&mut self, comps: &[Span]) -> Result<bool> {
|
||||
if comps.len() >= 2 && comps[0].text() == "future" && comps[1].text() == "keywords" {
|
||||
match comps.len() - 2 {
|
||||
1 => self.set_future_keyword(comps[2].text(), &comps[2])?,
|
||||
1 => self.set_future_keyword(comps[2].text(), &Some(comps[2].clone()))?,
|
||||
0 => {
|
||||
let span = &comps[1];
|
||||
for kw in FUTURE_KEYWORDS.iter() {
|
||||
self.set_future_keyword(kw, span)?;
|
||||
self.set_future_keyword(kw, &Some(span.clone()))?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -487,7 +499,7 @@ impl<'source> Parser<'source> {
|
||||
|
||||
fn parse_parens_expr(&mut self) -> Result<Expr> {
|
||||
self.next_token()?;
|
||||
let expr = self.parse_membership_expr()?;
|
||||
let expr = self.parse_expr()?;
|
||||
self.expect(")", "while parsing parenthesized expression")?;
|
||||
//TODO: if needed introduce a parens-expr node or adjust expr's span.
|
||||
Ok(expr)
|
||||
@@ -688,7 +700,7 @@ impl<'source> Parser<'source> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_and_expr(&mut self) -> Result<Expr> {
|
||||
fn parse_set_intersection_expr(&mut self) -> Result<Expr> {
|
||||
let start = self.tok.1.start;
|
||||
let mut expr = self.parse_arith_expr()?;
|
||||
|
||||
@@ -700,7 +712,7 @@ impl<'source> Parser<'source> {
|
||||
span.end = self.end;
|
||||
expr = Expr::BinExpr {
|
||||
span,
|
||||
op: BinOp::And,
|
||||
op: BinOp::Intersection,
|
||||
lhs: Ref::new(expr),
|
||||
rhs: Ref::new(right),
|
||||
};
|
||||
@@ -708,19 +720,19 @@ impl<'source> Parser<'source> {
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn parse_or_expr(&mut self) -> Result<Expr> {
|
||||
fn parse_set_union_expr(&mut self) -> Result<Expr> {
|
||||
let start = self.tok.1.start;
|
||||
let mut expr = self.parse_and_expr()?;
|
||||
let mut expr = self.parse_set_intersection_expr()?;
|
||||
|
||||
while self.token_text() == "|" {
|
||||
let mut span = self.tok.1.clone();
|
||||
span.start = start;
|
||||
self.next_token()?;
|
||||
let right = self.parse_and_expr()?;
|
||||
let right = self.parse_set_intersection_expr()?;
|
||||
span.end = self.end;
|
||||
expr = Expr::BinExpr {
|
||||
span,
|
||||
op: BinOp::Or,
|
||||
op: BinOp::Union,
|
||||
lhs: Ref::new(expr),
|
||||
rhs: Ref::new(right),
|
||||
};
|
||||
@@ -730,7 +742,7 @@ impl<'source> Parser<'source> {
|
||||
|
||||
fn parse_bool_expr(&mut self) -> Result<Expr> {
|
||||
let start = self.tok.1.start;
|
||||
let mut expr = self.parse_or_expr()?;
|
||||
let mut expr = self.parse_set_union_expr()?;
|
||||
loop {
|
||||
let mut span = self.tok.1.clone();
|
||||
span.start = start;
|
||||
@@ -744,7 +756,7 @@ impl<'source> Parser<'source> {
|
||||
_ => break,
|
||||
};
|
||||
self.next_token()?;
|
||||
let right = self.parse_or_expr()?;
|
||||
let right = self.parse_set_union_expr()?;
|
||||
span.end = self.end;
|
||||
expr = Expr::BoolExpr {
|
||||
span,
|
||||
@@ -799,6 +811,32 @@ impl<'source> Parser<'source> {
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
pub fn parse_expr(&mut self) -> Result<Expr> {
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
return self.parse_or_expr();
|
||||
|
||||
#[cfg(not(feature = "rego-extensions"))]
|
||||
return self.parse_membership_expr();
|
||||
}
|
||||
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
pub fn parse_or_expr(&mut self) -> Result<Expr> {
|
||||
let start = self.tok.1.start;
|
||||
let mut expr = self.parse_membership_expr()?;
|
||||
while self.token_text() == "or" {
|
||||
let mut span = self.tok.1.clone();
|
||||
span.start = start;
|
||||
self.next_token()?;
|
||||
let rhs = self.parse_membership_expr()?;
|
||||
expr = Expr::OrExpr {
|
||||
span,
|
||||
lhs: Ref::new(expr),
|
||||
rhs: Ref::new(rhs),
|
||||
};
|
||||
}
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
pub fn parse_membership_expr(&mut self) -> Result<Expr> {
|
||||
let start = self.tok.1.start;
|
||||
let mut expr = self.parse_bool_expr()?;
|
||||
@@ -839,12 +877,12 @@ impl<'source> Parser<'source> {
|
||||
":=" => AssignOp::ColEq,
|
||||
_ => {
|
||||
*self = state;
|
||||
return self.parse_membership_expr();
|
||||
return self.parse_expr();
|
||||
}
|
||||
};
|
||||
|
||||
self.next_token()?;
|
||||
let right = self.parse_membership_expr()?;
|
||||
let right = self.parse_expr()?;
|
||||
span.end = self.end;
|
||||
Ok(Expr::AssignExpr {
|
||||
span,
|
||||
@@ -1026,6 +1064,29 @@ impl<'source> Parser<'source> {
|
||||
let mut literals = vec![];
|
||||
|
||||
let stmt = match self.parse_literal_stmt() {
|
||||
Ok(_) if self.token_text() == ":" => {
|
||||
// This is likely an object comprehension.
|
||||
// Restore the state and return.
|
||||
*self = state;
|
||||
bail!("try parsing as comprehension");
|
||||
}
|
||||
Ok(stmt) if self.token_text() == end_delim => {
|
||||
// Treat { 1 | 1 } as a comprehension instead of a
|
||||
// set of 1 element.
|
||||
if let Literal::Expr { expr: e, .. } = &stmt.literal {
|
||||
if matches!(
|
||||
e.as_ref(),
|
||||
Expr::BinExpr {
|
||||
op: BinOp::Union,
|
||||
..
|
||||
}
|
||||
) {
|
||||
*self = state;
|
||||
bail!("try parse as comprehension");
|
||||
}
|
||||
}
|
||||
stmt
|
||||
}
|
||||
Ok(stmt) => stmt,
|
||||
Err(e) if is_definite_query => return Err(e),
|
||||
Err(e) if matches!(self.token_text(), "=" | ":=") => return Err(e),
|
||||
@@ -1087,7 +1148,7 @@ impl<'source> Parser<'source> {
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
let expr = Ref::new(self.parse_membership_expr()?);
|
||||
let expr = Ref::new(self.parse_expr()?);
|
||||
span.end = self.end;
|
||||
Ok(Some(RuleAssign {
|
||||
span,
|
||||
@@ -1235,7 +1296,7 @@ impl<'source> Parser<'source> {
|
||||
}
|
||||
"[" => {
|
||||
self.next_token()?;
|
||||
let index = self.parse_membership_expr()?;
|
||||
let index = self.parse_expr()?;
|
||||
span.end = self.end;
|
||||
self.expect("]", "while parsing bracketed reference")?;
|
||||
term = Expr::RefBrack {
|
||||
@@ -1283,7 +1344,7 @@ impl<'source> Parser<'source> {
|
||||
}
|
||||
"contains" => {
|
||||
self.next_token()?;
|
||||
let key = Ref::new(self.parse_membership_expr()?);
|
||||
let key = Ref::new(self.parse_expr()?);
|
||||
span.end = self.end;
|
||||
Ok(RuleHead::Set {
|
||||
span,
|
||||
@@ -1648,10 +1709,7 @@ impl<'source> Parser<'source> {
|
||||
|
||||
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)?;
|
||||
}
|
||||
self.turn_on_rego_v1(&Some(span.clone()))?;
|
||||
true
|
||||
} else {
|
||||
self.handle_import_future_keywords(&comps)?
|
||||
|
||||
@@ -258,6 +258,12 @@ pub fn traverse(expr: &Ref<Expr>, f: &mut dyn FnMut(&Ref<Expr>) -> Result<bool>)
|
||||
traverse(rhs, f)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
OrExpr { lhs, rhs, .. } => {
|
||||
traverse(lhs, f)?;
|
||||
traverse(rhs, f)?;
|
||||
}
|
||||
|
||||
Membership {
|
||||
key,
|
||||
value,
|
||||
|
||||
@@ -141,6 +141,7 @@ pub fn eval_file(
|
||||
strict: bool,
|
||||
) -> Result<(Vec<Value>, Vec<String>)> {
|
||||
let mut engine: Engine = Engine::new();
|
||||
engine.set_rego_v0(true);
|
||||
engine.set_strict_builtin_errors(strict);
|
||||
engine.set_gather_prints(true);
|
||||
|
||||
@@ -385,6 +386,11 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
}
|
||||
|
||||
fn yaml_test(file: &str) -> Result<()> {
|
||||
#[cfg(not(feature = "rego-extensions"))]
|
||||
if file.contains("rego-extensions") {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match yaml_test_impl(file) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
|
||||
@@ -78,10 +78,7 @@ pub fn get_extra_arg(
|
||||
module: Option<&str>,
|
||||
functions: &FunctionTable,
|
||||
) -> Option<Ref<Expr>> {
|
||||
match get_extra_arg_impl(expr, module, functions) {
|
||||
Ok(a) => a,
|
||||
_ => None,
|
||||
}
|
||||
get_extra_arg_impl(expr, module, functions).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn gather_functions(modules: &[Ref<Module>]) -> Result<FunctionTable> {
|
||||
|
||||
184
src/value.rs
184
src/value.rs
@@ -322,7 +322,8 @@ impl Value {
|
||||
/// // Convert the value back to json.
|
||||
/// let json_str = value.to_json_str()?;
|
||||
///
|
||||
/// assert_eq!(json_str.trim(), std::fs::read_to_string("tests/aci/input.json")?.trim());
|
||||
/// assert_eq!(json_str.trim(),
|
||||
/// std::fs::read_to_string("tests/aci/input.json")?.trim().replace("\r\n", "\n"));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@@ -345,7 +346,8 @@ impl Value {
|
||||
/// // Convert the value back to json.
|
||||
/// let json_str = value.to_json_str()?;
|
||||
///
|
||||
/// assert_eq!(json_str.trim(), std::fs::read_to_string("tests/aci/input.json")?.trim());
|
||||
/// assert_eq!(json_str.trim(),
|
||||
/// std::fs::read_to_string("tests/aci/input.json")?.trim().replace("\r\n", "\n"));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@@ -403,7 +405,9 @@ impl Value {
|
||||
#[cfg(feature = "yaml")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
pub fn from_yaml_str(yaml: &str) -> Result<Value> {
|
||||
Ok(serde_yaml::from_str(yaml)?)
|
||||
let value = serde_yaml::from_str(yaml)
|
||||
.map_err(|err| anyhow::anyhow!("Failed to parse YAML: {}", err))?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Deserialize a value from a file containing YAML.
|
||||
@@ -902,6 +906,180 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast value to [`& u32`] if [`Value::Number`].
|
||||
///
|
||||
/// Error is raised if the value is not a number or if the numeric value
|
||||
/// does not fit in a u32.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let v = Value::from(10);
|
||||
/// assert_eq!(v.as_u32()?, 10u32);
|
||||
///
|
||||
/// let v = Value::from(-10);
|
||||
/// assert!(v.as_u32().is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn as_u32(&self) -> Result<u32> {
|
||||
match self {
|
||||
Value::Number(b) => {
|
||||
if let Some(n) = b.as_u64() {
|
||||
if let Ok(v) = u32::try_from(n) {
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
bail!("not a u32");
|
||||
}
|
||||
_ => Err(anyhow!("not a u32")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast value to [`& i32`] if [`Value::Number`].
|
||||
///
|
||||
/// Error is raised if the value is not a number or if the numeric value
|
||||
/// does not fit in a i32.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let v = Value::from(-10);
|
||||
/// assert_eq!(v.as_i32()?, -10i32);
|
||||
///
|
||||
/// let v = Value::from(2_147_483_648i64);
|
||||
/// assert!(v.as_i32().is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn as_i32(&self) -> Result<i32> {
|
||||
match self {
|
||||
Value::Number(b) => {
|
||||
if let Some(n) = b.as_i64() {
|
||||
if let Ok(v) = i32::try_from(n) {
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
bail!("not an i32");
|
||||
}
|
||||
_ => Err(anyhow!("not an i32")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast value to [`& u16`] if [`Value::Number`].
|
||||
///
|
||||
/// Error is raised if the value is not a number or if the numeric value
|
||||
/// does not fit in a u16.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let v = Value::from(10);
|
||||
/// assert_eq!(v.as_u16()?, 10u16);
|
||||
///
|
||||
/// let v = Value::from(-10);
|
||||
/// assert!(v.as_u16().is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn as_u16(&self) -> Result<u16> {
|
||||
match self {
|
||||
Value::Number(b) => {
|
||||
if let Some(n) = b.as_u64() {
|
||||
if let Ok(v) = u16::try_from(n) {
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
bail!("not a u16");
|
||||
}
|
||||
_ => Err(anyhow!("not a u16")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast value to [`& i16`] if [`Value::Number`].
|
||||
///
|
||||
/// Error is raised if the value is not a number or if the numeric value
|
||||
/// does not fit in a i16.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let v = Value::from(-10);
|
||||
/// assert_eq!(v.as_i16()?, -10i16);
|
||||
///
|
||||
/// let v = Value::from(32768i64);
|
||||
/// assert!(v.as_i16().is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn as_i16(&self) -> Result<i16> {
|
||||
match self {
|
||||
Value::Number(b) => {
|
||||
if let Some(n) = b.as_i64() {
|
||||
if let Ok(v) = i16::try_from(n) {
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
bail!("not an i16");
|
||||
}
|
||||
_ => Err(anyhow!("not an i16")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast value to [`& u8`] if [`Value::Number`].
|
||||
///
|
||||
/// Error is raised if the value is not a number or if the numeric value
|
||||
/// does not fit in a u8.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let v = Value::from(10);
|
||||
/// assert_eq!(v.as_u8()?, 10u8);
|
||||
///
|
||||
/// let v = Value::from(-10);
|
||||
/// assert!(v.as_u8().is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn as_u8(&self) -> Result<u8> {
|
||||
match self {
|
||||
Value::Number(b) => {
|
||||
if let Some(n) = b.as_u64() {
|
||||
if let Ok(v) = u8::try_from(n) {
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
bail!("not a u8");
|
||||
}
|
||||
_ => Err(anyhow!("not a u8")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast value to [`& i8`] if [`Value::Number`].
|
||||
///
|
||||
/// Error is raised if the value is not a number or if the numeric value
|
||||
/// does not fit in a i8.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let v = Value::from(-10);
|
||||
/// assert_eq!(v.as_i8()?, -10i8);
|
||||
///
|
||||
/// let v = Value::from(128);
|
||||
/// assert!(v.as_i8().is_err());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn as_i8(&self) -> Result<i8> {
|
||||
match self {
|
||||
Value::Number(b) => {
|
||||
if let Some(n) = b.as_i64() {
|
||||
if let Ok(v) = i8::try_from(n) {
|
||||
return Ok(v);
|
||||
}
|
||||
}
|
||||
bail!("not an i8");
|
||||
}
|
||||
_ => Err(anyhow!("not an i8")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast value to [`& f64`] if [`Value::Number`].
|
||||
/// Error is raised if the value is not a number or if the numeric value
|
||||
/// does not fit in a i64.
|
||||
|
||||
@@ -27,6 +27,7 @@ struct YamlTest {
|
||||
|
||||
fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> {
|
||||
let mut engine = Engine::new();
|
||||
engine.set_rego_v0(true);
|
||||
|
||||
engine.add_data(case.data.clone())?;
|
||||
engine.set_input(case.input.clone());
|
||||
@@ -116,6 +117,7 @@ fn run_aci_tests(dir: &Path) -> Result<()> {
|
||||
#[cfg(feature = "coverage")]
|
||||
fn run_aci_tests_coverage(dir: &Path) -> Result<()> {
|
||||
let mut engine = Engine::new();
|
||||
engine.set_rego_v0(true);
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
let mut added = std::collections::BTreeSet::new();
|
||||
|
||||
@@ -45,6 +45,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_enable_coverage(true);
|
||||
engine.set_rego_v0(true);
|
||||
|
||||
for (idx, rego) in case.modules.iter().enumerate() {
|
||||
engine.add_policy(format!("rego_{idx}"), rego.clone())?;
|
||||
|
||||
151
tests/interpreter/cases/builtins/strings/indexof.yaml
Normal file
151
tests/interpreter/cases/builtins/strings/indexof.yaml
Normal file
@@ -0,0 +1,151 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: base
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
v1 = indexof("Hello world", "llo") # valid substring
|
||||
v2 = indexof("Hello world", "hel") # case sensitive
|
||||
v3 = indexof("Hello world", "l") # single character
|
||||
v4 = indexof("", ",") # empty string
|
||||
v5 = indexof("", "") # empty substring and string
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
v1: 2
|
||||
v2: -1
|
||||
v3: 2
|
||||
v4: -1
|
||||
v5: -1
|
||||
|
||||
- note: unicode-char
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
v1 = indexof("μx", "x")
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
v1: 1
|
||||
|
||||
- note: unicode-chars-not-found
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
v1 = indexof("μ", "μμ")
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
v1: -1
|
||||
|
||||
- note: unicode-string
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
v1 = indexof("skön var våren", "vår")
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
v1: 9
|
||||
|
||||
- note: undefined-string
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x { false }
|
||||
y = indexof(x, "")
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: undefined-substring
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x { false }
|
||||
y = indexof(",", x)
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: invalid-null-string
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(null, ``)"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-bool-string
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(true, ``)"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-number-string
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(1, ``)"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-array-string
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof([], ``)"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-set-string
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(set(), ``)"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-object-string
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof({}, ``)"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-null-substring
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(``, null)"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-bool-substring
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(``, true)"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-number-substring
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(``, 1)"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-array-substring
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(``, [])"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-set-substring
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(``, set())"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
|
||||
- note: invalid-object-substring
|
||||
data: {}
|
||||
modules: ["package test\nx=indexof(``, {})"]
|
||||
query: data.test
|
||||
error: "`indexof` expects string argument."
|
||||
79
tests/interpreter/cases/builtins/strings/lower.yaml
Normal file
79
tests/interpreter/cases/builtins/strings/lower.yaml
Normal file
@@ -0,0 +1,79 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: base
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
v1 = lower("") # empty string
|
||||
v2 = lower("a")
|
||||
v3 = lower("A")
|
||||
v4 = lower("AbCd")
|
||||
v5 = lower("aBcD109")
|
||||
v6 = lower("aabAB09_")
|
||||
v7 = lower("longStrinGwitHmixofsmaLLandcAps")
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
v1: ""
|
||||
v2: "a"
|
||||
v3: "a"
|
||||
v4: "abcd"
|
||||
v5: "abcd109"
|
||||
v6: "aabab09_"
|
||||
v7: "longstringwithmixofsmallandcaps"
|
||||
|
||||
- note: unicode string
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
v1 = lower("Σ")
|
||||
v2 = lower("ὈΔΥΣΣΕΎΣ")
|
||||
v3 = lower("LONG\u2C6FSTRING\u2C6FWITH\u2C6FNONASCII\u2C6FCHARS")
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
v1: "σ"
|
||||
v2: "ὀδυσσεύς"
|
||||
v3: "long\u0250string\u0250with\u0250nonascii\u0250chars"
|
||||
|
||||
- note: invalid-null-string
|
||||
data: {}
|
||||
modules: ["package test\nx=lower(null)"]
|
||||
query: data.test
|
||||
error: "`lower` expects string argument."
|
||||
|
||||
- note: invalid-bool-string
|
||||
data: {}
|
||||
modules: ["package test\nx=lower(true)"]
|
||||
query: data.test
|
||||
error: "`lower` expects string argument."
|
||||
|
||||
- note: invalid-number-string
|
||||
data: {}
|
||||
modules: ["package test\nx=lower(1)"]
|
||||
query: data.test
|
||||
error: "`lower` expects string argument."
|
||||
|
||||
- note: invalid-array-string
|
||||
data: {}
|
||||
modules: ["package test\nx=lower([])"]
|
||||
query: data.test
|
||||
error: "`lower` expects string argument."
|
||||
|
||||
- note: invalid-set-string
|
||||
data: {}
|
||||
modules: ["package test\nx=lower(set())"]
|
||||
query: data.test
|
||||
error: "`lower` expects string argument."
|
||||
|
||||
- note: invalid-object-string
|
||||
data: {}
|
||||
modules: ["package test\nx=lower({})"]
|
||||
query: data.test
|
||||
error: "`lower` expects string argument."
|
||||
@@ -59,3 +59,20 @@ cases:
|
||||
a1 = inc(5)
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: call parameter raises error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
bar := 1 if {
|
||||
1 + "hello"
|
||||
}
|
||||
foo := 1 if {
|
||||
count(bar)
|
||||
}
|
||||
query: data.test
|
||||
error: expects numeric argument.
|
||||
|
||||
|
||||
45
tests/interpreter/cases/compr/tricky.yaml
Normal file
45
tests/interpreter/cases/compr/tricky.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: treat { 1 | 1 } as comprehension
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
x if { 1 | 1 }
|
||||
y := { 1 | 1 }
|
||||
z := { (1) | 1}
|
||||
|
||||
# Parsed as a set
|
||||
a := { ({1} | {2}) }
|
||||
|
||||
b := v if { { 1 | 1 } = v }
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
x: true
|
||||
y:
|
||||
set!: [1]
|
||||
z:
|
||||
set!: [1]
|
||||
a:
|
||||
set!:
|
||||
- set!: [1, 2]
|
||||
b:
|
||||
set!: [1]
|
||||
|
||||
- note: rule body is object comprehension
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import future.keywords
|
||||
x if { 1:2 | some p in [1,2] }
|
||||
y := 2 if { 1:2 | some p in [1,2] }
|
||||
query: data.test
|
||||
want_result:
|
||||
x: true
|
||||
y: 2
|
||||
52
tests/interpreter/cases/rego-extensions/or/tests.yaml
Normal file
52
tests/interpreter/cases/rego-extensions/or/tests.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
cases:
|
||||
- note: basic
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
x := data.foo or 2 # undefined lhs
|
||||
y := false or 3 # false rhs
|
||||
z := null or 4
|
||||
a := data.foo or false or null or 5
|
||||
b := startswith("a", "b") or startswith("a", "a")
|
||||
c := 5 in [1,2] or 6 in [6]
|
||||
d := x if {
|
||||
x := false or [1, 2][_]
|
||||
x > 1
|
||||
}
|
||||
e if 1 > 2 or false
|
||||
query: data.test
|
||||
want_result:
|
||||
x: 2
|
||||
y: 3
|
||||
z: 4
|
||||
a: 5
|
||||
b: true
|
||||
c: true
|
||||
d: 2
|
||||
- note: Azure Policy
|
||||
modules:
|
||||
- |
|
||||
package policy
|
||||
|
||||
effect := parameters.effect if {
|
||||
resource.type == "Microsoft.Storage/storageaccounts"
|
||||
resource.properties.networkAcls.defaultAction == "Deny"
|
||||
or count(resource.properties.networkAcls.ipRules) >= 1
|
||||
}
|
||||
|
||||
resource := input.resource
|
||||
parameters := input.parameters
|
||||
input:
|
||||
resource:
|
||||
type: "Microsoft.Storage/storageaccounts"
|
||||
properties:
|
||||
networksAcls:
|
||||
ipRules: ["rule1", "rule2"]
|
||||
parameters:
|
||||
effect: "Deny"
|
||||
query: data.policy.effect
|
||||
want_result: "Deny"
|
||||
@@ -2,17 +2,34 @@
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: else without body
|
||||
# - note: else without body
|
||||
# data: {}
|
||||
# modules:
|
||||
# - |
|
||||
# package test
|
||||
# x = 4 {
|
||||
# false
|
||||
# } else = 5
|
||||
|
||||
# y = 6
|
||||
# query: data.test
|
||||
# want_result:
|
||||
# x: 5
|
||||
# y: 6
|
||||
- note: undefined values being assigned
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = 4 {
|
||||
false
|
||||
} else = 5
|
||||
|
||||
y = 6
|
||||
|
||||
import rego.v1
|
||||
|
||||
x := data.y if {
|
||||
true
|
||||
} else := 2 if {
|
||||
true
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
x: 5
|
||||
y: 6
|
||||
x: 2
|
||||
|
||||
|
||||
26
tests/interpreter/cases/rule/generic.yaml
Normal file
26
tests/interpreter/cases/rule/generic.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: undefined components
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
import rego.v1
|
||||
|
||||
principal := input.principal
|
||||
action := input.action
|
||||
|
||||
p[principal][action] := 1 if {
|
||||
some a in []
|
||||
}
|
||||
|
||||
q[principal][action] contains 1 if {
|
||||
some a in []
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
p: {}
|
||||
q: {}
|
||||
@@ -9,6 +9,16 @@ use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
fn normalize_printed_paths(mut prints: Vec<String>) -> Vec<String> {
|
||||
prints.iter_mut().for_each(|p| {
|
||||
*p = p
|
||||
.replace("\\", "/")
|
||||
.replace("//", "/")
|
||||
.replace("\r\n", "\n");
|
||||
});
|
||||
prints
|
||||
}
|
||||
|
||||
fn run_kata_tests(
|
||||
tests_dir: &Path,
|
||||
name: &Option<String>,
|
||||
@@ -43,6 +53,7 @@ fn run_kata_tests(
|
||||
let prints_file = path.join("prints.json");
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_rego_v0(true);
|
||||
engine.add_policy_from_file(&policy_file)?;
|
||||
engine.set_gather_prints(true);
|
||||
engine.set_strict_builtin_errors(false);
|
||||
@@ -110,14 +121,14 @@ fn run_kata_tests(
|
||||
|
||||
if generate {
|
||||
results.push(r);
|
||||
prints.push(engine.take_prints()?);
|
||||
prints.push(normalize_printed_paths(engine.take_prints()?));
|
||||
} else {
|
||||
let expected = results.pop().unwrap();
|
||||
assert_eq!(r, expected, "{lineno} failed in {}", inputs_file.display());
|
||||
|
||||
let p = engine.take_prints()?;
|
||||
assert_eq!(p, new_engine.take_prints()?);
|
||||
assert_eq!(p, prints.pop().unwrap());
|
||||
let p = normalize_printed_paths(engine.take_prints()?);
|
||||
assert_eq!(p, normalize_printed_paths(new_engine.take_prints()?));
|
||||
assert_eq!(p, normalize_printed_paths(prints.pop().unwrap()));
|
||||
}
|
||||
|
||||
num_queries += 2;
|
||||
@@ -179,7 +190,6 @@ fn stateful_policy_test() -> Result<()> {
|
||||
let policy = String::from(
|
||||
r#"
|
||||
package example
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
|
||||
@@ -1,115 +1,230 @@
|
||||
aggregates
|
||||
all
|
||||
any
|
||||
arithmetic
|
||||
array
|
||||
assignments
|
||||
base64builtins
|
||||
base64urlbuiltins
|
||||
baseandvirtualdocs
|
||||
bitsand
|
||||
bitsnegate
|
||||
bitsor
|
||||
bitsshiftleft
|
||||
bitsshiftright
|
||||
bitsxor
|
||||
casts
|
||||
comparisonexpr
|
||||
completedoc
|
||||
compositebasedereference
|
||||
compositereferences
|
||||
comprehensions
|
||||
containskeyword
|
||||
cryptohmacequal
|
||||
cryptohmacmd5
|
||||
cryptohmacsha1
|
||||
cryptohmacsha256
|
||||
cryptohmacsha512
|
||||
cryptomd5
|
||||
cryptosha1
|
||||
cryptosha256
|
||||
dataderef
|
||||
defaultkeyword
|
||||
disjunction
|
||||
elsekeyword
|
||||
embeddedvirtualdoc
|
||||
eqexpr
|
||||
evaltermexpr
|
||||
every
|
||||
example
|
||||
fix1863
|
||||
functionerrors
|
||||
functions
|
||||
globmatch
|
||||
globquotemeta
|
||||
helloworld
|
||||
hexbuiltins
|
||||
indexing
|
||||
indirectreferences
|
||||
inputvalues
|
||||
intersection
|
||||
jsonbuiltins
|
||||
jsonfilter
|
||||
jsonfilteridempotent
|
||||
jsonremove
|
||||
jsonremoveidempotent
|
||||
jsonschema
|
||||
jwtbuiltins
|
||||
negation
|
||||
nestedreferences
|
||||
numbersrange
|
||||
numbersrangestep
|
||||
objectfilter
|
||||
objectfilteridempotent
|
||||
objectfilternonstringkey
|
||||
objectget
|
||||
objectkeys
|
||||
objectremove
|
||||
objectremoveidempotent
|
||||
objectremovenonstringkey
|
||||
objectunion
|
||||
objectunionn
|
||||
partialdocconstants
|
||||
partialiter
|
||||
partialobjectdoc
|
||||
partialsetdoc
|
||||
planner-ir
|
||||
rand
|
||||
reachable
|
||||
refheads
|
||||
regexfind
|
||||
regexfindallstringsubmatch
|
||||
regexisvalid
|
||||
regexmatch
|
||||
regexmatchtemplate
|
||||
regexreplace
|
||||
regexsplit
|
||||
replacen
|
||||
semvercompare
|
||||
semverisvalid
|
||||
sets
|
||||
sprintf
|
||||
strings
|
||||
subset
|
||||
toarray
|
||||
topdowndynamicdispatch
|
||||
toset
|
||||
time
|
||||
trim
|
||||
trimleft
|
||||
trimprefix
|
||||
trimright
|
||||
trimspace
|
||||
trimsuffix
|
||||
type
|
||||
typebuiltin
|
||||
typenamebuiltin
|
||||
undos
|
||||
union
|
||||
units
|
||||
urlbuiltins
|
||||
uuid
|
||||
varreferences
|
||||
virtualdocs
|
||||
walkbuiltin
|
||||
withkeyword
|
||||
v0/aggregates
|
||||
v0/all
|
||||
v0/any
|
||||
v0/arithmetic
|
||||
v0/array
|
||||
v0/assignments
|
||||
v0/base64builtins
|
||||
v0/base64urlbuiltins
|
||||
v0/baseandvirtualdocs
|
||||
v0/bitsand
|
||||
v0/bitsnegate
|
||||
v0/bitsor
|
||||
v0/bitsshiftleft
|
||||
v0/bitsshiftright
|
||||
v0/bitsxor
|
||||
v0/casts
|
||||
v0/comparisonexpr
|
||||
v0/completedoc
|
||||
v0/compositebasedereference
|
||||
v0/compositereferences
|
||||
v0/comprehensions
|
||||
v0/containskeyword
|
||||
v0/cryptohmacequal
|
||||
v0/cryptohmacmd5
|
||||
v0/cryptohmacsha1
|
||||
v0/cryptohmacsha256
|
||||
v0/cryptohmacsha512
|
||||
v0/cryptomd5
|
||||
v0/cryptosha1
|
||||
v0/cryptosha256
|
||||
v0/dataderef
|
||||
v0/defaultkeyword
|
||||
v0/disjunction
|
||||
v0/elsekeyword
|
||||
v0/embeddedvirtualdoc
|
||||
v0/eqexpr
|
||||
v0/evaltermexpr
|
||||
v0/every
|
||||
v0/example
|
||||
v0/fix1863
|
||||
v0/functionerrors
|
||||
v0/functions
|
||||
v0/globmatch
|
||||
v0/globquotemeta
|
||||
v0/helloworld
|
||||
v0/hexbuiltins
|
||||
v0/indexing
|
||||
v0/indirectreferences
|
||||
v0/inputvalues
|
||||
v0/intersection
|
||||
v0/jsonbuiltins
|
||||
v0/jsonfilter
|
||||
v0/jsonfilteridempotent
|
||||
v0/jsonremove
|
||||
v0/jsonremoveidempotent
|
||||
v0/jsonschema
|
||||
v0/jwtbuiltins
|
||||
v0/negation
|
||||
v0/nestedreferences
|
||||
v0/numbersrange
|
||||
v0/numbersrangestep
|
||||
v0/objectfilter
|
||||
v0/objectfilteridempotent
|
||||
v0/objectfilternonstringkey
|
||||
v0/objectget
|
||||
v0/objectkeys
|
||||
v0/objectremove
|
||||
v0/objectremoveidempotent
|
||||
v0/objectremovenonstringkey
|
||||
v0/objectunion
|
||||
v0/objectunionn
|
||||
v0/partialdocconstants
|
||||
v0/partialiter
|
||||
v0/partialobjectdoc
|
||||
v0/partialsetdoc
|
||||
v0/planner-ir
|
||||
v0/rand
|
||||
v0/reachable
|
||||
v0/refheads
|
||||
v0/regexfind
|
||||
v0/regexfindallstringsubmatch
|
||||
v0/regexisvalid
|
||||
v0/regexmatch
|
||||
v0/regexmatchtemplate
|
||||
v0/regexreplace
|
||||
v0/regexsplit
|
||||
v0/replacen
|
||||
v0/semvercompare
|
||||
v0/semverisvalid
|
||||
v0/sets
|
||||
v0/sprintf
|
||||
v0/strings
|
||||
v0/subset
|
||||
v0/toarray
|
||||
v0/topdowndynamicdispatch
|
||||
v0/toset
|
||||
v0/time
|
||||
v0/trim
|
||||
v0/trimleft
|
||||
v0/trimprefix
|
||||
v0/trimright
|
||||
v0/trimspace
|
||||
v0/trimsuffix
|
||||
v0/type
|
||||
v0/typebuiltin
|
||||
v0/typenamebuiltin
|
||||
v0/undos
|
||||
v0/union
|
||||
v0/units
|
||||
v0/urlbuiltins
|
||||
v0/uuid
|
||||
v0/varreferences
|
||||
v0/virtualdocs
|
||||
v0/walkbuiltin
|
||||
v0/withkeyword
|
||||
v1/aggregates
|
||||
v1/all
|
||||
v1/any
|
||||
v1/arithmetic
|
||||
v1/array
|
||||
v1/assignments
|
||||
v1/base64builtins
|
||||
v1/base64urlbuiltins
|
||||
v1/baseandvirtualdocs
|
||||
v1/bitsand
|
||||
v1/bitsnegate
|
||||
v1/bitsor
|
||||
v1/bitsshiftleft
|
||||
v1/bitsshiftright
|
||||
v1/bitsxor
|
||||
v1/casts
|
||||
v1/comparisonexpr
|
||||
v1/completedoc
|
||||
v1/compositebasedereference
|
||||
v1/compositereferences
|
||||
v1/comprehensions
|
||||
v1/containskeyword
|
||||
v1/cryptohmacequal
|
||||
v1/cryptohmacmd5
|
||||
v1/cryptohmacsha1
|
||||
v1/cryptohmacsha256
|
||||
v1/cryptohmacsha512
|
||||
v1/cryptomd5
|
||||
v1/cryptosha1
|
||||
v1/cryptosha256
|
||||
v1/dataderef
|
||||
v1/defaultkeyword
|
||||
v1/disjunction
|
||||
v1/elsekeyword
|
||||
v1/embeddedvirtualdoc
|
||||
v1/eqexpr
|
||||
v1/evaltermexpr
|
||||
v1/every
|
||||
v1/example
|
||||
v1/fix1863
|
||||
v1/functionerrors
|
||||
v1/functions
|
||||
v1/globmatch
|
||||
v1/globquotemeta
|
||||
v1/helloworld
|
||||
v1/hexbuiltins
|
||||
v1/indexing
|
||||
v1/indirectreferences
|
||||
v1/inputvalues
|
||||
v1/intersection
|
||||
v1/jsonbuiltins
|
||||
v1/jsonfilter
|
||||
v1/jsonfilteridempotent
|
||||
v1/jsonremove
|
||||
v1/jsonremoveidempotent
|
||||
v1/jsonschema
|
||||
v1/jwtbuiltins
|
||||
v1/negation
|
||||
v1/nestedreferences
|
||||
v1/numbersrange
|
||||
v1/numbersrangestep
|
||||
v1/objectfilter
|
||||
v1/objectfilteridempotent
|
||||
v1/objectfilternonstringkey
|
||||
v1/objectget
|
||||
v1/objectkeys
|
||||
v1/objectremove
|
||||
v1/objectremoveidempotent
|
||||
v1/objectremovenonstringkey
|
||||
v1/objectunion
|
||||
v1/objectunionn
|
||||
v1/partialdocconstants
|
||||
v1/partialiter
|
||||
v1/partialobjectdoc
|
||||
v1/partialsetdoc
|
||||
v1/planner-ir
|
||||
v1/rand
|
||||
v1/reachable
|
||||
v1/refheads
|
||||
v1/regexfind
|
||||
v1/regexfindallstringsubmatch
|
||||
v1/regexisvalid
|
||||
v1/regexmatch
|
||||
v1/regexmatchtemplate
|
||||
v1/regexreplace
|
||||
v1/regexsplit
|
||||
v1/replacen
|
||||
v1/semvercompare
|
||||
v1/semverisvalid
|
||||
v1/sets
|
||||
v1/sprintf
|
||||
v1/strings
|
||||
v1/subset
|
||||
v1/toarray
|
||||
v1/topdowndynamicdispatch
|
||||
v1/toset
|
||||
v1/time
|
||||
v1/trim
|
||||
v1/trimleft
|
||||
v1/trimprefix
|
||||
v1/trimright
|
||||
v1/trimspace
|
||||
v1/trimsuffix
|
||||
v1/type
|
||||
v1/typebuiltin
|
||||
v1/typenamebuiltin
|
||||
v1/undos
|
||||
v1/union
|
||||
v1/units
|
||||
v1/urlbuiltins
|
||||
v1/uuid
|
||||
v1/varreferences
|
||||
v1/virtualdocs
|
||||
v1/walkbuiltin
|
||||
v1/withkeyword
|
||||
|
||||
15
tests/opa.rs
15
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.67.0";
|
||||
const OPA_BRANCH: &str = "v1.2.0";
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -51,12 +51,14 @@ struct YamlTest {
|
||||
cases: Vec<TestCase>,
|
||||
}
|
||||
|
||||
fn eval_test_case(case: &TestCase) -> Result<Value> {
|
||||
fn eval_test_case(case: &TestCase, is_rego_v0_test: bool) -> Result<Value> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
engine.set_rego_v0(is_rego_v0_test);
|
||||
|
||||
if let Some(data) = &case.data {
|
||||
engine.add_data(data.clone())?;
|
||||
}
|
||||
@@ -172,6 +174,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_rego_v0_test = path_dir_str.starts_with("v0/") || path_dir.starts_with("v0\\");
|
||||
let entry = status.entry(path_dir_str).or_insert((0, 0, 0));
|
||||
|
||||
let yaml_str = std::fs::read_to_string(&path_str)?;
|
||||
@@ -216,7 +219,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
|
||||
|
||||
print!("{:4}: {:90}", entry.2, case.note);
|
||||
entry.2 += 1;
|
||||
match (eval_test_case(&case), &case.want_result) {
|
||||
match (eval_test_case(&case, is_rego_v0_test), &case.want_result) {
|
||||
(Ok(actual), Some(expected))
|
||||
if is_json_schema_test && json_schema_tests_check(&actual, &expected) =>
|
||||
{
|
||||
@@ -291,6 +294,10 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
if is_rego_v0_test {
|
||||
cmd += " -v0";
|
||||
}
|
||||
|
||||
std::fs::write(path.join(format!("query{n}.text")), case.query.as_bytes())?;
|
||||
cmd += format!(" \"{}\"", &case.query).as_str();
|
||||
|
||||
@@ -391,7 +398,7 @@ fn main() -> Result<()> {
|
||||
bail!("failed to clone OPA repository");
|
||||
}
|
||||
}
|
||||
format!("{branch_dir}/test/cases/testdata")
|
||||
format!("{branch_dir}/v1/test/cases/testdata")
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -198,6 +198,13 @@ fn match_expr_impl(e: &Expr, v: &Value) -> Result<()> {
|
||||
match_expr(value, &v["inexpr"]["value"])?;
|
||||
match_expr(collection, &v["inexpr"]["collection"])
|
||||
}
|
||||
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
Expr::OrExpr { span, lhs, rhs } => {
|
||||
match_span_opt(span, &v["orexpr"]["span"])?;
|
||||
match_expr(lhs, &v["orexpr"]["lhs"])?;
|
||||
match_expr(rhs, &v["orexpr"]["rhs"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,8 +331,8 @@ fn match_expr_opt(s: &Span, e: &Option<Ref<Expr>>, v: &Value) -> Result<()> {
|
||||
|
||||
fn match_bin_op(s: &Span, op: &BinOp, v: &Value) -> Result<()> {
|
||||
match (op, v) {
|
||||
(BinOp::And, Value::String(s)) if s.as_ref() == "&" => Ok(()),
|
||||
(BinOp::Or, Value::String(s)) if s.as_ref() == "|" => Ok(()),
|
||||
(BinOp::Intersection, Value::String(s)) if s.as_ref() == "&" => Ok(()),
|
||||
(BinOp::Union, Value::String(s)) if s.as_ref() == "|" => Ok(()),
|
||||
_ => bail!(
|
||||
"{}",
|
||||
s.source.message(
|
||||
|
||||
Reference in New Issue
Block a user