mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
49 Commits
regorus-v0
...
regorus-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c56da34843 | ||
|
|
61f82d1b34 | ||
|
|
00f45c70fe | ||
|
|
df73b20192 | ||
|
|
992b202f60 | ||
|
|
ce6ecd6fd6 | ||
|
|
37262ccf8f | ||
|
|
dcd040cf40 | ||
|
|
f0a3cf26a0 | ||
|
|
13d8289a58 | ||
|
|
d2b27ee512 | ||
|
|
adb9da0c0c | ||
|
|
dc0f48f6bc | ||
|
|
334db3d6ce | ||
|
|
7565ec3ecf | ||
|
|
8498274356 | ||
|
|
ecd341bbcc | ||
|
|
b6935d1add | ||
|
|
502b830c19 | ||
|
|
8003cfc5b7 | ||
|
|
a4a80d7fc6 | ||
|
|
af5071446b | ||
|
|
edd0ccca5b | ||
|
|
fec6f8f8b4 | ||
|
|
dff65f0329 | ||
|
|
6bf40c7394 | ||
|
|
a488a84969 | ||
|
|
e4a58ad1dc | ||
|
|
430a453fde | ||
|
|
ef549a6528 | ||
|
|
52afcbe5c5 | ||
|
|
f0576cef77 | ||
|
|
20eece58ed | ||
|
|
6599ce6001 | ||
|
|
7095e269b7 | ||
|
|
6e1f8cdb36 | ||
|
|
fb5151e0e4 | ||
|
|
37d283cb38 | ||
|
|
25dbd27d82 | ||
|
|
292948a694 | ||
|
|
5a0048cd64 | ||
|
|
4a2a724a80 | ||
|
|
46e28b36f8 | ||
|
|
ee898e112e | ||
|
|
e6f2ec825d | ||
|
|
45627aa64a | ||
|
|
df98c8d168 | ||
|
|
25902bab57 | ||
|
|
e62bfdf161 |
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)
|
||||
|
||||
5
.github/workflows/test-c-cpp.yml
vendored
5
.github/workflows/test-c-cpp.yml
vendored
@@ -18,6 +18,11 @@ jobs:
|
||||
- name: Setup gcc, g++, cmake, ninja
|
||||
run: sudo apt update && sudo apt install -y gcc g++ cmake ninja-build
|
||||
|
||||
- name: Workaround to ensure that regorus.h is generated
|
||||
run: |
|
||||
cargo build -r
|
||||
working-directory: ./bindings/ffi
|
||||
|
||||
- name: Test c binding
|
||||
run: |
|
||||
mkdir bindings/c/build
|
||||
|
||||
4
.github/workflows/test-csharp.yml
vendored
4
.github/workflows/test-csharp.yml
vendored
@@ -19,8 +19,8 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
working-directory: ./bindings/csharp
|
||||
working-directory: ./bindings/csharp/net8.0
|
||||
|
||||
- name: Run
|
||||
run: LD_LIBRARY_PATH=. dotnet run
|
||||
working-directory: ./bindings/csharp
|
||||
working-directory: ./bindings/csharp/net8.0
|
||||
|
||||
28
.github/workflows/test-csharp40.yml
vendored
Normal file
28
.github/workflows/test-csharp40.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: bindings/csharp40
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: "5.0.x"
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
working-directory: ./bindings/csharp/net40
|
||||
|
||||
- name: Run
|
||||
run: dotnet run
|
||||
working-directory: ./bindings/csharp/net40
|
||||
2
.github/workflows/test-ffi.yml
vendored
2
.github/workflows/test-ffi.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: bindings/c-cpp
|
||||
name: bindings/ffi
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
4
.github/workflows/test-java.yml
vendored
4
.github/workflows/test-java.yml
vendored
@@ -32,6 +32,6 @@ jobs:
|
||||
|
||||
- name: Test jar
|
||||
run: |
|
||||
javac -cp target/regorus-java-0.1.5.jar Test.java
|
||||
java -Djava.library.path=../../target/release -cp target/regorus-java-0.1.5.jar:. Test
|
||||
javac -cp target/regorus-java-0.2.2.jar Test.java
|
||||
java -Djava.library.path=../../target/release -cp target/regorus-java-0.2.2.jar:. Test
|
||||
working-directory: ./bindings/java
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
**/wheels/
|
||||
**/__pycache__/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
|
||||
88
CHANGELOG.md
88
CHANGELOG.md
@@ -6,6 +6,93 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [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
|
||||
- Match OPA behavior for split ([#295](https://github.com/microsoft/regorus/pull/295))
|
||||
- Merge data to init document ([#293](https://github.com/microsoft/regorus/pull/293))
|
||||
|
||||
### Other
|
||||
- Update cbindgen requirement from 0.26.0 to 0.27.0 ([#296](https://github.com/microsoft/regorus/pull/296))
|
||||
- Bump rexml in /bindings/ruby in the bundler group across 1 directory ([#294](https://github.com/microsoft/regorus/pull/294))
|
||||
- Update csbindgen requirement from =1.9.0 to =1.9.3 ([#292](https://github.com/microsoft/regorus/pull/292))
|
||||
|
||||
## [0.2.2](https://github.com/microsoft/regorus/compare/regorus-v0.2.1...regorus-v0.2.2) - 2024-07-28
|
||||
|
||||
### Added
|
||||
- Update to opa v0.67.0 ([#286](https://github.com/microsoft/regorus/pull/286))
|
||||
|
||||
### Fixed
|
||||
- Handle aliases in scheduler ([#285](https://github.com/microsoft/regorus/pull/285))
|
||||
|
||||
### Other
|
||||
- Update readme ([#288](https://github.com/microsoft/regorus/pull/288))
|
||||
- Update binding versions ([#287](https://github.com/microsoft/regorus/pull/287))
|
||||
- build.rs create hooks dir if not exists ([#283](https://github.com/microsoft/regorus/pull/283))
|
||||
- add extension_list example ([#281](https://github.com/microsoft/regorus/pull/281))
|
||||
- Fix build break ([#278](https://github.com/microsoft/regorus/pull/278))
|
||||
- Update pyo3 requirement from 0.21.0 to 0.22.0 ([#275](https://github.com/microsoft/regorus/pull/275))
|
||||
- Update to OPA v0.66.0 ([#274](https://github.com/microsoft/regorus/pull/274))
|
||||
|
||||
## [0.2.1](https://github.com/microsoft/regorus/compare/regorus-v0.2.0...regorus-v0.2.1) - 2024-06-19
|
||||
|
||||
### Added
|
||||
- get_policies: Way to obtain policy files and content ([#267](https://github.com/microsoft/regorus/pull/267))
|
||||
|
||||
### Other
|
||||
- Fix c,cpp,no-std binding examples ([#272](https://github.com/microsoft/regorus/pull/272))
|
||||
- Update binding versions for next release ([#270](https://github.com/microsoft/regorus/pull/270))
|
||||
- rename method from 'Clone' to 'clone' in 'Engine' class to match the java naming convention and definiont in the of java.lang.Object. ([#268](https://github.com/microsoft/regorus/pull/268))
|
||||
- Suppress clippy unused warning ([#269](https://github.com/microsoft/regorus/pull/269))
|
||||
- Provide ability to get JSON representation of policy AST ([#266](https://github.com/microsoft/regorus/pull/266))
|
||||
- Update OPA tests to v0.65.0 ([#264](https://github.com/microsoft/regorus/pull/264))
|
||||
- Allow lexer to be used for other policy languages ([#262](https://github.com/microsoft/regorus/pull/262))
|
||||
|
||||
## [0.2.0](https://github.com/microsoft/regorus/compare/regorus-v0.1.5...regorus-v0.2.0) - 2024-05-30
|
||||
|
||||
### Other
|
||||
@@ -340,3 +427,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- LICENSE committed
|
||||
- CODE_OF_CONDUCT.md committed
|
||||
- Initial commit
|
||||
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -12,7 +12,7 @@ members = [
|
||||
[package]
|
||||
name = "regorus"
|
||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||
version = "0.2.0"
|
||||
version = "0.2.7"
|
||||
edition = "2021"
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
@@ -27,6 +27,7 @@ doctest = false
|
||||
default = ["full-opa", "arc"]
|
||||
|
||||
arc = ["scientific/arc"]
|
||||
ast = []
|
||||
base64 = ["dep:data-encoding"]
|
||||
base64url = ["dep:data-encoding"]
|
||||
coverage = []
|
||||
@@ -66,7 +67,9 @@ full-opa = [
|
||||
"time",
|
||||
"uuid",
|
||||
"urlquery",
|
||||
"yaml"
|
||||
"yaml",
|
||||
|
||||
#"rego-extensions"
|
||||
]
|
||||
|
||||
# Features that can be used in no_std environments.
|
||||
@@ -88,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"]
|
||||
@@ -114,9 +120,9 @@ semver = {version = "1.0.20", optional = true, default-features = false }
|
||||
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
|
||||
url = { version = "2.5.0", optional = true }
|
||||
uuid = { version = "1.6.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
|
||||
jsonschema = { version = "0.18.0", default-features = false, optional = true }
|
||||
jsonschema = { version = "0.24.0", default-features = false, optional = true }
|
||||
chrono = { version = "0.4.31", optional = true }
|
||||
chrono-tz = { version = "0.8.5", optional = true }
|
||||
chrono-tz = { version = "0.10.0", optional = true }
|
||||
jsonwebtoken = { version = "9.2.0", optional = true }
|
||||
itertools = { version = "0.13.0", default-features = false, optional = true }
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -10,7 +10,7 @@ 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.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
|
||||
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.69.0](https://github.com/open-policy-agent/opa/releases/tag/v0.69.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.
|
||||
@@ -99,7 +99,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.64.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
||||
Regorus passes the [OPA v0.69.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 +184,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 +204,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 +276,7 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
|
||||
```
|
||||
## OPA Conformance
|
||||
|
||||
Regorus has been verified to be compliant with [OPA v0.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.0)
|
||||
Regorus has been verified to be compliant with [OPA v0.69.0](https://github.com/open-policy-agent/opa/releases/tag/v0.69.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:
|
||||
|
||||
@@ -38,4 +38,4 @@ corrosion_import_crate(
|
||||
add_executable(regorus_test main.c)
|
||||
# Add path to <regorus-source-folder>/bindings/ffi
|
||||
target_include_directories(regorus_test PRIVATE "../ffi")
|
||||
target_link_libraries(regorus_test regorus-ffi)
|
||||
target_link_libraries(regorus_test regorus_ffi)
|
||||
|
||||
@@ -31,4 +31,4 @@ corrosion_import_crate(
|
||||
add_executable(regorus_test main.c)
|
||||
# Add path to <regorus-source-folder>/bindings/ffi
|
||||
target_include_directories(regorus_test PRIVATE "../ffi")
|
||||
target_link_libraries(regorus_test regorus-ffi)
|
||||
target_link_libraries(regorus_test regorus_ffi)
|
||||
|
||||
@@ -31,4 +31,4 @@ corrosion_import_crate(
|
||||
add_executable(regorus_test main.cpp)
|
||||
# Add path to <regorus-source-folder>/bindings/ffi
|
||||
target_include_directories(regorus_test PRIVATE "../ffi")
|
||||
target_link_libraries(regorus_test regorus-ffi)
|
||||
target_link_libraries(regorus_test regorus_ffi)
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Regorus
|
||||
{
|
||||
public class Exception : System.Exception
|
||||
{
|
||||
public Exception(string? message) : base(message) {}
|
||||
}
|
||||
|
||||
public class Engine : ICloneable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
public Engine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var clone = (Engine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
public string AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
var regoBytes = Encoding.UTF8.GetBytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed(byte* regoPtr = regoBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = Encoding.UTF8.GetBytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = Encoding.UTF8.GetBytes(query);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalRule(string rule)
|
||||
{
|
||||
var ruleBytes = Encoding.UTF8.GetBytes(rule);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* rulePtr = ruleBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEnableCoverage(bool enable)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_enable_coverage(E, enable));
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCoverageData()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_clear_coverage_data(E));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverageReport()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report(E));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverageReportPretty()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report_pretty(E));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGatherPrints(bool enable)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_gather_prints(E, enable));
|
||||
}
|
||||
}
|
||||
|
||||
public string TakePrints()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_take_prints(E));
|
||||
}
|
||||
}
|
||||
|
||||
~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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
65
bindings/csharp/net40/Program.cs
Normal file
65
bindings/csharp/net40/Program.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="Program.cs" company="Microsoft">
|
||||
// Copyright (c)2012 Microsoft. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Contains code to test the Regorus Policy Engine base class for C#
|
||||
// and .NET4.0 bindings. It can be built and tested in Windows only.
|
||||
// </summary>
|
||||
//-----------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.WindowsAzure.Regorus.IaaS;
|
||||
|
||||
namespace regoregorus_test
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
long nanosecPerTick = (1000L * 1000L * 1000L) / Stopwatch.Frequency;
|
||||
var w = new Stopwatch();
|
||||
w.Restart();
|
||||
|
||||
var engine = new RegorusPolicyEngine();
|
||||
|
||||
w.Stop();
|
||||
var newEngineTicks = w.ElapsedTicks;
|
||||
|
||||
|
||||
w.Restart();
|
||||
|
||||
// Load policies and data.
|
||||
engine.AddPolicyFromFile("../../../examples/extension_list/agent_extension_policy.rego");
|
||||
engine.AddDataFromJsonFile("../../../examples/extension_list/agent-extension-data-allow-only.json");
|
||||
|
||||
|
||||
w.Stop();
|
||||
var loadPoliciesTicks = w.ElapsedTicks;
|
||||
|
||||
|
||||
w.Restart();
|
||||
|
||||
// Set input and eval query.
|
||||
engine.SetInputFromJsonFile("../../../examples/extension_list/agent-extension-input.json");
|
||||
var results = engine.EvalQuery("data.agent_extension_policy.extensions_to_download=x");
|
||||
Console.WriteLine("Download query test: \n {0}", results);
|
||||
|
||||
results = engine.EvalQuery("data.agent_extension_policy.extensions_validated");
|
||||
|
||||
Console.WriteLine("Signing validation test: \n {0}", results);
|
||||
|
||||
engine.Dispose();
|
||||
|
||||
w.Stop();
|
||||
var evalTicks = w.ElapsedTicks;
|
||||
|
||||
Console.WriteLine("Engine creation took {0} msecs", (newEngineTicks * nanosecPerTick) / (1000.0 * 1000.0));
|
||||
Console.WriteLine("Load policies and data took {0} msecs", (loadPoliciesTicks * nanosecPerTick) / (1000.0 * 1000.0));
|
||||
Console.WriteLine("EvalQuery and print results took {0} msecs", (evalTicks * nanosecPerTick) / (1000.0 * 1000.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
bindings/csharp/net40/README
Normal file
4
bindings/csharp/net40/README
Normal file
@@ -0,0 +1,4 @@
|
||||
The Regorus C# binding library can be built via command "dotnet build". We can use the Regorus C# binding library built from this
|
||||
directory to create a Nuget. This Nuget will contain the Regorus C# binding library with definitions that
|
||||
work for .NET framework 4.0 (net40) and above. Note the Nuget can only be created after the binding library has been built.
|
||||
RegorusCsharp-Lib-x64.nuspec is built for x64 architecture.
|
||||
213
bindings/csharp/net40/Regorus.cs
Normal file
213
bindings/csharp/net40/Regorus.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="Regorus.cs" company="Microsoft">
|
||||
// Copyright (c)2012 Microsoft. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Contains code for the Regorus Policy Engine base class for C# and
|
||||
// .NET4.0 bindings. Currently this base class is not thread-safe. Make
|
||||
// sure we use it in a signle-threaded environment or add additional
|
||||
// protection when using it.
|
||||
// </summary>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
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 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 AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
var regoBytes = NullTerminatedUTF8Bytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed (byte* regoPtr = regoBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(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;
|
||||
}
|
||||
|
||||
string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories);
|
||||
foreach (string file in regoFiles)
|
||||
{
|
||||
AddPolicyFromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = NullTerminatedUTF8Bytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
var inputBytes = 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
22
bindings/csharp/net40/RegorusCsharp-Lib-x64.nuspec
Normal file
22
bindings/csharp/net40/RegorusCsharp-Lib-x64.nuspec
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>RegorusCsharp-Lib-x64</id>
|
||||
<version>0.2.1</version>
|
||||
<title>RegorusCsharp-Lib-x64</title>
|
||||
<authors>yangjie@microsoft.com</authors>
|
||||
<owners>yangjie@microsoft.com</owners>
|
||||
<projectUrl>https://www.microsoft.com</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Regorus C# library for x64</description>
|
||||
<releaseNotes>remove Regorus.cs from Nuget</releaseNotes>
|
||||
<copyright>Copyright (C) Microsoft Corp</copyright>
|
||||
<summary></summary>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="RegorusFFI.g.cs" target="RegorusFFI.g.cs"/>
|
||||
<file src="regorus_ffi.dll" target="lib\regorusc.dll" />
|
||||
<file src="README" target="README" />
|
||||
<file src="..\..\..\LICENSE" target="LICENSE" />
|
||||
</files>
|
||||
</package>
|
||||
24
bindings/csharp/net40/regorus-test.csproj
Normal file
24
bindings/csharp/net40/regorus-test.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="BuildRegorusFFI">
|
||||
|
||||
<Target Name="BuildRegorusFFI">
|
||||
<Exec Command="cargo build -r --manifest-path ../../ffi/Cargo.toml" />
|
||||
<Copy SourceFiles="../../ffi/RegorusFFI.g.cs" DestinationFolder="." />
|
||||
<ItemGroup>
|
||||
<RegorusDylib Include="..\..\..\target\release\*regorus_ffi*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
|
||||
</Target>
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net40</TargetFramework>
|
||||
<RootNamespace>regorus_test</RootNamespace>
|
||||
<StartupObject>regoregorus_test.Program</StartupObject>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -1,4 +1,14 @@
|
||||
using System.Diagnostics;
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="Program.cs" company="Microsoft">
|
||||
// Copyright (c)2012 Microsoft. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Contains code to test the Regorus class for C#
|
||||
// and .NET 8.0 bindings.
|
||||
// </summary>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
long nanosecPerTick = (1000L*1000L*1000L) / Stopwatch.Frequency;
|
||||
var w = new Stopwatch();
|
||||
@@ -21,10 +31,10 @@ var newEngineTicks = w.ElapsedTicks;
|
||||
w.Restart();
|
||||
|
||||
// Load policies and data.
|
||||
engine.AddPolicyFromFile("../../tests/aci/framework.rego");
|
||||
engine.AddPolicyFromFile("../../tests/aci/api.rego");
|
||||
engine.AddPolicyFromFile("../../tests/aci/policy.rego");
|
||||
engine.AddDataFromJsonFile("../../tests/aci/data.json");
|
||||
engine.AddPolicyFromFile("../../../tests/aci/framework.rego");
|
||||
engine.AddPolicyFromFile("../../../tests/aci/api.rego");
|
||||
engine.AddPolicyFromFile("../../../tests/aci/policy.rego");
|
||||
engine.AddDataFromJsonFile("../../../tests/aci/data.json");
|
||||
|
||||
|
||||
w.Stop();
|
||||
@@ -34,7 +44,7 @@ var loadPoliciesTicks = w.ElapsedTicks;
|
||||
w.Restart();
|
||||
|
||||
// Set input and eval rule.
|
||||
engine.SetInputFromJsonFile("../../tests/aci/input.json");
|
||||
engine.SetInputFromJsonFile("../../../tests/aci/input.json");
|
||||
var value = engine.EvalQuery("data.framework.mount_overlay");
|
||||
var valueDoc = System.Text.Json.JsonDocument.Parse(value);
|
||||
|
||||
228
bindings/csharp/net8.0/Regorus.cs
Normal file
228
bindings/csharp/net8.0/Regorus.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Regorus
|
||||
{
|
||||
public class Exception : System.Exception
|
||||
{
|
||||
public Exception(string? message) : base(message) { }
|
||||
}
|
||||
|
||||
public class Engine : ICloneable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
public Engine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var clone = (Engine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
byte[] NullTerminatedUTF8Bytes(string s)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(s + char.MinValue);
|
||||
}
|
||||
|
||||
public string AddPolicy(string path, string rego)
|
||||
{
|
||||
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 string AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = NullTerminatedUTF8Bytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
var inputBytes = 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);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="BuildRegorusFFI">
|
||||
|
||||
<Target Name="BuildRegorusFFI">
|
||||
<Exec Command="cargo build -r --manifest-path ../ffi/Cargo.toml" />
|
||||
<Copy SourceFiles="../ffi/RegorusFFI.g.cs" DestinationFolder="." />
|
||||
<Exec Command="cargo build -r --manifest-path ../../ffi/Cargo.toml" />
|
||||
<Copy SourceFiles="../../ffi/RegorusFFI.g.cs" DestinationFolder="." />
|
||||
<ItemGroup>
|
||||
<RegorusDylib Include="..\..\target\release\*regorus_ffi*" />
|
||||
<RegorusDylib Include="..\..\..\target\release\*regorus_ffi*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(RegorusDylib)" DestinationFolder="." />
|
||||
</Target>
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorus-ffi"
|
||||
version = "0.1.5"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -13,11 +13,12 @@ regorus = { path = "../..", default-features = false }
|
||||
serde_json = "1.0.113"
|
||||
|
||||
[features]
|
||||
default = ["std", "coverage", "regorus/arc", "regorus/full-opa"]
|
||||
default = ["ast", "std", "coverage", "regorus/arc", "regorus/full-opa"]
|
||||
ast = ["regorus/ast"]
|
||||
std = ["regorus/std"]
|
||||
coverage = ["regorus/coverage"]
|
||||
custom_allocator = []
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.26.0"
|
||||
csbindgen = "1.9.0"
|
||||
cbindgen = "0.27.0"
|
||||
csbindgen = "=1.9.3"
|
||||
|
||||
@@ -189,7 +189,6 @@ pub extern "C" fn regorus_engine_add_data_json(
|
||||
/// Get list of loaded Rego packages as JSON.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages
|
||||
/// * `data`: JSON encoded value to be used as policy data.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
@@ -198,6 +197,16 @@ pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> Reg
|
||||
}())
|
||||
}
|
||||
|
||||
/// Get list of policies as JSON.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_get_policies(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
to_regorus_string_result(|| -> Result<String> {
|
||||
to_ref(&engine)?.engine.get_policies_as_json()
|
||||
}())
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn regorus_engine_add_data_from_json_file(
|
||||
@@ -411,6 +420,23 @@ pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> Rego
|
||||
}
|
||||
}
|
||||
|
||||
/// Get AST of policies.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Engine.html#method.get_ast_as_json
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "ast")]
|
||||
pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> RegorusResult {
|
||||
let output = || -> Result<String> { to_ref(&engine)?.engine.get_ast_as_json() }();
|
||||
match output {
|
||||
Ok(out) => RegorusResult {
|
||||
status: RegorusStatus::RegorusStatusOk,
|
||||
output: to_c_str(out),
|
||||
error_message: std::ptr::null_mut(),
|
||||
},
|
||||
Err(e) => to_regorus_result(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "custom_allocator")]
|
||||
extern "C" {
|
||||
fn regorus_aligned_alloc(alignment: usize, size: usize) -> *mut u8;
|
||||
|
||||
@@ -82,6 +82,19 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%s\n", output)
|
||||
|
||||
// Print packages
|
||||
if output, err = engine1.GetPackages(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%s\n", output)
|
||||
|
||||
// Print policies
|
||||
if output, err = engine1.GetPolicies(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%s\n", output)
|
||||
}
|
||||
|
||||
@@ -55,6 +55,25 @@ func (e *Engine) AddPolicyFromFile(path string) (string, error) {
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (e *Engine) GetPackages() (string, error) {
|
||||
result := C.regorus_engine_get_packages(e.e)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.RegorusStatusOk {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
func (e *Engine) GetPolicies() (string, error) {
|
||||
result := C.regorus_engine_get_policies(e.e)
|
||||
defer C.regorus_result_drop(result)
|
||||
if result.status != C.RegorusStatusOk {
|
||||
return "", fmt.Errorf("%s", C.GoString(result.error_message))
|
||||
}
|
||||
return C.GoString(result.output), nil
|
||||
}
|
||||
|
||||
|
||||
func (e *Engine) AddDataJson(data string) error {
|
||||
data_c := C.CString(data)
|
||||
defer C.free(unsafe.Pointer(data_c))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorus-java"
|
||||
version = "0.1.5"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/java"
|
||||
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
@@ -11,7 +11,9 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["regorus/std", "regorus/full-opa"]
|
||||
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
|
||||
coverage = ["regorus/coverage"]
|
||||
ast = ["regorus/ast"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
|
||||
@@ -31,6 +31,12 @@ public class Test {
|
||||
System.out.println(coverageJson);
|
||||
|
||||
System.out.println(engine.getCoverageReportPretty());
|
||||
|
||||
String packagesJson = engine.getPackages();
|
||||
System.out.println(packagesJson);
|
||||
|
||||
String policiesJson = engine.getPolicies();
|
||||
System.out.println(policiesJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>com.microsoft.regorus</groupId>
|
||||
<artifactId>regorus-java</artifactId>
|
||||
<version>0.1.5</version>
|
||||
<version>0.2.2</version>
|
||||
|
||||
<name>Regorus Java</name>
|
||||
<description>Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust</description>
|
||||
|
||||
@@ -89,6 +89,25 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPackages(
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPolicies(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
) -> jstring {
|
||||
let res = throw_err(env, |env| {
|
||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||
let policies = engine.get_policies_as_json()?;
|
||||
let policies_json = env.new_string(&policies)?;
|
||||
Ok(policies_json.into_raw())
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(_) => JObject::null().into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearData(
|
||||
env: JNIEnv,
|
||||
@@ -205,6 +224,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalRule(
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
@@ -219,6 +239,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
@@ -238,6 +259,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReportPretty(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
@@ -257,6 +279,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "coverage")]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearCoverageData(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
@@ -302,6 +325,26 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeTakePrints(
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "ast")]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_getAstAsJson(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
engine_ptr: jlong,
|
||||
) -> jstring {
|
||||
let res = throw_err(env, |env| {
|
||||
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
|
||||
let ast = engine.get_ast_as_json()?;
|
||||
let output = env.new_string(&ast)?;
|
||||
Ok(output.into_raw())
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(val) => val,
|
||||
Err(_) => JObject::null().into_raw(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
|
||||
_env: JNIEnv,
|
||||
|
||||
@@ -26,6 +26,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
||||
private static native String nativeAddPolicy(long enginePtr, String path, String rego);
|
||||
private static native String nativeAddPolicyFromFile(long enginePtr, String path);
|
||||
private static native String nativeGetPackages(long enginePtr);
|
||||
private static native String nativeGetPolicies(long enginePtr);
|
||||
private static native void nativeClearData(long enginePtr);
|
||||
private static native void nativeAddDataJson(long enginePtr, String data);
|
||||
private static native void nativeAddDataJsonFromFile(long enginePtr, String path);
|
||||
@@ -60,7 +61,7 @@ public class Engine implements AutoCloseable, Cloneable {
|
||||
/**
|
||||
* Efficiently clones an Engine.
|
||||
*/
|
||||
public Engine Clone() {
|
||||
public Engine clone() {
|
||||
return new Engine(nativeClone(enginePtr));
|
||||
}
|
||||
|
||||
@@ -96,6 +97,15 @@ public class Engine implements AutoCloseable, Cloneable {
|
||||
return nativeGetPackages(enginePtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of loaded policies.
|
||||
*
|
||||
* @return List of Rego policies as a JSON array of sources.
|
||||
*/
|
||||
public String getPolicies() {
|
||||
return nativeGetPolicies(enginePtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the data document.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regoruspy"
|
||||
version = "0.1.5"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/python"
|
||||
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
@@ -12,12 +12,14 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["regorus/std", "regorus/full-opa"]
|
||||
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
|
||||
ast = ["regorus/ast"]
|
||||
coverage = ["regorus/coverage"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
ordered-float = "4.2.0"
|
||||
pyo3 = {version = "0.21.0", features = ["anyhow", "extension-module"] }
|
||||
pyo3 = {version = "0.22.0", features = ["anyhow", "extension-module"] }
|
||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
||||
serde_json = "1.0.112"
|
||||
|
||||
|
||||
@@ -184,10 +184,18 @@ impl Engine {
|
||||
|
||||
/// Get the list of packages defined by loaded policies.
|
||||
///
|
||||
pub fn get_packages(&mut self) -> Result<Vec<String>> {
|
||||
pub fn get_packages(&self) -> Result<Vec<String>> {
|
||||
self.engine.get_packages()
|
||||
}
|
||||
|
||||
/// Get the list of policies.
|
||||
///
|
||||
pub fn get_policies(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string_pretty(
|
||||
&self.engine.get_policies_as_json()?,
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Add policy data.
|
||||
///
|
||||
/// * `data`: Rego value. A Rego value is a number, bool, string, None
|
||||
@@ -312,6 +320,7 @@ impl Engine {
|
||||
|
||||
/// Get coverage report as json.
|
||||
///
|
||||
#[cfg(feature = "coverage")]
|
||||
pub fn get_coverage_report_as_json(&self) -> Result<String> {
|
||||
let report = self.engine.get_coverage_report()?;
|
||||
serde_json::to_string_pretty(&report).map_err(|e| anyhow!("{e}"))
|
||||
@@ -319,12 +328,14 @@ impl Engine {
|
||||
|
||||
/// Get coverage report as pretty printable string.
|
||||
///
|
||||
#[cfg(feature = "coverage")]
|
||||
pub fn get_coverage_report_pretty(&self) -> Result<String> {
|
||||
self.engine.get_coverage_report()?.to_string_pretty()
|
||||
}
|
||||
|
||||
/// Clear coverage data.
|
||||
///
|
||||
#[cfg(feature = "coverage")]
|
||||
pub fn clear_coverage_data(&mut self) {
|
||||
self.engine.clear_coverage_data();
|
||||
}
|
||||
@@ -350,6 +361,13 @@ impl Engine {
|
||||
engine: self.engine.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get AST of policies.
|
||||
///
|
||||
#[cfg(feature = "ast")]
|
||||
pub fn get_ast_as_json(&self) -> Result<String> {
|
||||
self.engine.get_ast_as_json()
|
||||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
|
||||
214
bindings/python/test_extensions.py
Normal file
214
bindings/python/test_extensions.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
import json
|
||||
import pytest
|
||||
import regorus
|
||||
|
||||
TEST_EXT_NAME = "Microsoft.Azure.ActiveDirectory.AADSSHLoginForLinux"
|
||||
|
||||
|
||||
@pytest.fixture(name="engine", scope="function")
|
||||
def engine_fixture():
|
||||
"""
|
||||
Fixture to handle creation and cleanup of a default policy engine.
|
||||
New engine is created for each test case.
|
||||
"""
|
||||
engine = regorus.Engine()
|
||||
engine.add_policy_from_file('../../examples/extension_list/agent_extension_policy.rego')
|
||||
yield engine
|
||||
|
||||
|
||||
@pytest.fixture(name="input_data")
|
||||
def input_data_fixture():
|
||||
"""
|
||||
Fixture to handle creation and cleanup of a default input data.
|
||||
New input data is created for each test case.
|
||||
"""
|
||||
input_data = {
|
||||
"extensions": {
|
||||
TEST_EXT_NAME: {
|
||||
"signingInfo": {
|
||||
"extensionSigned": False
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
input_json = json.dumps(input_data)
|
||||
yield input_json
|
||||
|
||||
|
||||
@pytest.fixture(name="default_data")
|
||||
def default_data_fixture():
|
||||
"""Fixture for default data"""
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": False
|
||||
},
|
||||
"allowListOnly": False
|
||||
}
|
||||
}
|
||||
data_json = json.dumps(data_json)
|
||||
yield data_json
|
||||
|
||||
|
||||
def test_default_data_json(engine, input_data):
|
||||
"""Test the default data in json format for extension policy."""
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": False
|
||||
},
|
||||
"allowListOnly": False
|
||||
}
|
||||
}
|
||||
data_json = json.dumps(data_json)
|
||||
engine.add_data_json(data_json)
|
||||
engine.set_input_json(input_data)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
def test_default_data_file(engine, input_data):
|
||||
"""Test the default data in file format for extension policy."""
|
||||
data_default_path = "../../examples/extension_list/agent-extension-default-data.json"
|
||||
engine.add_data_from_json_file(data_default_path)
|
||||
engine.set_input_json(input_data)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
def test_allow_all(engine, input_data):
|
||||
"""Test the policy engine with allow all policy."""
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": False
|
||||
},
|
||||
"allowListOnly": False
|
||||
}
|
||||
}
|
||||
data_json = json.dumps(data_json)
|
||||
engine.add_data_json(data_json)
|
||||
engine.set_input_json(input_data)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
def test_name_only_input(engine, default_data):
|
||||
"""Test input with only the extension name."""
|
||||
input_data = {
|
||||
"extensions": {
|
||||
TEST_EXT_NAME: {
|
||||
}
|
||||
}
|
||||
}
|
||||
input_json = json.dumps(input_data)
|
||||
engine.add_data_json(default_data)
|
||||
engine.set_input_json(input_json)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize("input_signed, extension_signed", [
|
||||
(True, True),
|
||||
(True, False),
|
||||
(False, True),
|
||||
(False, False)
|
||||
])
|
||||
def test_extension_signed_rule(engine, input_signed, extension_signed):
|
||||
"""
|
||||
Test extension signing rule. Engine should be able to handle
|
||||
both signed and unsigned extensions, with extensionSigned rule set
|
||||
to either true or false.
|
||||
"""
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": extension_signed
|
||||
},
|
||||
"allowListOnly": False
|
||||
}
|
||||
}
|
||||
input_data = {
|
||||
"extensions": {
|
||||
TEST_EXT_NAME: {
|
||||
"signingInfo": {
|
||||
"extensionSigned": input_signed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data_json = json.dumps(data_json)
|
||||
input_data = json.dumps(input_data)
|
||||
engine.add_data_json(data_json)
|
||||
engine.set_input_json(input_data)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
|
||||
# assert results
|
||||
if extension_signed:
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_validated'][TEST_EXT_NAME]['signingValidated'] == input_signed
|
||||
else:
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_validated'][TEST_EXT_NAME]['signingValidated']
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][TEST_EXT_NAME]['downloadAllowed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ext_allowed, allow_rule", [
|
||||
(True, True),
|
||||
(True, False),
|
||||
(False, True),
|
||||
(False, False)
|
||||
])
|
||||
def test_allowlist_rule(engine, ext_allowed, allow_rule):
|
||||
"""
|
||||
Test allowListOnly rule. Engine should be able to handle
|
||||
both allowed and disallowed extensions, with allowListOnly rule
|
||||
set to either true or false.
|
||||
"""
|
||||
if ext_allowed:
|
||||
ext_name = TEST_EXT_NAME
|
||||
else:
|
||||
ext_name = "random_disallowed_extension"
|
||||
|
||||
input_json = {
|
||||
"extensions": {
|
||||
ext_name: {
|
||||
"signingInfo": {
|
||||
"extensionSigned": False
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data_json = {
|
||||
"azureGuestAgentPolicy": {
|
||||
"signingRules": {
|
||||
"extensionSigned": False
|
||||
},
|
||||
"allowListOnly": allow_rule
|
||||
},
|
||||
"azureGuestExtensionsPolicy": {
|
||||
"Microsoft.CPlat.Core.RunCommandLinux": {
|
||||
},
|
||||
TEST_EXT_NAME: {
|
||||
}
|
||||
}
|
||||
}
|
||||
input_json = json.dumps(input_json)
|
||||
data_json = json.dumps(data_json)
|
||||
engine.add_data_json(data_json)
|
||||
engine.set_input_json(input_json)
|
||||
# Eval query
|
||||
results = engine.eval_query('data.agent_extension_policy')
|
||||
if allow_rule:
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][ext_name]['downloadAllowed'] == ext_allowed
|
||||
else:
|
||||
assert results['result'][0]['expressions'][0]['value']['extensions_to_download'][ext_name]['downloadAllowed']
|
||||
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
regorusrb (0.1.5)
|
||||
regorusrb (0.2.1)
|
||||
rb_sys (~> 0.9.97)
|
||||
|
||||
GEM
|
||||
@@ -10,9 +10,9 @@ GEM
|
||||
ast (2.4.2)
|
||||
json (2.7.2)
|
||||
language_server-protocol (3.17.0.3)
|
||||
minitest (5.23.1)
|
||||
parallel (1.24.0)
|
||||
parser (3.3.1.0)
|
||||
minitest (5.24.1)
|
||||
parallel (1.25.1)
|
||||
parser (3.3.4.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
racc (1.8.0)
|
||||
@@ -20,25 +20,25 @@ GEM
|
||||
rake (13.2.1)
|
||||
rake-compiler (1.2.7)
|
||||
rake
|
||||
rake-compiler-dock (1.5.0)
|
||||
rb_sys (0.9.97)
|
||||
rake-compiler-dock (1.5.1)
|
||||
rb_sys (0.9.99)
|
||||
regexp_parser (2.9.2)
|
||||
rexml (3.2.8)
|
||||
strscan (>= 3.0.9)
|
||||
rubocop (1.64.0)
|
||||
rexml (3.3.6)
|
||||
strscan
|
||||
rubocop (1.65.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
regexp_parser (>= 2.4, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.31.3)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-minitest (0.35.0)
|
||||
rubocop-minitest (0.35.1)
|
||||
rubocop (>= 1.61, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rake (0.6.0)
|
||||
@@ -62,4 +62,4 @@ DEPENDENCIES
|
||||
rubocop-rake
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.10
|
||||
2.5.13
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorusrb"
|
||||
version = "0.1.5"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
publish = false
|
||||
@@ -10,10 +10,12 @@ crate-type = ["cdylib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["regorus/std", "regorus/full-opa"]
|
||||
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
|
||||
ast = ["regorus/ast"]
|
||||
coverage = ["regorus/coverage"]
|
||||
|
||||
[dependencies]
|
||||
magnus = { version = "0.6.4" }
|
||||
regorus = { git = "https://github.com/microsoft/regorus", default-features = false, features = ["arc"] }
|
||||
regorus = { path = "../../../..", default-features = false, features = ["arc"] }
|
||||
serde_json = "1.0.117"
|
||||
serde_magnus = "0.8.1"
|
||||
|
||||
@@ -92,6 +92,20 @@ impl Engine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_packages(&self) -> Result<Vec<String>, Error> {
|
||||
self.engine
|
||||
.borrow()
|
||||
.get_packages()
|
||||
.map_err(|e| Error::new(runtime_error(), format!("Failed to get packages: {e}")))
|
||||
}
|
||||
|
||||
fn get_policies(&self) -> Result<String, Error> {
|
||||
self.engine
|
||||
.borrow()
|
||||
.get_policies_as_json()
|
||||
.map_err(|e| Error::new(runtime_error(), format!("Failed to get policies: {e}")))
|
||||
}
|
||||
|
||||
fn set_input(&self, ruby_hash: magnus::RHash) -> Result<(), Error> {
|
||||
let input_value: regorus::Value = serde_magnus::deserialize(ruby_hash).map_err(|e| {
|
||||
Error::new(
|
||||
@@ -192,11 +206,13 @@ impl Engine {
|
||||
Ok(self.engine.borrow_mut().eval_deny_query(query, false))
|
||||
}
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
fn set_enable_coverage(&self, enable: bool) -> Result<(), Error> {
|
||||
self.engine.borrow_mut().set_enable_coverage(enable);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
fn get_coverage_report_as_json(&self) -> Result<String, Error> {
|
||||
let report = self
|
||||
.engine
|
||||
@@ -217,6 +233,7 @@ impl Engine {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
fn get_coverage_report_pretty(&self) -> Result<String, Error> {
|
||||
let report = self
|
||||
.engine
|
||||
@@ -237,6 +254,7 @@ impl Engine {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
fn clear_coverage_data(&self) -> Result<(), Error> {
|
||||
self.engine.borrow_mut().clear_coverage_data();
|
||||
Ok(())
|
||||
@@ -256,6 +274,14 @@ impl Engine {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "ast")]
|
||||
fn get_ast_as_json(&self) -> Result<String, Error> {
|
||||
self.engine
|
||||
.borrow()
|
||||
.get_ast_as_json()
|
||||
.map_err(|e| Error::new(runtime_error(), format!("Failed to get ast: {e}")))
|
||||
}
|
||||
}
|
||||
|
||||
#[magnus::init]
|
||||
@@ -277,6 +303,8 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
||||
"add_policy_from_file",
|
||||
method!(Engine::add_policy_from_file, 1),
|
||||
)?;
|
||||
engine_class.define_method("get_packages", method!(Engine::get_packages, 0))?;
|
||||
engine_class.define_method("get_policies", method!(Engine::get_policies, 0))?;
|
||||
|
||||
// data operations
|
||||
engine_class.define_method("add_data", method!(Engine::add_data, 1))?;
|
||||
@@ -325,5 +353,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
||||
engine_class.define_method("set_gather_prints", method!(Engine::set_gather_prints, 1))?;
|
||||
engine_class.define_method("take_prints", method!(Engine::take_prints, 0))?;
|
||||
|
||||
// ast
|
||||
engine_class.define_method("get_ast_as_json", method!(Engine::get_ast_as_json, 0))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Regorus
|
||||
VERSION = "0.1.5"
|
||||
VERSION = "0.2.1"
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "regorusjs"
|
||||
version = "0.1.5"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/microsoft/regorus/bindings/wasm"
|
||||
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
|
||||
@@ -11,7 +11,9 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["regorus/std", "regorus/full-opa"]
|
||||
default = ["ast", "coverage", "regorus/std", "regorus/full-opa"]
|
||||
ast = ["regorus/ast"]
|
||||
coverage = ["regorus/coverage"]
|
||||
|
||||
[dependencies]
|
||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
||||
|
||||
@@ -72,6 +72,13 @@ impl Engine {
|
||||
self.engine.get_packages().map_err(error_to_jsvalue)
|
||||
}
|
||||
|
||||
/// Get the list of policies.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_policies
|
||||
pub fn getPolicies(&self) -> Result<String, JsValue> {
|
||||
self.engine.get_policies_as_json().map_err(error_to_jsvalue)
|
||||
}
|
||||
|
||||
/// Clear policy data.
|
||||
///
|
||||
/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data
|
||||
@@ -131,6 +138,7 @@ impl Engine {
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage
|
||||
/// * `b`: Whether to enable gathering coverage or not.
|
||||
#[cfg(feature = "coverage")]
|
||||
pub fn setEnableCoverage(&mut self, enable: bool) {
|
||||
self.engine.set_enable_coverage(enable)
|
||||
}
|
||||
@@ -138,6 +146,7 @@ impl Engine {
|
||||
/// Get the coverage report as json.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report
|
||||
#[cfg(feature = "coverage")]
|
||||
pub fn getCoverageReport(&self) -> Result<String, JsValue> {
|
||||
let report = self
|
||||
.engine
|
||||
@@ -149,6 +158,7 @@ impl Engine {
|
||||
/// Clear gathered coverage data.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data
|
||||
#[cfg(feature = "coverage")]
|
||||
pub fn clearCoverageData(&mut self) {
|
||||
self.engine.clear_coverage_data()
|
||||
}
|
||||
@@ -156,6 +166,7 @@ impl Engine {
|
||||
/// Get ANSI color coded coverage report.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty
|
||||
#[cfg(feature = "coverage")]
|
||||
pub fn getCoverageReportPretty(&self) -> Result<String, JsValue> {
|
||||
let report = self
|
||||
.engine
|
||||
@@ -163,14 +174,24 @@ impl Engine {
|
||||
.map_err(error_to_jsvalue)?;
|
||||
report.to_string_pretty().map_err(error_to_jsvalue)
|
||||
}
|
||||
|
||||
/// Get AST of policies.
|
||||
///
|
||||
/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_ast_as_json
|
||||
#[cfg(feature = "ast")]
|
||||
pub fn getAstAsJson(&self) -> Result<String, JsValue> {
|
||||
self.engine.get_ast_as_json().map_err(error_to_jsvalue)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::error_to_jsvalue;
|
||||
use wasm_bindgen::prelude::*;
|
||||
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);
|
||||
@@ -204,7 +225,7 @@ mod tests {
|
||||
assert_eq!(pkg, "data.test");
|
||||
|
||||
let results = engine.evalQuery("data".to_string())?;
|
||||
let r = regorus::Value::from_json_str(&results).map_err(crate::error_to_jsvalue)?;
|
||||
let r = regorus::Value::from_json_str(&results).map_err(error_to_jsvalue)?;
|
||||
|
||||
let v = &r["result"][0]["expressions"][0]["value"];
|
||||
|
||||
@@ -216,7 +237,7 @@ mod tests {
|
||||
|
||||
// Use eval_rule to perform same query.
|
||||
let v = engine.evalRule("data.test.message".to_owned())?;
|
||||
let v = regorus::Value::from_json_str(&v).map_err(crate::error_to_jsvalue)?;
|
||||
let v = regorus::Value::from_json_str(&v).map_err(error_to_jsvalue)?;
|
||||
|
||||
// Ensure that input and policy were evaluated.
|
||||
assert_eq!(v, regorus::Value::from("Hello"));
|
||||
@@ -234,7 +255,7 @@ mod tests {
|
||||
|
||||
// Test code coverage.
|
||||
let report = engine1.getCoverageReport()?;
|
||||
let r = regorus::Value::from_json_str(&report).map_err(crate::error_to_jsvalue)?;
|
||||
let r = regorus::Value::from_json_str(&report).map_err(error_to_jsvalue)?;
|
||||
|
||||
assert_eq!(
|
||||
r["files"][0]["covered"]
|
||||
@@ -246,6 +267,13 @@ mod tests {
|
||||
println!("{}", engine1.getCoverageReportPretty()?);
|
||||
|
||||
engine1.clearCoverageData();
|
||||
|
||||
let policies = engine1.getPolicies()?;
|
||||
let v = regorus::Value::from_json_str(&policies).map_err(error_to_jsvalue)?;
|
||||
assert_eq!(
|
||||
v[0]["path"].as_string().map_err(error_to_jsvalue)?.as_ref(),
|
||||
"hello.rego"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
3
build.rs
3
build.rs
@@ -8,6 +8,9 @@ fn main() -> Result<()> {
|
||||
// Copy hooks to appropriate location so that git will run them.
|
||||
// In git worktrees, .git is a symlink and the following commands fail.
|
||||
if Path::new(".git").is_dir() {
|
||||
if !Path::new("./.git/hooks").exists() {
|
||||
std::fs::create_dir_all("./.git/hooks")?;
|
||||
}
|
||||
std::fs::copy("./scripts/pre-commit", "./.git/hooks/pre-commit")?;
|
||||
std::fs::copy("./scripts/pre-push", "./.git/hooks/pre-push")?;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
19
examples/extension_list/agent-extension-data-allow-only.json
Normal file
19
examples/extension_list/agent-extension-data-allow-only.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"azureGuestAgentPolicy": {
|
||||
"signingRules": {
|
||||
"extensionSigned": true
|
||||
},
|
||||
"allowListOnly": true
|
||||
},
|
||||
"azureGuestExtensionsPolicy": {
|
||||
"test3": {
|
||||
"runtimeRules": {}
|
||||
},
|
||||
"test2": {
|
||||
"signingRules": {
|
||||
"extensionSigned": false
|
||||
},
|
||||
"runtimeRules": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"azureGuestAgentPolicy": {
|
||||
"policyVersion": "0.1.0",
|
||||
"signingRules": {
|
||||
"extensionSigned": false
|
||||
},
|
||||
"allowListOnly": false
|
||||
}
|
||||
}
|
||||
28
examples/extension_list/agent-extension-input.json
Normal file
28
examples/extension_list/agent-extension-input.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"extensions": {
|
||||
"Microsoft.Azure.ActiveDirectory.AADSSHLoginForLinux": {
|
||||
"signingInfo": {
|
||||
|
||||
"extensionSigned": false
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
"test2": {
|
||||
"signingInfo": {
|
||||
"extensionSigned": true
|
||||
}
|
||||
},
|
||||
"test3": {
|
||||
"signingInfo": {
|
||||
"extensionSigned": false
|
||||
}
|
||||
},
|
||||
"test1": {
|
||||
"signingInfo": {
|
||||
"extensionSigned": false
|
||||
}
|
||||
},
|
||||
"test4": {}
|
||||
}
|
||||
}
|
||||
125
examples/extension_list/agent_extension_policy.rego
Normal file
125
examples/extension_list/agent_extension_policy.rego
Normal file
@@ -0,0 +1,125 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
package agent_extension_policy
|
||||
|
||||
import rego.v1
|
||||
|
||||
policy_version := "0.1.0"
|
||||
|
||||
default default_global_rules := {
|
||||
"allowListOnly": false,
|
||||
"signingRules": {
|
||||
"extensionSigned": false,
|
||||
"signingDetails": {},
|
||||
},
|
||||
"updateAllowed": true,
|
||||
"uninstallAllowed": true,
|
||||
}
|
||||
|
||||
default global_rules := {
|
||||
"allowListOnly": false,
|
||||
"signingRules": {
|
||||
"extensionSigned": false,
|
||||
"signingDetails": {},
|
||||
},
|
||||
"updateAllowed": true,
|
||||
"uninstallAllowed": true,
|
||||
}
|
||||
|
||||
global_rules := object.union(default_global_rules, data.azureGuestAgentPolicy) if {
|
||||
data.azureGuestAgentPolicy
|
||||
}
|
||||
|
||||
default any_extension_allowed := true
|
||||
|
||||
any_extension_allowed := false if {
|
||||
global_rules.allowListOnly
|
||||
}
|
||||
|
||||
default default_signing_info := {"signingInfo": {}}
|
||||
|
||||
# Download rule 1: if the extension is in the list and download rule satisfied: download allowed
|
||||
extensions_to_download[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
data.azureGuestExtensionsPolicy[name]
|
||||
download_rule_validated(input_extension, data.azureGuestExtensionsPolicy[name])
|
||||
extension := object.union(input_extension, {"downloadAllowed": true})
|
||||
}
|
||||
|
||||
# Download rule 2: if the extension is in the list and download rule not satisfied: download denied
|
||||
extensions_to_download[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
data.azureGuestExtensionsPolicy[name]
|
||||
not download_rule_validated(input_extension, data.azureGuestExtensionsPolicy[name])
|
||||
extension := object.union(input_extension, {"downloadAllowed": false})
|
||||
}
|
||||
|
||||
# Download rule 3: if the extension is not in the list: depending on allowListOnly on or off
|
||||
extensions_to_download[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
not data.azureGuestExtensionsPolicy[name]
|
||||
extension := object.union(input_extension, {"downloadAllowed": any_extension_allowed})
|
||||
}
|
||||
|
||||
# Validate rule 1: if individual signing rule exists, signing rule validated according to the rules
|
||||
extensions_validated[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
data.azureGuestExtensionsPolicy[name]
|
||||
|
||||
extension_global_rules := object.union(global_rules, data.azureGuestExtensionsPolicy[name])
|
||||
extension_signing_info := object.union(extension_global_rules, default_signing_info)
|
||||
output := object.union(input_extension, extension_signing_info)
|
||||
signing_validated(output.signingInfo, output.signingRules)
|
||||
extension := object.union(output, {"signingValidated": true})
|
||||
}
|
||||
|
||||
# Validate rule 2: if indivual signing rule exists, signing rule not validated according to the rules
|
||||
extensions_validated[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
data.azureGuestExtensionsPolicy[name]
|
||||
|
||||
extension_global_rules := object.union(global_rules, data.azureGuestExtensionsPolicy[name])
|
||||
extension_signing_info := object.union(extension_global_rules, default_signing_info)
|
||||
output := object.union(input_extension, extension_signing_info)
|
||||
not signing_validated(output.signingInfo, output.signingRules)
|
||||
extension := object.union(output, {"signingValidated": false})
|
||||
}
|
||||
|
||||
# Validate rule 3: if individual signing rule doesn't exist, signing rule validated according to global signing rule
|
||||
extensions_validated[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
not data.azureGuestExtensionsPolicy[name]
|
||||
extension_global_rules := object.union(input_extension, global_rules)
|
||||
output := object.union(extension_global_rules, default_signing_info)
|
||||
signing_validated(output.signingInfo, output.signingRules)
|
||||
extension := object.union(output, {"signingValidated": true})
|
||||
}
|
||||
|
||||
# Validate rule 4: if individual signing rule doesn't exist, signing rule not validated according to the global rules
|
||||
extensions_validated[name] := extension if {
|
||||
some name, input_extension in input.extensions
|
||||
not data.azureGuestExtensionsPolicy[name]
|
||||
extension_global_rules := object.union(input_extension, global_rules)
|
||||
output := object.union(extension_global_rules, default_signing_info)
|
||||
not signing_validated(output.signingInfo, output.signingRules)
|
||||
extension := object.union(output, {"signingValidated": false})
|
||||
}
|
||||
|
||||
# Currently if download rules doesn't exist, allow the extension because its name is in the list.
|
||||
# In the future additional rules can be checked with downloadRules present.
|
||||
download_rule_validated(_, rules) if {
|
||||
not rules.downloadRules
|
||||
}
|
||||
|
||||
# Signing is validated if input comes with extension signed, or the input of signing information is matching the
|
||||
# rules in data.
|
||||
signing_validated(signingInfo, signingRules) if {
|
||||
signingInfo
|
||||
signingRules
|
||||
signingInfo.extensionSigned
|
||||
} else if {
|
||||
signingInfo
|
||||
signingRules
|
||||
signingInfo.extensionSigned == signingRules.extensionSigned
|
||||
}
|
||||
@@ -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,
|
||||
v1: 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_v1(v1);
|
||||
|
||||
// Load files from given bundles.
|
||||
for dir in bundles.iter() {
|
||||
let entries =
|
||||
@@ -170,8 +174,40 @@ fn rego_parse(file: String) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn rego_ast(file: String) -> Result<()> {
|
||||
#[cfg(feature = "ast")]
|
||||
{
|
||||
// Create engine.
|
||||
let mut engine = regorus::Engine::new();
|
||||
|
||||
// Create source.
|
||||
#[cfg(feature = "std")]
|
||||
engine.add_policy_from_file(file)?;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
engine.add_policy(file.clone(), read_file(&file)?)?;
|
||||
|
||||
let ast = engine.get_ast_as_json()?;
|
||||
|
||||
println!("{ast}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ast"))]
|
||||
{
|
||||
bail!("`ast` feature must be enabled");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand)]
|
||||
enum RegorusCommand {
|
||||
/// Parse a Rego policy and dump AST.
|
||||
Ast {
|
||||
/// Rego policy file.
|
||||
file: String,
|
||||
},
|
||||
|
||||
/// Evaluate a Rego Query.
|
||||
Eval {
|
||||
/// Directories containing Rego files.
|
||||
@@ -201,6 +237,10 @@ enum RegorusCommand {
|
||||
#[cfg(feature = "coverage")]
|
||||
#[arg(long, short)]
|
||||
coverage: bool,
|
||||
|
||||
/// Turn on rego.v1
|
||||
#[arg(long)]
|
||||
v1: bool,
|
||||
},
|
||||
|
||||
/// Tokenize a Rego policy.
|
||||
@@ -242,6 +282,7 @@ fn main() -> Result<()> {
|
||||
non_strict,
|
||||
#[cfg(feature = "coverage")]
|
||||
coverage,
|
||||
v1,
|
||||
} => rego_eval(
|
||||
&bundles,
|
||||
&data,
|
||||
@@ -251,8 +292,10 @@ fn main() -> Result<()> {
|
||||
non_strict,
|
||||
#[cfg(feature = "coverage")]
|
||||
coverage,
|
||||
v1,
|
||||
),
|
||||
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
|
||||
RegorusCommand::Parse { file } => rego_parse(file),
|
||||
RegorusCommand::Ast { file } => rego_ast(file),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 rego-extensions
|
||||
cargo test -r --test kata 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
|
||||
|
||||
34
src/ast.rs
34
src/ast.rs
@@ -8,12 +8,14 @@ use crate::*;
|
||||
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)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub enum ArithOp {
|
||||
Add,
|
||||
Sub,
|
||||
@@ -23,6 +25,7 @@ pub enum ArithOp {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub enum BoolOp {
|
||||
Lt,
|
||||
Le,
|
||||
@@ -33,12 +36,15 @@ pub enum BoolOp {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub enum AssignOp {
|
||||
Eq,
|
||||
ColEq,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct NodeRef<T> {
|
||||
#[cfg_attr(feature = "ast", serde(flatten))]
|
||||
r: Rc<T>,
|
||||
}
|
||||
|
||||
@@ -97,6 +103,7 @@ impl<T> NodeRef<T> {
|
||||
pub type Ref<T> = NodeRef<T>;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub enum Expr {
|
||||
// Simple items that only have a span as content.
|
||||
String((Span, Value)),
|
||||
@@ -202,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 {
|
||||
@@ -225,11 +239,14 @@ impl Expr {
|
||||
| ArithExpr { span, .. }
|
||||
| AssignExpr { span, .. }
|
||||
| Membership { span, .. } => span,
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
OrExpr { span, .. } => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub enum Literal {
|
||||
SomeVars {
|
||||
span: Span,
|
||||
@@ -259,6 +276,7 @@ pub enum Literal {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct WithModifier {
|
||||
pub span: Span,
|
||||
pub refr: Ref<Expr>,
|
||||
@@ -266,19 +284,23 @@ pub struct WithModifier {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct LiteralStmt {
|
||||
pub span: Span,
|
||||
pub literal: Literal,
|
||||
#[cfg_attr(feature = "ast", serde(skip_serializing_if = "Vec::is_empty"))]
|
||||
pub with_mods: Vec<WithModifier>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct Query {
|
||||
pub span: Span,
|
||||
pub stmts: Vec<LiteralStmt>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct RuleAssign {
|
||||
pub span: Span,
|
||||
pub op: AssignOp,
|
||||
@@ -286,6 +308,7 @@ pub struct RuleAssign {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct RuleBody {
|
||||
pub span: Span,
|
||||
pub assign: Option<RuleAssign>,
|
||||
@@ -293,6 +316,7 @@ pub struct RuleBody {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub enum RuleHead {
|
||||
Compr {
|
||||
span: Span,
|
||||
@@ -313,6 +337,7 @@ pub enum RuleHead {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub enum Rule {
|
||||
Spec {
|
||||
span: Span,
|
||||
@@ -337,22 +362,27 @@ impl Rule {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct Package {
|
||||
pub span: Span,
|
||||
pub refr: Ref<Expr>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct Import {
|
||||
pub span: Span,
|
||||
pub refr: Ref<Expr>,
|
||||
#[cfg_attr(feature = "ast", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub r#as: Option<Span>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct Module {
|
||||
pub package: Package,
|
||||
pub imports: Vec<Import>,
|
||||
#[cfg_attr(feature = "ast", serde(rename(serialize = "rules")))]
|
||||
pub policy: Vec<Ref<Rule>>,
|
||||
pub rego_v1: bool,
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn
|
||||
m.insert("startswith", (startswith, 2));
|
||||
m.insert("strings.any_prefix_match", (any_prefix_match, 2));
|
||||
m.insert("strings.any_suffix_match", (any_suffix_match, 2));
|
||||
m.insert("strings.count", (strings_count, 2));
|
||||
m.insert("strings.replace_n", (replace_n, 2));
|
||||
m.insert("strings.reverse", (reverse, 1));
|
||||
m.insert("substring", (substring, 3));
|
||||
@@ -145,11 +146,18 @@ fn split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
|
||||
let s = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
let delimiter = ensure_string(name, ¶ms[1], &args[1])?;
|
||||
|
||||
Ok(Value::from_array(
|
||||
// Handle https://github.com/microsoft/regorus/issues/291
|
||||
let parts: Vec<Value> = if delimiter.as_ref() == "" {
|
||||
// If delimiter is "", str::split returns a leading and trailing "" whereas Golang's split doesn't.
|
||||
// Therefore avoid str::split and instead return each char as a Value::String.
|
||||
s.chars().map(|c| Value::from(c.to_string())).collect()
|
||||
} else {
|
||||
s.split(delimiter.as_ref())
|
||||
.map(|s| Value::String(s.into()))
|
||||
.collect(),
|
||||
))
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok(Value::from(parts))
|
||||
}
|
||||
|
||||
fn to_string(v: &Value, unescape: bool) -> String {
|
||||
@@ -512,6 +520,27 @@ fn any_suffix_match(
|
||||
))
|
||||
}
|
||||
|
||||
fn strings_count(
|
||||
span: &Span,
|
||||
params: &[Ref<Expr>],
|
||||
args: &[Value],
|
||||
_strict: bool,
|
||||
) -> Result<Value> {
|
||||
let name = "strings.count";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
|
||||
let search = ensure_string(name, ¶ms[0], &args[0])?;
|
||||
let substring = ensure_string(name, ¶ms[0], &args[1])?;
|
||||
|
||||
Ok(Value::from(
|
||||
search
|
||||
.as_bytes()
|
||||
.windows(substring.len())
|
||||
.filter(|&w| w == substring.as_bytes())
|
||||
.count(),
|
||||
))
|
||||
}
|
||||
|
||||
fn startswith(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "startswith";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
|
||||
@@ -121,6 +121,7 @@ fn timestamp(uuid: &Uuid) -> Option<Timestamp> {
|
||||
// https://github.com/uuid-rs/uuid/blob/94ecea893fadac93248f1bd6f47673c09cec5912/src/lib.rs#L900-L904
|
||||
if uuid.get_version_num() == 2 {
|
||||
let (ticks, counter) = decode_rfc4122_timestamp(uuid);
|
||||
#[allow(deprecated)]
|
||||
return Some(Timestamp::from_rfc4122(ticks, counter));
|
||||
}
|
||||
|
||||
|
||||
184
src/engine.rs
184
src/engine.rs
@@ -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,35 @@ impl Engine {
|
||||
modules: vec![],
|
||||
interpreter: Interpreter::new(),
|
||||
prepared: false,
|
||||
rego_v1: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn rego.v1 on/off for subsequently added policies.
|
||||
///
|
||||
/// Explicit import rego.v1 is not needed if set.
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.set_rego_v1(true);
|
||||
/// engine.add_policy(
|
||||
/// "test.rego".to_string(),
|
||||
/// r#"
|
||||
/// package test
|
||||
/// allow if true # if keyword is automatically imported
|
||||
/// "#.to_string())?;
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
pub fn set_rego_v1(&mut self, rego_v1: bool) {
|
||||
self.rego_v1 = rego_v1;
|
||||
}
|
||||
|
||||
/// Add a policy.
|
||||
///
|
||||
/// The policy file will be parsed and converted to AST representation.
|
||||
@@ -67,7 +94,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
|
||||
@@ -98,7 +125,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
|
||||
@@ -129,6 +156,66 @@ impl Engine {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the list of policy files.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # let mut engine = Engine::new();
|
||||
///
|
||||
/// let pkg = engine.add_policy("hello.rego".to_string(), "package test".to_string())?;
|
||||
/// assert_eq!(pkg, "data.test");
|
||||
///
|
||||
/// let policies = engine.get_policies()?;
|
||||
///
|
||||
/// assert_eq!(policies[0].get_path(), "hello.rego");
|
||||
/// assert_eq!(policies[0].get_contents(), "package test");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn get_policies(&self) -> Result<Vec<Source>> {
|
||||
Ok(self
|
||||
.modules
|
||||
.iter()
|
||||
.map(|m| m.package.refr.span().source.clone())
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Get the list of policy files as a JSON object.
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # let mut engine = Engine::new();
|
||||
///
|
||||
/// let pkg = engine.add_policy("hello.rego".to_string(), "package test".to_string())?;
|
||||
/// assert_eq!(pkg, "data.test");
|
||||
///
|
||||
/// let policies = engine.get_policies_as_json()?;
|
||||
///
|
||||
/// let v = Value::from_json_str(&policies)?;
|
||||
/// assert_eq!(v[0]["path"].as_string()?.as_ref(), "hello.rego");
|
||||
/// assert_eq!(v[0]["contents"].as_string()?.as_ref(), "package test");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn get_policies_as_json(&self) -> Result<String> {
|
||||
#[derive(Serialize)]
|
||||
struct Source<'a> {
|
||||
path: &'a String,
|
||||
contents: &'a String,
|
||||
}
|
||||
|
||||
let mut sources = vec![];
|
||||
for m in self.modules.iter() {
|
||||
let source = &m.package.refr.span().source;
|
||||
sources.push(Source {
|
||||
path: source.get_path(),
|
||||
contents: source.get_contents(),
|
||||
});
|
||||
}
|
||||
|
||||
serde_json::to_string_pretty(&sources).map_err(anyhow::Error::msg)
|
||||
}
|
||||
|
||||
/// Set the input document.
|
||||
///
|
||||
/// * `input`: Input documented. Typically this [Value] is constructed from JSON or YAML.
|
||||
@@ -179,7 +266,7 @@ impl Engine {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn clear_data(&mut self) {
|
||||
self.interpreter.set_data(Value::new_object());
|
||||
self.interpreter.set_init_data(Value::new_object());
|
||||
self.prepared = false;
|
||||
}
|
||||
|
||||
@@ -216,7 +303,40 @@ impl Engine {
|
||||
bail!("data must be object");
|
||||
}
|
||||
self.prepared = false;
|
||||
self.interpreter.get_data_mut().merge(data)
|
||||
self.interpreter.get_init_data_mut().merge(data)
|
||||
}
|
||||
|
||||
/// Get the data document.
|
||||
///
|
||||
/// The returned value is the data document that has been constructed using
|
||||
/// one or more calls to [`Engine::add_data`]. The values of policy rules are
|
||||
/// not included in the returned document.
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
/// # use regorus::*;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // If not set, data document is empty.
|
||||
/// assert_eq!(engine.get_data(), Value::new_object());
|
||||
///
|
||||
/// // Merge { "x" : 1, "y" : {} }
|
||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?).is_ok());
|
||||
///
|
||||
/// // Merge { "z" : 2 }
|
||||
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?).is_ok());
|
||||
///
|
||||
/// let data = engine.get_data();
|
||||
/// assert_eq!(data["x"], Value::from(1));
|
||||
/// assert_eq!(data["y"], Value::new_object());
|
||||
/// assert_eq!(data["z"], Value::from(2));
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn get_data(&self) -> Value {
|
||||
self.interpreter.get_init_data().clone()
|
||||
}
|
||||
|
||||
pub fn add_data_json(&mut self, data_json: &str) -> Result<()> {
|
||||
@@ -335,7 +455,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)?;
|
||||
@@ -452,7 +572,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(
|
||||
@@ -477,11 +597,7 @@ impl Engine {
|
||||
self.interpreter.set_modules(&self.modules);
|
||||
|
||||
self.interpreter.clear_builtins_cache();
|
||||
// when the interpreter is prepared the initial data is saved
|
||||
// the data will be reset to init_data each time clean_internal_evaluation_state is called
|
||||
let init_data = self.interpreter.get_data_mut().clone();
|
||||
self.interpreter.set_init_data(init_data);
|
||||
|
||||
// clean_internal_evaluation_state will set data to an efficient clont of use supplied init_data
|
||||
// Initialize the with-document with initial data values.
|
||||
// with-modifiers will be applied to this document.
|
||||
self.interpreter.init_with_document()?;
|
||||
@@ -743,4 +859,50 @@ impl Engine {
|
||||
pub fn take_prints(&mut self) -> Result<Vec<String>> {
|
||||
self.interpreter.take_prints()
|
||||
}
|
||||
|
||||
/// Get the policies and corresponding AST.
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// # use regorus::*;
|
||||
/// # use anyhow::{bail, Result};
|
||||
/// # fn main() -> Result<()> {
|
||||
/// # let mut engine = Engine::new();
|
||||
/// engine.add_policy("test.rego".to_string(), "package test\n x := 1".to_string())?;
|
||||
///
|
||||
/// let ast = engine.get_ast_as_json()?;
|
||||
/// let value = Value::from_json_str(&ast)?;
|
||||
///
|
||||
/// assert_eq!(value[0]["ast"]["package"]["refr"]["Var"][1].as_string()?.as_ref(), "test");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(feature = "ast")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "ast")))]
|
||||
pub fn get_ast_as_json(&self) -> Result<String> {
|
||||
#[derive(Serialize)]
|
||||
struct Policy<'a> {
|
||||
source: &'a Source,
|
||||
version: u32,
|
||||
ast: &'a Module,
|
||||
}
|
||||
let mut ast = vec![];
|
||||
for m in &self.modules {
|
||||
ast.push(Policy {
|
||||
source: &m.package.span.source,
|
||||
version: 1,
|
||||
ast: m,
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,18 +216,22 @@ impl Interpreter {
|
||||
self.modules = modules.to_vec();
|
||||
}
|
||||
|
||||
pub fn set_init_data(&mut self, init_data: Value) {
|
||||
self.init_data = init_data;
|
||||
}
|
||||
|
||||
pub fn set_data(&mut self, data: Value) {
|
||||
self.data = data;
|
||||
}
|
||||
|
||||
pub fn get_data_mut(&mut self) -> &mut Value {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
pub fn set_init_data(&mut self, data: Value) {
|
||||
self.init_data = data;
|
||||
}
|
||||
|
||||
pub fn get_init_data(&self) -> &Value {
|
||||
&self.init_data
|
||||
}
|
||||
|
||||
pub fn get_init_data_mut(&mut self) -> &mut Value {
|
||||
&mut self.init_data
|
||||
}
|
||||
|
||||
pub fn set_traces(&mut self, enable_tracing: bool) {
|
||||
self.traces = match enable_tracing {
|
||||
true => Some(vec![]),
|
||||
@@ -480,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,
|
||||
@@ -550,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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1554,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 {
|
||||
@@ -1688,6 +1702,7 @@ impl Interpreter {
|
||||
}
|
||||
|
||||
if output == Value::Undefined || !comps_defined {
|
||||
ctx.rule_value = Value::Undefined;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -1867,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2274,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;
|
||||
@@ -2299,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(),
|
||||
@@ -2832,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),
|
||||
@@ -2944,7 +2958,6 @@ impl Interpreter {
|
||||
});
|
||||
}
|
||||
result = self.eval_query(&body.query);
|
||||
|
||||
if matches!(&result, Ok(true) | Err(_)) {
|
||||
break;
|
||||
}
|
||||
@@ -3128,6 +3141,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()))
|
||||
|
||||
86
src/lexer.rs
86
src/lexer.rs
@@ -12,17 +12,34 @@ use crate::Value;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
struct SourceInternal {
|
||||
pub file: String,
|
||||
pub contents: String,
|
||||
#[cfg_attr(feature = "ast", serde(skip_serializing))]
|
||||
pub lines: Vec<(u32, u32)>,
|
||||
}
|
||||
|
||||
/// A policy file.
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct Source {
|
||||
#[cfg_attr(feature = "ast", serde(flatten))]
|
||||
src: Rc<SourceInternal>,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
/// The path associated with the policy file.
|
||||
pub fn get_path(&self) -> &String {
|
||||
&self.src.file
|
||||
}
|
||||
|
||||
/// The contents of the policy file.
|
||||
pub fn get_contents(&self) -> &String {
|
||||
&self.src.contents
|
||||
}
|
||||
}
|
||||
|
||||
impl cmp::Ord for Source {
|
||||
fn cmp(&self, other: &Source) -> cmp::Ordering {
|
||||
Rc::as_ptr(&self.src).cmp(&Rc::as_ptr(&other.src))
|
||||
@@ -212,7 +229,9 @@ impl Source {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub struct Span {
|
||||
#[cfg_attr(feature = "ast", serde(skip_serializing))]
|
||||
pub source: Source,
|
||||
pub line: u32,
|
||||
pub col: u32,
|
||||
@@ -274,6 +293,10 @@ pub struct Lexer<'source> {
|
||||
iter: Peekable<CharIndices<'source>>,
|
||||
line: u32,
|
||||
col: u32,
|
||||
unknown_char_is_symbol: bool,
|
||||
allow_slash_star_escape: bool,
|
||||
comment_starts_with_double_slash: bool,
|
||||
double_colon_token: bool,
|
||||
}
|
||||
|
||||
impl<'source> Lexer<'source> {
|
||||
@@ -283,9 +306,29 @@ impl<'source> Lexer<'source> {
|
||||
iter: source.contents().char_indices().peekable(),
|
||||
line: 1,
|
||||
col: 1,
|
||||
unknown_char_is_symbol: false,
|
||||
allow_slash_star_escape: false,
|
||||
comment_starts_with_double_slash: false,
|
||||
double_colon_token: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_unknown_char_is_symbol(&mut self, b: bool) {
|
||||
self.unknown_char_is_symbol = b;
|
||||
}
|
||||
|
||||
pub fn set_allow_slash_star_escape(&mut self, b: bool) {
|
||||
self.allow_slash_star_escape = b;
|
||||
}
|
||||
|
||||
pub fn set_comment_starts_with_double_slash(&mut self, b: bool) {
|
||||
self.comment_starts_with_double_slash = b;
|
||||
}
|
||||
|
||||
pub fn set_double_colon_token(&mut self, b: bool) {
|
||||
self.double_colon_token = b;
|
||||
}
|
||||
|
||||
fn peek(&mut self) -> (usize, char) {
|
||||
match self.iter.peek() {
|
||||
Some((index, chr)) => (*index, *chr),
|
||||
@@ -465,6 +508,7 @@ impl<'source> Lexer<'source> {
|
||||
match ch {
|
||||
// json escape sequence
|
||||
'"' | '\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't' => (),
|
||||
'*' if self.allow_slash_star_escape => (),
|
||||
'u' => {
|
||||
for _i in 0..4 {
|
||||
let (offset, ch) = self.peek();
|
||||
@@ -528,12 +572,24 @@ impl<'source> Lexer<'source> {
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn skip_past_newline(&mut self) -> Result<()> {
|
||||
self.iter.next();
|
||||
loop {
|
||||
match self.peek().1 {
|
||||
'\n' | '\x00' => break,
|
||||
_ => self.iter.next(),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn skip_ws(&mut self) -> Result<()> {
|
||||
// Only the 4 json whitespace characters are recognized.
|
||||
// https://www.crockford.com/mckeeman.html.
|
||||
// Additionally, comments are also skipped.
|
||||
// A tab is considered 4 space characters.
|
||||
'outer: loop {
|
||||
loop {
|
||||
match self.peek().1 {
|
||||
' ' => self.col += 1,
|
||||
'\t' => self.col += 4,
|
||||
@@ -550,14 +606,13 @@ impl<'source> Lexer<'source> {
|
||||
self.col = 1;
|
||||
self.line += 1;
|
||||
}
|
||||
'#' => {
|
||||
self.iter.next();
|
||||
loop {
|
||||
match self.peek().1 {
|
||||
'\n' | '\x00' => continue 'outer,
|
||||
_ => self.iter.next(),
|
||||
};
|
||||
}
|
||||
'#' if !self.comment_starts_with_double_slash => {
|
||||
self.skip_past_newline()?;
|
||||
continue;
|
||||
}
|
||||
'/' if self.comment_starts_with_double_slash && self.peekahead(1).1 == '/' => {
|
||||
self.skip_past_newline()?;
|
||||
continue;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
@@ -601,7 +656,7 @@ impl<'source> Lexer<'source> {
|
||||
self.col += 1;
|
||||
self.iter.next();
|
||||
let mut end = start as u32 + 1;
|
||||
if self.peek().1 == '=' {
|
||||
if self.peek().1 == '=' || (self.peek().1 == ':' && self.double_colon_token) {
|
||||
self.col += 1;
|
||||
self.iter.next();
|
||||
end += 1;
|
||||
@@ -675,6 +730,17 @@ impl<'source> Lexer<'source> {
|
||||
}
|
||||
Ok(ident)
|
||||
}
|
||||
_ if self.unknown_char_is_symbol => {
|
||||
self.col += 1;
|
||||
self.iter.next();
|
||||
Ok(Token(TokenKind::Symbol, Span {
|
||||
source: self.source.clone(),
|
||||
line: self.line,
|
||||
col,
|
||||
start: start as u32,
|
||||
end: start as u32 + 1,
|
||||
}))
|
||||
}
|
||||
_ => Err(self.source.error(self.line, self.col, "invalid character"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// 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.
|
||||
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
||||
// We'll default to building for no_std - use core, alloc instead of std.
|
||||
@@ -28,6 +31,7 @@ mod utils;
|
||||
mod value;
|
||||
|
||||
pub use engine::Engine;
|
||||
pub use lexer::Source;
|
||||
pub use value::Value;
|
||||
|
||||
#[cfg(feature = "arc")]
|
||||
|
||||
@@ -40,6 +40,18 @@ impl<'source> Parser<'source> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enable_rego_v1(&mut self) -> Result<()> {
|
||||
self.turn_on_rego_v1(self.tok.1.clone())
|
||||
}
|
||||
|
||||
fn turn_on_rego_v1(&mut self, span: 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 => {
|
||||
@@ -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(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,
|
||||
@@ -386,7 +392,9 @@ pub struct Analyzer {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Schedule {
|
||||
#[allow(unused)]
|
||||
pub scopes: BTreeMap<Ref<Query>, Scope>,
|
||||
pub order: BTreeMap<Ref<Query>, Vec<u16>>,
|
||||
}
|
||||
@@ -410,7 +418,7 @@ impl Analyzer {
|
||||
}
|
||||
|
||||
pub fn analyze(mut self, modules: &[Ref<Module>]) -> Result<Schedule> {
|
||||
self.add_rules(modules)?;
|
||||
self.add_rules_and_aliases(modules)?;
|
||||
self.functions = gather_functions(modules)?;
|
||||
|
||||
for m in modules {
|
||||
@@ -428,7 +436,7 @@ impl Analyzer {
|
||||
modules: &[Ref<Module>],
|
||||
query: &Ref<Query>,
|
||||
) -> Result<Schedule> {
|
||||
self.add_rules(modules)?;
|
||||
self.add_rules_and_aliases(modules)?;
|
||||
self.analyze_query(None, None, query, Scope::default())?;
|
||||
|
||||
Ok(Schedule {
|
||||
@@ -437,7 +445,7 @@ impl Analyzer {
|
||||
})
|
||||
}
|
||||
|
||||
fn add_rules(&mut self, modules: &[Ref<Module>]) -> Result<()> {
|
||||
fn add_rules_and_aliases(&mut self, modules: &[Ref<Module>]) -> Result<()> {
|
||||
for m in modules {
|
||||
let path = get_path_string(&m.package.refr, Some("data"))?;
|
||||
let scope: &mut Scope = self.packages.entry(path).or_default();
|
||||
@@ -454,6 +462,12 @@ impl Analyzer {
|
||||
};
|
||||
scope.unscoped.insert(var);
|
||||
}
|
||||
|
||||
for import in &m.imports {
|
||||
if let Some(var) = &import.r#as {
|
||||
scope.unscoped.insert(var.source_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -385,6 +385,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) => {
|
||||
@@ -422,3 +427,32 @@ fn one_yaml() -> Result<()> {
|
||||
fn run(path: &str) {
|
||||
yaml_test(path).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_data() -> Result<()> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Merge { "x" : 1, "y" : {} }
|
||||
engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?)?;
|
||||
|
||||
// Merge { "z" : 2 }
|
||||
engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?)?;
|
||||
|
||||
// Add a policy
|
||||
engine.add_policy("policy.rego".to_string(), "package a".to_string())?;
|
||||
|
||||
// Evaluate virtual data document. The virtual document includes all rules as well.
|
||||
let v_data = engine.eval_query("data".to_string(), false)?.result[0].expressions[0]
|
||||
.value
|
||||
.clone();
|
||||
// There must be an empty package.
|
||||
assert_eq!(v_data["a"], Value::new_object());
|
||||
|
||||
// Get the data document.
|
||||
let data = engine.get_data();
|
||||
|
||||
// There must NOT be any value of `a`.
|
||||
assert_eq!(data["a"], Value::Undefined);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
180
src/value.rs
180
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(())
|
||||
/// # }
|
||||
/// ```
|
||||
@@ -902,6 +904,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.
|
||||
|
||||
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."
|
||||
17
tests/interpreter/cases/builtins/strings/split.yaml
Normal file
17
tests/interpreter/cases/builtins/strings/split.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: empty separator
|
||||
data: {}
|
||||
modules: []
|
||||
query: "x := split(\"test\", \"\")"
|
||||
want_result:
|
||||
x: ["t", "e", "s", "t"]
|
||||
|
||||
- note: empty separator, empty string
|
||||
data: {}
|
||||
modules: []
|
||||
query: "x := split(\"\", \"\")"
|
||||
want_result:
|
||||
x: []
|
||||
@@ -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
|
||||
@@ -34,6 +34,7 @@ cases:
|
||||
|
||||
- |
|
||||
package b
|
||||
import rego.v1
|
||||
# Both the following imports are overridden by rules
|
||||
#import data.a.b as a
|
||||
#import data.a.b
|
||||
@@ -43,6 +44,10 @@ cases:
|
||||
|
||||
a = 10
|
||||
c = C + b
|
||||
|
||||
r if {
|
||||
some v in [C]
|
||||
}
|
||||
query: data
|
||||
want_result:
|
||||
a:
|
||||
@@ -50,6 +55,7 @@ cases:
|
||||
b:
|
||||
a: 10
|
||||
c: 22
|
||||
r: true
|
||||
|
||||
- note: import overridden by rule
|
||||
modules:
|
||||
|
||||
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: {}
|
||||
@@ -172,6 +172,64 @@ struct Cli {
|
||||
generate: bool,
|
||||
}
|
||||
|
||||
fn stateful_policy_test() -> Result<()> {
|
||||
// Create an engine for evaluating Rego policies.
|
||||
let mut engine = regorus::Engine::new();
|
||||
|
||||
let policy = String::from(
|
||||
r#"
|
||||
package example
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
print("data.allowed_actions = ", data.allowed_actions)
|
||||
input.action in data.allowed_actions["user1"]
|
||||
print("This rule should be allowed")
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
// Add policy to the engine.
|
||||
engine.add_policy(String::from("policy.rego"), policy)?;
|
||||
|
||||
// Evaluate first input. Expect to evaluate to false, since state is not set
|
||||
engine.set_input(regorus::Value::from_json_str(
|
||||
r#"{
|
||||
"action": "write"
|
||||
}"#,
|
||||
)?);
|
||||
|
||||
let r = engine.eval_bool_query(String::from("data.example.allow"), false)?;
|
||||
println!("Received result: {:?}", r);
|
||||
assert_eq!(r, false);
|
||||
|
||||
// Add data to engine. Set state
|
||||
engine.add_data(regorus::Value::from_json_str(
|
||||
r#"{
|
||||
"allowed_actions": {
|
||||
"user1" : ["read", "write"]
|
||||
}}"#,
|
||||
)?)?;
|
||||
|
||||
// Evaluate second input. Expect to evaluate to true, since state has been set now
|
||||
engine.set_input(regorus::Value::from_json_str(
|
||||
r#"{
|
||||
"action": "write"
|
||||
}"#,
|
||||
)?);
|
||||
|
||||
let r = engine.eval_bool_query(String::from("data.example.allow"), false)?;
|
||||
println!("Received result: {:?}", r);
|
||||
assert_eq!(
|
||||
r, true,
|
||||
"expect result to be true since rule evaluates to true after state has been updated, per rego logs"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
run_kata_tests(
|
||||
@@ -179,5 +237,6 @@ fn main() -> Result<()> {
|
||||
&cli.name,
|
||||
cli.coverage,
|
||||
cli.generate,
|
||||
)
|
||||
)?;
|
||||
stateful_policy_test()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
13
tests/opa.rs
13
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.64.0";
|
||||
const OPA_BRANCH: &str = "v0.69.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_v1_test: bool) -> Result<Value> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[cfg(feature = "coverage")]
|
||||
engine.set_enable_coverage(true);
|
||||
|
||||
engine.set_rego_v1(is_rego_v1_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_v1_test = path_dir_str.starts_with("v1/") || path_dir.starts_with("v1\\");
|
||||
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_v1_test), &case.want_result) {
|
||||
(Ok(actual), Some(expected))
|
||||
if is_json_schema_test && json_schema_tests_check(&actual, &expected) =>
|
||||
{
|
||||
@@ -325,9 +328,9 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
|
||||
if npass == 0 && nfail == 0 {
|
||||
bail!("no matching tests found.");
|
||||
} else if nfail == 0 {
|
||||
println!("\x1b[32m {:42}: {npass:4} {nfail:4}\x1b[0m", "TOTAL");
|
||||
println!("\x1b[32m {:40}: {npass:4} {nfail:4}\x1b[0m", "TOTAL");
|
||||
} else {
|
||||
println!("\x1b[31m {:42}: {npass:4} {nfail:4}\x1b[0m", "TOTAL");
|
||||
println!("\x1b[31m {:40}: {npass:4} {nfail:4}\x1b[0m", "TOTAL");
|
||||
}
|
||||
|
||||
if !missing_functions.is_empty() {
|
||||
|
||||
@@ -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