mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
23 Commits
regorus-v0
...
regorus-v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cabd086619 | ||
|
|
4ec25f37a1 | ||
|
|
c281d28474 | ||
|
|
1bfe38f9af | ||
|
|
5bf7cd7cc8 | ||
|
|
c56da34843 | ||
|
|
61f82d1b34 | ||
|
|
00f45c70fe | ||
|
|
df73b20192 | ||
|
|
992b202f60 | ||
|
|
ce6ecd6fd6 | ||
|
|
37262ccf8f | ||
|
|
dcd040cf40 | ||
|
|
f0a3cf26a0 | ||
|
|
13d8289a58 | ||
|
|
d2b27ee512 | ||
|
|
adb9da0c0c | ||
|
|
dc0f48f6bc | ||
|
|
334db3d6ce | ||
|
|
7565ec3ecf | ||
|
|
8498274356 | ||
|
|
ecd341bbcc | ||
|
|
b6935d1add |
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)
|
||||
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -6,6 +6,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.8](https://github.com/microsoft/regorus/compare/regorus-v0.2.7...regorus-v0.2.8) - 2024-11-06
|
||||
|
||||
### Other
|
||||
- *(deps)* update jsonschema requirement from 0.24.0 to 0.26.1 ([#343](https://github.com/microsoft/regorus/pull/343))
|
||||
- Update to OPA v0.70.0 ([#341](https://github.com/microsoft/regorus/pull/341))
|
||||
|
||||
## [0.2.7](https://github.com/microsoft/regorus/compare/regorus-v0.2.6...regorus-v0.2.7) - 2024-10-22
|
||||
|
||||
### Fixed
|
||||
- docs failing to build ([#334](https://github.com/microsoft/regorus/pull/334))
|
||||
|
||||
### Other
|
||||
- *(deps)* update jsonschema requirement from 0.23.0 to 0.24.0 ([#332](https://github.com/microsoft/regorus/pull/332))
|
||||
- *(deps)* update jsonschema requirement from 0.22.3 to 0.23.0 ([#331](https://github.com/microsoft/regorus/pull/331))
|
||||
|
||||
## [0.2.6](https://github.com/microsoft/regorus/compare/regorus-v0.2.5...regorus-v0.2.6) - 2024-10-09
|
||||
|
||||
### Added
|
||||
- integer conversion functions for Value ([#328](https://github.com/microsoft/regorus/pull/328))
|
||||
|
||||
### Other
|
||||
- update to OPA v0.69.0 ([#327](https://github.com/microsoft/regorus/pull/327))
|
||||
- *(deps)* update jsonschema requirement from 0.21.0 to 0.22.3 ([#326](https://github.com/microsoft/regorus/pull/326))
|
||||
- *(deps)* update jsonschema requirement from 0.20.0 to 0.21.0 ([#325](https://github.com/microsoft/regorus/pull/325))
|
||||
- update to jsonschema 0.20.0 ([#323](https://github.com/microsoft/regorus/pull/323))
|
||||
|
||||
## [0.2.5](https://github.com/microsoft/regorus/compare/regorus-v0.2.4...regorus-v0.2.5) - 2024-09-18
|
||||
|
||||
### Added
|
||||
- or keyword ([#315](https://github.com/microsoft/regorus/pull/315))
|
||||
|
||||
### Fixed
|
||||
- Null terminate C# strings in Rust boundary ([#318](https://github.com/microsoft/regorus/pull/318))
|
||||
- Update readme with correct path to example policy ([#312](https://github.com/microsoft/regorus/pull/312))
|
||||
|
||||
### Other
|
||||
- Update jsonschema requirement from 0.18.0 to 0.19.1 ([#317](https://github.com/microsoft/regorus/pull/317))
|
||||
- Update chrono-tz requirement from 0.8.5 to 0.10.0 ([#316](https://github.com/microsoft/regorus/pull/316))
|
||||
- Add tests for builtin strings::lower method ([#313](https://github.com/microsoft/regorus/pull/313))
|
||||
- Add tests for builtin strings::indexof method ([#311](https://github.com/microsoft/regorus/pull/311))
|
||||
|
||||
## [0.2.4](https://github.com/microsoft/regorus/compare/regorus-v0.2.3...regorus-v0.2.4) - 2024-09-04
|
||||
|
||||
### Added
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -12,7 +12,7 @@ members = [
|
||||
[package]
|
||||
name = "regorus"
|
||||
description = "A fast, lightweight Rego (OPA policy language) interpreter"
|
||||
version = "0.2.4"
|
||||
version = "0.2.8"
|
||||
edition = "2021"
|
||||
license-file = "LICENSE"
|
||||
repository = "https://github.com/microsoft/regorus"
|
||||
@@ -67,7 +67,9 @@ full-opa = [
|
||||
"time",
|
||||
"uuid",
|
||||
"urlquery",
|
||||
"yaml"
|
||||
"yaml",
|
||||
|
||||
#"rego-extensions"
|
||||
]
|
||||
|
||||
# Features that can be used in no_std environments.
|
||||
@@ -89,6 +91,9 @@ opa-no-std = [
|
||||
"lazy_static/spin_no_std"
|
||||
]
|
||||
|
||||
# Rego language extensions
|
||||
rego-extensions = []
|
||||
|
||||
# This feature enables some testing utils for OPA tests.
|
||||
opa-testutil = []
|
||||
rand = ["dep:rand"]
|
||||
@@ -115,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.26.1", 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.68.0](https://github.com/open-policy-agent/opa/releases/tag/v0.68.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.70.0](https://github.com/open-policy-agent/opa/releases/tag/v0.70.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.68.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
||||
Regorus passes the [OPA v0.70.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.68.0](https://github.com/open-policy-agent/opa/releases/tag/v0.68.0)
|
||||
Regorus has been verified to be compliant with [OPA v0.70.0](https://github.com/open-policy-agent/opa/releases/tag/v0.70.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:
|
||||
|
||||
@@ -19,185 +19,195 @@ using System.Threading;
|
||||
namespace Microsoft.WindowsAzure.Regorus.IaaS
|
||||
{
|
||||
|
||||
public class RegorusPolicyEngine : ICloneable, IDisposable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
|
||||
public RegorusPolicyEngine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
public class RegorusPolicyEngine : ICloneable, IDisposable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
|
||||
public RegorusPolicyEngine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (E != null)
|
||||
{
|
||||
RegorusFFI.API.regorus_engine_drop(E);
|
||||
// to avoid Dispose() being called multiple times by mistake.
|
||||
E = null;
|
||||
}
|
||||
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;
|
||||
public object Clone()
|
||||
{
|
||||
var clone = (RegorusPolicyEngine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
var regoBytes = Encoding.UTF8.GetBytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed(byte* regoPtr = regoBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
byte[] NullTerminatedUTF8Bytes(string s)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(s + char.MinValue);
|
||||
}
|
||||
|
||||
public void AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPolicyFromPath(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
public void AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
var regoBytes = NullTerminatedUTF8Bytes(rego);
|
||||
|
||||
string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories);
|
||||
foreach (string file in regoFiles)
|
||||
{
|
||||
AddPolicyFromFile(file);
|
||||
}
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed (byte* regoPtr = regoBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_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 AddPolicyFromPath(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
string[] regoFiles = Directory.GetFiles(path, "*.rego", SearchOption.AllDirectories);
|
||||
foreach (string file in regoFiles)
|
||||
{
|
||||
AddPolicyFromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = Encoding.UTF8.GetBytes(query);
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = NullTerminatedUTF8Bytes(data);
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,219 +2,227 @@ using System.Text;
|
||||
|
||||
namespace Regorus
|
||||
{
|
||||
public class Exception : System.Exception
|
||||
{
|
||||
public Exception(string? message) : base(message) {}
|
||||
}
|
||||
|
||||
public class Engine : ICloneable
|
||||
{
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
public Engine()
|
||||
public class Exception : System.Exception
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
public Exception(string? message) : base(message) { }
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
public class Engine : ICloneable
|
||||
{
|
||||
var clone = (Engine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
|
||||
public string AddPolicy(string path, string rego)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
var regoBytes = Encoding.UTF8.GetBytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
unsafe private RegorusFFI.RegorusEngine* E;
|
||||
public Engine()
|
||||
{
|
||||
fixed(byte* regoPtr = regoBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
E = RegorusFFI.API.regorus_engine_new();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string AddPolicyFromFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
public object Clone()
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
var clone = (Engine)this.MemberwiseClone();
|
||||
unsafe
|
||||
{
|
||||
clone.E = RegorusFFI.API.regorus_engine_clone(E);
|
||||
}
|
||||
return clone;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
var dataBytes = Encoding.UTF8.GetBytes(data);
|
||||
byte[] NullTerminatedUTF8Bytes(string s)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(s + char.MinValue);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
public string AddPolicy(string path, string rego)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
var regoBytes = NullTerminatedUTF8Bytes(rego);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
fixed (byte* regoPtr = regoBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy(E, pathPtr, regoPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
public string AddPolicyFromFile(string path)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_add_policy_from_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
|
||||
var dataBytes = NullTerminatedUTF8Bytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = dataBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_json(E, dataPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
var pathBytes = Encoding.UTF8.GetBytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_add_data_from_json_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = Encoding.UTF8.GetBytes(query);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
|
||||
var inputBytes = NullTerminatedUTF8Bytes(input);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* inputPtr = inputBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_json(E, inputPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalRule(string rule)
|
||||
{
|
||||
var ruleBytes = Encoding.UTF8.GetBytes(rule);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* rulePtr = ruleBytes)
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr));
|
||||
var pathBytes = NullTerminatedUTF8Bytes(path);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* pathPtr = pathBytes)
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_input_from_json_file(E, pathPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEnableCoverage(bool enable)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_enable_coverage(E, enable));
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCoverageData()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_clear_coverage_data(E));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverageReport()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report(E));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCoverageReportPretty()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_get_coverage_report_pretty(E));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGatherPrints(bool enable)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(RegorusFFI.API.regorus_engine_set_gather_prints(E, enable));
|
||||
}
|
||||
}
|
||||
|
||||
public string TakePrints()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_take_prints(E));
|
||||
}
|
||||
}
|
||||
|
||||
~Engine()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
RegorusFFI.API.regorus_engine_drop(E);
|
||||
}
|
||||
}
|
||||
|
||||
public string EvalQuery(string query)
|
||||
{
|
||||
var queryBytes = NullTerminatedUTF8Bytes(query);
|
||||
|
||||
string CheckAndDropResult(RegorusFFI.RegorusResult result)
|
||||
{
|
||||
if (result.status != RegorusFFI.RegorusStatus.RegorusStatusOk) {
|
||||
unsafe {
|
||||
var message = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.error_message);
|
||||
var ex = new Exception(message);
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
throw ex;
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* queryPtr = queryBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, queryPtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resultString = "";
|
||||
unsafe
|
||||
{
|
||||
if (result.output is not null) {
|
||||
resultString = System.Runtime.InteropServices.Marshal.PtrToStringUTF8((IntPtr)result.output);
|
||||
public string EvalRule(string rule)
|
||||
{
|
||||
var ruleBytes = NullTerminatedUTF8Bytes(rule);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* rulePtr = ruleBytes)
|
||||
{
|
||||
return CheckAndDropResult(RegorusFFI.API.regorus_engine_eval_query(E, rulePtr));
|
||||
}
|
||||
}
|
||||
}
|
||||
RegorusFFI.API.regorus_result_drop(result);
|
||||
}
|
||||
return resultString;
|
||||
}
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ GEM
|
||||
rake-compiler-dock (1.5.1)
|
||||
rb_sys (0.9.99)
|
||||
regexp_parser (2.9.2)
|
||||
rexml (3.3.6)
|
||||
strscan
|
||||
rexml (3.3.9)
|
||||
rubocop (1.65.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
@@ -44,7 +43,6 @@ GEM
|
||||
rubocop-rake (0.6.0)
|
||||
rubocop (~> 1.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
strscan (3.1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
|
||||
PLATFORMS
|
||||
|
||||
@@ -18,7 +18,7 @@ coverage = ["regorus/coverage"]
|
||||
[dependencies]
|
||||
regorus = { path = "../..", default-features = false, features = ["arc"] }
|
||||
serde_json = "1.0.111"
|
||||
wasm-bindgen = "0.2.90"
|
||||
wasm-bindgen = "=0.2.93"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.40"
|
||||
|
||||
@@ -191,6 +191,7 @@ mod tests {
|
||||
use wasm_bindgen_test::wasm_bindgen_test;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
#[allow(dead_code)]
|
||||
pub fn basic() -> Result<(), JsValue> {
|
||||
let mut engine = crate::Engine::new();
|
||||
engine.setEnableCoverage(true);
|
||||
|
||||
@@ -61,9 +61,9 @@ in-expr ::= in-expr 'in' bool-expr
|
||||
bool-expr ::= bool-expr bool-op or-expr
|
||||
| or-expr
|
||||
bool-op ::= '<' | '<=' | '==' | '>=' | '>' | '!='
|
||||
or-expr ::= or-expr '|' and-expr
|
||||
| and-expr
|
||||
and-expr ::= and-expr '&' arith-expr
|
||||
set-union-expr ::= set-union-expr '|' set-intersection-expr
|
||||
| set-intersection-expr
|
||||
set-intersection-expr ::= set-intersection-expr '&' arith-expr
|
||||
| arith-expr
|
||||
arith-expr ::= arith-expr ('+' | '-') mul-div-expr
|
||||
| mul-div-expr
|
||||
|
||||
@@ -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
|
||||
|
||||
13
src/ast.rs
13
src/ast.rs
@@ -10,8 +10,8 @@ use core::{cmp, fmt, ops::Deref};
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||
pub enum BinOp {
|
||||
And,
|
||||
Or,
|
||||
Intersection,
|
||||
Union,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
@@ -209,6 +209,13 @@ pub enum Expr {
|
||||
value: Ref<Expr>,
|
||||
collection: Ref<Expr>,
|
||||
},
|
||||
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
OrExpr {
|
||||
span: Span,
|
||||
lhs: Ref<Expr>,
|
||||
rhs: Ref<Expr>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
@@ -232,6 +239,8 @@ impl Expr {
|
||||
| ArithExpr { span, .. }
|
||||
| AssignExpr { span, .. }
|
||||
| Membership { span, .. } => span,
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
OrExpr { span, .. } => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
@@ -445,10 +445,7 @@ fn json_match_schema(
|
||||
match compile_json_schema(¶ms[1], &args[1]) {
|
||||
Ok(schema) => match schema.validate(&document) {
|
||||
Ok(_) => [Value::Bool(true), Value::Null],
|
||||
Err(e) => [
|
||||
Value::Bool(false),
|
||||
Value::from_array(e.map(|e| Value::String(e.to_string().into())).collect()),
|
||||
],
|
||||
Err(e) => [Value::Bool(false), Value::from(e.to_string())],
|
||||
},
|
||||
Err(e) if strict => bail!(params[1]
|
||||
.span()
|
||||
|
||||
@@ -484,6 +484,12 @@ impl Interpreter {
|
||||
self.hoist_loops_impl(rhs, loops);
|
||||
}
|
||||
|
||||
#[cfg(feature = "rego-extensions")]
|
||||
OrExpr { lhs, rhs, .. } => {
|
||||
self.hoist_loops_impl(lhs, loops);
|
||||
self.hoist_loops_impl(rhs, loops);
|
||||
}
|
||||
|
||||
Membership {
|
||||
key,
|
||||
value,
|
||||
@@ -554,8 +560,8 @@ impl Interpreter {
|
||||
}
|
||||
|
||||
match op {
|
||||
BinOp::Or => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
|
||||
BinOp::And => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
|
||||
BinOp::Union => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
|
||||
BinOp::Intersection => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2831,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),
|
||||
@@ -3126,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()))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::doc_lazy_continuation)]
|
||||
// Use README.md as crate documentation.
|
||||
|
||||
@@ -499,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)
|
||||
@@ -700,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()?;
|
||||
|
||||
@@ -712,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),
|
||||
};
|
||||
@@ -720,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),
|
||||
};
|
||||
@@ -742,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;
|
||||
@@ -756,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,
|
||||
@@ -811,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()?;
|
||||
@@ -851,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,
|
||||
@@ -1048,7 +1074,13 @@ impl<'source> Parser<'source> {
|
||||
// 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::Or, .. }) {
|
||||
if matches!(
|
||||
e.as_ref(),
|
||||
Expr::BinExpr {
|
||||
op: BinOp::Union,
|
||||
..
|
||||
}
|
||||
) {
|
||||
*self = state;
|
||||
bail!("try parse as comprehension");
|
||||
}
|
||||
@@ -1116,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,
|
||||
@@ -1264,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 {
|
||||
@@ -1312,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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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."
|
||||
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"
|
||||
@@ -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.68.0";
|
||||
const OPA_BRANCH: &str = "v0.70.0";
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -174,7 +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/");
|
||||
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)?;
|
||||
|
||||
@@ -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