Compare commits

...

4 Commits

Author SHA1 Message Date
Anand Krishnamoorthi
0af97840f7 chore: release (#112)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-19 15:07:49 -08:00
Anand Krishnamoorthi
6eca85b497 Improve crate documentation (#111)
- Document QueryResults
- Delete snippets folder
- Document Value

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-01-19 14:51:14 -08:00
Anand Krishnamoorthi
f3884e87e5 Try out manual trigger for release-plz (#110) 2024-01-16 10:49:39 -08:00
Anand Krishnamoorthi
d39200a52c - Document Location, Expression, QueryResult (#109)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-01-15 17:21:49 -08:00
19 changed files with 1629 additions and 291 deletions

View File

@@ -4,10 +4,7 @@ permissions:
pull-requests: write
contents: write
on:
push:
branches:
- main
on: workflow_dispatch
jobs:
release-plz:
@@ -24,4 +21,4 @@ jobs:
uses: MarcoIeni/release-plz-action@v0.5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View File

@@ -27,11 +27,13 @@ jobs:
- name: Clippy
run: cargo clippy --all-targets --no-deps -- -Dwarnings
- name: Run tests
run: cargo test --verbose
run: cargo test -r --verbose
- name: Build (MUSL)
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl
- name: Run tests (MUSL)
run: cargo test --verbose --target x86_64-unknown-linux-musl
run: cargo test -r --verbose --target x86_64-unknown-linux-musl
- name: Run tests (ACI)
run: cargo test -r --test aci
- name: Run tests (OPA Conformance)
run: >-
cargo test --test opa -- $(tr '\n' ' ' < tests/opa.passing)
cargo test -r --test opa -- $(tr '\n' ' ' < tests/opa.passing)

View File

@@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.1.0-alpha.2](https://github.com/microsoft/regorus/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) - 2024-01-19
### Other
- Improve crate documentation ([#111](https://github.com/microsoft/regorus/pull/111))
- Try out manual trigger for release-plz ([#110](https://github.com/microsoft/regorus/pull/110))
- - Document Location, Expression, QueryResult ([#109](https://github.com/microsoft/regorus/pull/109))
## [0.1.0-alpha.1](https://github.com/microsoft/regorus/releases/tag/v0.1.0-alpha.1) - 2024-01-15
### Fixed

View File

@@ -1,7 +1,7 @@
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.1.0-alpha.1"
version = "0.1.0-alpha.2"
edition = "2021"
license-file = "LICENSE"
repository = "https://github.com/microsoft/regorus"
@@ -82,6 +82,7 @@ uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.17.1", default-features = false, optional = true }
chrono = { version = "0.4.31", optional = true }
chrono-tz = { version = "0.8.5", optional = true }
document-features = "0.2.8"
[dev-dependencies]

View File

@@ -2,11 +2,14 @@
**Regorus** is
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) interpreter written in Rust.
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
interpreter written in Rust.
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
Regorus is available as a library that can be easily integrated into your Rust projects.
Here is an example of evaluating a simple Rego policy:
```rust
use anyhow::Result;
use regorus::*;
@@ -21,11 +24,11 @@ fn main() -> Result<()> {
// Filename to be associated with the policy.
"hello.rego".to_string(),
// Rego policy that just sets a message.
r#"
package test
message = "Hello, World!"
"#.to_string()
// Rego policy that just sets a message.
r#"
package test
message = "Hello, World!"
"#.to_string()
)?;
// Evaluate the policy, fetch the message and print it.
@@ -36,12 +39,32 @@ fn main() -> Result<()> {
}
```
Regorus passes the [OPA v0.60.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few builtins.
See [OPA Conformance](#opa-conformance) below.
Regorus is designed with [Confidential Computing](https://confidentialcomputing.io/about/) in mind. In Confidential Computing environments,
it is important to be able to control exactly what is being run. Regorus allows enabling and disabling various components using cargo
features. By default all features are enabled.
The default build of regorus example program is 6.4M:
```bash
$ cargo build -r --example regorus; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
$ cargo build -r --example regorus; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 6.4M Jan 19 11:23 target/release/examples/regorus*
```
When all features except for `yaml` are disabled, the binary size drops down to 2.9M.
```bash
$ cargo build -r --example regorus --features "yaml" --no-default-features; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
-rwxr-xr-x 1 anand staff 2.9M Jan 19 11:26 target/release/examples/regorus*
```
Regorus passes the [OPA v0.60.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
builtins. See [OPA Conformance](#opa-conformance) below.
## Getting Started
[examples/regorus](https://github.com/microsoft/regorus/blob/main/examples/regorus.rs) is an example program that shows how to integrate Regorus into your project and evaluate Rego policies.
[examples/regorus](https://github.com/microsoft/regorus/blob/main/examples/regorus.rs) is an example program that
shows how to integrate Regorus into your project and evaluate Rego policies.
To build and install it, do
@@ -94,7 +117,8 @@ This produces the following output
}
```
Next, evaluate a sample [policy](examples/example.rego) and [input](examples/input.json) (borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)):
Next, evaluate a sample [policy](examples/example.rego) and [input](examples/input.json)
(borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)):
```bash
$ regorus eval -d examples/example.rego -i examples/input.json data.example
@@ -178,7 +202,8 @@ The test driver can be invoked by running:
$ cargo test -r --test opa
```
Currently, Regorus passes all the non-builtin specific tests. See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing).
Currently, Regorus passes all the non-builtin specific tests.
See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing).
The following test suites don't pass fully due to mising builtins:
- `cryptoparsersaprivatekeys`
@@ -219,7 +244,9 @@ They are captured in the following [github issues](https://github.com/microsoft/
### Grammar
The grammar used by Regorus to parse Rego policies is described in [grammar.md](https://github.com/microsoft/regorus/blob/main/docs/grammar.md) in both [W3C EBNF](https://www.w3.org/Notation.html) and [RailRoad Diagram](https://en.wikipedia.org/wiki/Syntax_diagram) formats.
The grammar used by Regorus to parse Rego policies is described in [grammar.md](https://github.com/microsoft/regorus/blob/main/docs/grammar.md)
in both [W3C EBNF](https://www.w3.org/Notation.html) and [RailRoad Diagram](https://en.wikipedia.org/wiki/Syntax_diagram) formats.
## Contributing

239
docs/builtins.md Normal file
View File

@@ -0,0 +1,239 @@
# Built-in Functions
This page lists all the supported Rego built-in functions and the cargo feature that is needed to enable each builtin.
Those builtins that are not need for a specific use of the Regorus crate can be excluded from the binary by not specifying
the corresponding feature. This is useful in Confidential Computing scenarios where
- There needs to be control over what a policy execution can and cannot do.
- There needs to be control over exactly what goes into the [Trusted Computing Base](https://en.wikipedia.org/wiki/Trusted_computing_base).
Currently many builtins are `baked-in`, i.e. there is no way to exclude them from the TCB.
In future, each builtin will be associated with a feature (many builtins could be associated with the same feature).
- [Comparison](https://www.openpolicyagent.org/docs/latest/policy-reference/#comparison)
| Builtin | Feature |
|--------------------------------------------------------------------------------------------------|---------|
| [x == y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-equal) | _ |
| [x > y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-gt) | _ |
| [x >= y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-gte) | _ |
| [x < y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-lt) | _ |
| [x <= y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-lte) | _ |
| [x != y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-comparison-neq) | _ |
- [Numbers](https://www.openpolicyagent.org/docs/latest/policy-reference/#numbers)
| Builtin | Feature |
|-----------------------------------------------------------------------------------------------------------------------|---------|
| [abs](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-abs) | _ |
| [ceil](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-ceil) | _ |
| [x / y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-div) | _ |
| [floor](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-floor) | _ |
| [x - y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-minus) | _ |
| [x * y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-mul) | _ |
| [numbers.range](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-numbersrange) | _ |
| [numbers.range_step](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-numbersrange_step) | _ |
| [x + y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-plus) | _ |
| [rand.intn](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-randintn) | _ |
| [x % y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-rem) | _ |
| [round](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-numbers-round) | _ |
- [Aggregates](https://www.openpolicyagent.org/docs/latest/policy-reference/#aggregates)
| Builtin | Feature |
|-----------------------------------------------------------------------------------------------------|---------|
| [count](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-count) | _ |
| [max](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-max) | _ |
| [min](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-min) | _ |
| [product](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-product) | _ |
| [sort](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-sort) | _ |
| [sum](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-aggregates-sum) | _ |
- [Arrays](https://www.openpolicyagent.org/docs/latest/policy-reference/#arrays-2)
| Builtin | Feature |
|-----------------------------------------------------------------------------------------------------------|---------|
| [array.concat](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-array-arrayconcat) | _ |
| [array.reverse](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-array-arrayreverse) | _ |
| [array.slice](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-array-arrayslice) | _ |
- [Sets](https://www.openpolicyagent.org/docs/latest/policy-reference/#sets-2)
| Builtin | Feature |
|---------------------------------------------------------------------------------------------------------|---------|
| [x & y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-and) | _ |
| [intersection](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-intersection) | _ |
| [x - y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-minus) | _ |
| [x \| y](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-or) | _ |
| [union](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-sets-union) | _ |
- [Objects](https://www.openpolicyagent.org/docs/latest/policy-reference/#object)
| Builtin | Feature |
|----------------------------------------------------------------------------------------------------------------------|--------------|
| [json.filter](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-jsonfilter) | _ |
| [json.match_schema](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-jsonmatch_schema) | `jsonschema` |
| [json.remove](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-jsonremove) | _ |
| [json.verify_schema](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-jsonverify_schema) | `jsonschema` |
| [object.filter](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectfilter) | _ |
| [object.get](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectget) | _ |
| [object.keys](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectkeys) | _ |
| [object.remove](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectremove) | _ |
| [object.subset](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectsubset) | _ |
| [object.union](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectunion) | _ |
| [object.union_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-object-objectunion_n) | _ |
- [Strings](https://www.openpolicyagent.org/docs/latest/policy-reference/#strings)
| Builtin | Feature |
|-----------------------------------------------------------------------------------------------------------------------------------|---------|
| [concat](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-concat) | _ |
| [contains](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-contains) | _ |
| [endswith](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-endswith) | _ |
| [format_int](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-format_int) | _ |
| [indexof](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-indexof) | _ |
| [indexof_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-indexof_n) | _ |
| [lower](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-lower) | _ |
| [replace](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-replace) | _ |
| [split](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-split) | _ |
| [sprintf](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-sprintf) | _ |
| [startswith](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-startswith) | _ |
| [strings.any_prefix_match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsany_prefix_match) | _ |
| [strings.any_suffix_match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsany_suffix_match) | _ |
| [strings.render_template](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsrender_template) | _ |
| [strings.replace_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsreplace_n) | _ |
| [strings.reverse](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-stringsreverse) | _ |
| [substring](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-substring) | _ |
| [trim](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim) | _ |
| [trim_left](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_left) | _ |
| [trim_prefix](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_prefix) | _ |
| [trim_right](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_right) | _ |
| [trim_space](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_space) | _ |
| [trim_suffix](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-trim_suffix) | _ |
| [upper](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-strings-upper) | _ |
- [Regex](https://www.openpolicyagent.org/docs/latest/policy-reference/#regex)
| Builtin | Feature |
|-------------------------------------------------------------------------------------------------------------------------------------------------|---------|
| [regex.find_all_string_submatch_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexfind_all_string_submatch_n) | `regex` |
| [regex.find_n](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexfind_n) | `regex` |
| [regex.globs_match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexglobs_match) | `regex` |
| [regex.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexis_valid) | `regex` |
| [regex.match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexmatch) | `regex` |
| [regex.replace](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexreplace) | `regex` |
| [regex.split](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regexsplit) | `regex` |
| [regex.template_match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-regex-regextemplate_match) | `regex` |
- [Glob](https://www.openpolicyagent.org/docs/latest/policy-reference/#regex)
| Builtin | Feature |
|--------------------------------------------------------------------------------------------------------------|---------|
| [glob.match](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-glob-globmatch) | `glob` |
| [glob.quote_meta](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-glob-globquote_meta) | `glob` |
- [Bitwise](https://www.openpolicyagent.org/docs/latest/policy-reference/#regex)
| Builtin | Feature |
|------------------------------------------------------------------------------------------------------|---------|
| [bits.and](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsand) | _ |
| [bits.lsh](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitslsh) | _ |
| [bits.negate](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsnegate) | _ |
| [bits.or](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsor) | _ |
| [bits.rsh](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsrsh) | _ |
| [bits.xor](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-bits-bitsxor) | _ |
- [Conversions](https://www.openpolicyagent.org/docs/latest/policy-reference/#conversions)
| Builtin | Feature |
|-------|---------|
[to_number](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-conversions-to_number) | _ |
|
- [Units](https://www.openpolicyagent.org/docs/latest/policy-reference/#units)
| Builtin | Feature |
|-------------------------------------------------------------------------------------------------------------------|---------|
| [units.parse](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-units-unitsparse) | _ |
| [units.parse_bytes](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-units-unitsparse_bytes) | _ |
- [Types](https://www.openpolicyagent.org/docs/latest/policy-reference/#types)
| Builtin | Feature |
|------------------------------------------------------------------------------------------------------|---------|
| [is_array](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_array) | _ |
| [is_boolean](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_boolean) | _ |
| [is_null](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_null) | _ |
| [is_number](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_number) | _ |
| [is_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_object) | _ |
| [is_set](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_set) | _ |
| [is_string](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-is_string) | _ |
| [type_name](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-type_name) | _ |
- [Encoding](https://www.openpolicyagent.org/docs/latest/policy-reference/#encoding)
| Builtin | Feature |
|----------------------------------------------------------------------------------------------------------------------------------|-------------|
| [base64.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64is_valid) | `base64` |
| [base64url.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urldecode) | `base64` |
| [base64url.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode) | `base64url` |
| [base64url.encode_no_pad](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode_no_pad) | `base64url` |
| [hex.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexdecode) | `hex` |
| [hex.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexencode) | `hex` |
| [json.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonis_valid) | _ |
| [json.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonmarshal) | _ |
| [json.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonunmarshal) | _ |
| [urlquery.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode) | `urlquery` |
| [urlquery.decode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode_object) | `urlquery` |
| [urlquery.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode) | `urlquery` |
| [urlquery.encode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode_object) | `urlquery` |
| [yaml.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlis_valid) | `yaml` |
| [yaml.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlmarshal) | `yaml` |
| [yaml.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlunmarshal) | `yaml` |
- [Time](https://www.openpolicyagent.org/docs/latest/policy-reference/#time)
| Builtin | Feature |
|----------------------------------------------------------------------------------------------------------------------------|---------|
| ([time.add_date](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeadd_date) | `time` |
| [time.add_date](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeadd_date) | `time` |
| [time.clock](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeclock) | `time` |
| [time.date](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timedate) | `time` |
| [time.diff](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timediff) | `time` |
| [time.format](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeformat) | `time` |
| [time.now_ns](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timenow_ns) | `time` |
| [time.parse_duration_ns](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeparse_duration_ns) | `time` |
| [time.parse_ns](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeparse_ns) | `time` |
| [time.parse_rfc3339_ns](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeparse_rfc3339_ns) | `time` |
| [time.weekday](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-time-timeweekday) | `time` |
- [Cryptography](https://www.openpolicyagent.org/docs/latest/policy-reference/#crypto)
| Builtin | Feature |
|---------------------------------------------------------------------------------------------------------------------|----------|
| [crypto.hmac.equal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacequal) | `crypto` |
| [crypto.hmac.md5](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacmd5) | `crypto` |
| [crypto.hmac.sha1](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacsha1) | `crypto` |
| [crypto.hmac.sha256](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacsha256) | `crypto` |
| [crypto.hmac.sha512](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptohmacsha512) | `crypto` |
| [crypto.md5](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptomd5) | `crypto` |
| [crypto.sha1](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptosha1) | `crypto` |
| [crypto.sha256](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-crypto-cryptosha256) | `crypto` |
- [Graphs](https://www.openpolicyagent.org/docs/latest/policy-reference/#graph)
| Builtin | Feature |
|---------------------------------------------------------------------------------------------------------------|---------|
| [graph.reachable](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-graph-graphreachable) | `graph` |
| [walk](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-graph-walk) | `graph` |
- [UUID](https://www.openpolicyagent.org/docs/latest/policy-reference/#uuid)
| Builtin | Feature |
|--------------------------------------------------------------------------------------------------------|---------|
| [uuid.parse](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-uuid-uuidparse) | `uuid` |
| [uuid.rfc4122](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-uuid-uuidrfc4122) | `uuid` |
- [Semantic Versions](https://www.openpolicyagent.org/docs/latest/policy-reference/#semver)
| Builtin | Feature |
|----------------------------------------------------------------------------------------------------------------|----------|
| [semver.compare](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-semver-semvercompare) | `semver` |
| [semver.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-semver-semveris_valid) | `semver` |
- [OPA](https://www.openpolicyagent.org/docs/latest/policy-reference/#opa
| Builtin | Feature |
|-----------------------------------------------------------------------------------------------------|---------|
| [opa.runtime](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-opa-oparuntime) | _ |
- [Debugging](https://www.openpolicyagent.org/docs/latest/policy-reference/#opa)
| Builtin | Feature |
|---------------------------------------------------------------------------------|---------|
| [print(...)](https://www.openpolicyagent.org/docs/latest/policy-reference/#opa) | _ |
- [Tracing](https://www.openpolicyagent.org/docs/latest/policy-reference/#tracing)
| Builtin | Feature |
|----------------------------------------------------------------------------------------------|---------|
| [trace](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-tracing-trace) | _ |

View File

@@ -1,23 +0,0 @@
Cpackage play
a := {4}
mydoc(x) := path {
path := "data.play.a"
}
x := [ y |
y := data.play.a | data.play.b with data.play.a as {5} with data.play.b as {6}
]
r := [ m | m := data.play.p with data.play.p as 5 + 6; true ]
allow {
input.x
== 5
input.y == 5
input.y
== 5
}

View File

@@ -28,11 +28,14 @@ fn print(span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> R
let mut msg = String::default();
for a in args {
match a {
Value::Undefined => msg += "<undefined>",
_ => msg += format!("{a}").as_str(),
Value::Undefined => msg += " <undefined>",
Value::String(s) => msg += &format!(" {s}"),
_ => msg += &format!(" {a}"),
};
}
span.message("print", msg.as_str());
if !msg.is_empty() {
println!("{}", &msg[1..]);
}
Ok(Value::Bool(true))
}

View File

@@ -3,14 +3,16 @@
use crate::ast::{Expr, Ref};
use crate::builtins;
#[allow(unused)]
use crate::builtins::utils::{
ensure_args_count, ensure_object, ensure_string, ensure_string_collection,
};
use crate::lexer::Span;
use crate::value::Value;
use std::collections::{BTreeMap, HashMap};
use std::collections::HashMap;
#[allow(unused)]
use anyhow::{anyhow, bail, Context, Result};
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
@@ -41,11 +43,6 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("json.is_valid", (json_is_valid, 1));
m.insert("json.marshal", (json_marshal, 1));
m.insert("json.unmarshal", (json_unmarshal, 1));
#[cfg(feature = "jsonschema")]
{
m.insert("json.match_schema", (json_match_schema, 2));
m.insert("json.verify_schema", (json_verify_schema, 1));
}
#[cfg(feature = "yaml")]
{
@@ -240,7 +237,7 @@ fn urlquery_decode_object(
Err(_) => bail!(params[0].span().error("not a valid url query")),
};
let mut map = BTreeMap::new();
let mut map = std::collections::BTreeMap::new();
for (k, v) in url.query_pairs() {
let key = Value::String(k.clone().into());
let value = Value::String(v.clone().into());
@@ -382,72 +379,3 @@ fn json_unmarshal(
let json_str = ensure_string(name, &params[0], &args[0])?;
Value::from_json_str(&json_str).with_context(|| span.error("could not deserialize json."))
}
#[cfg(feature = "jsonschema")]
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::JSONSchema> {
let schema_str = match arg {
Value::String(schema_str) => schema_str.as_ref().to_string(),
_ => arg.to_json_str()?,
};
if let Ok(schema) = serde_json::from_str(&schema_str) {
match jsonschema::JSONSchema::compile(&schema) {
Ok(schema) => return Ok(schema),
Err(e) => bail!(e.to_string()),
}
}
bail!(param.span().error("not a valid json schema"))
}
#[cfg(feature = "jsonschema")]
fn json_verify_schema(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
strict: bool,
) -> Result<Value> {
let name = "json.verify_schema";
ensure_args_count(span, name, params, args, 1)?;
Ok(Value::from_array(
match compile_json_schema(&params[0], &args[0]) {
Ok(_) => [Value::Bool(true), Value::Null],
Err(e) if strict => bail!(params[0]
.span()
.error(format!("invalid schema: {e}").as_str())),
Err(e) => [Value::Bool(false), Value::String(e.to_string().into())],
}
.to_vec(),
))
}
#[cfg(feature = "jsonschema")]
fn json_match_schema(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
strict: bool,
) -> Result<Value> {
let name = "json.match_schema";
ensure_args_count(span, name, params, args, 2)?;
// The following is expected to succeed.
let document: serde_json::Value = serde_json::from_str(&args[0].to_json_str()?)?;
Ok(Value::from_array(
match compile_json_schema(&params[1], &args[1]) {
Ok(schema) => match schema.validate(&document) {
Ok(_) => [Value::Bool(true), Value::Null],
Err(e) => [
Value::Bool(false),
Value::from_array(e.map(|e| Value::String(e.to_string().into())).collect()),
],
},
Err(e) if strict => bail!(params[1]
.span()
.error(format!("invalid schema: {e}").as_str())),
Err(e) => [Value::Bool(false), Value::String(e.to_string().into())],
}
.to_vec(),
))
}

View File

@@ -23,6 +23,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("object.subset", (subset, 2));
m.insert("object.union", (object_union, 2));
m.insert("object.union_n", (object_union_n, 1));
#[cfg(feature = "jsonschema")]
{
m.insert("json.match_schema", (json_match_schema, 2));
m.insert("json.verify_schema", (json_verify_schema, 1));
}
}
fn json_filter_impl(v: &Value, filter: &Value) -> Value {
@@ -382,3 +388,72 @@ fn object_union_n(
Ok(u)
}
#[cfg(feature = "jsonschema")]
fn compile_json_schema(param: &Ref<Expr>, arg: &Value) -> Result<jsonschema::JSONSchema> {
let schema_str = match arg {
Value::String(schema_str) => schema_str.as_ref().to_string(),
_ => arg.to_json_str()?,
};
if let Ok(schema) = serde_json::from_str(&schema_str) {
match jsonschema::JSONSchema::compile(&schema) {
Ok(schema) => return Ok(schema),
Err(e) => bail!(e.to_string()),
}
}
bail!(param.span().error("not a valid json schema"))
}
#[cfg(feature = "jsonschema")]
fn json_verify_schema(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
strict: bool,
) -> Result<Value> {
let name = "json.verify_schema";
ensure_args_count(span, name, params, args, 1)?;
Ok(Value::from_array(
match compile_json_schema(&params[0], &args[0]) {
Ok(_) => [Value::Bool(true), Value::Null],
Err(e) if strict => bail!(params[0]
.span()
.error(format!("invalid schema: {e}").as_str())),
Err(e) => [Value::Bool(false), Value::String(e.to_string().into())],
}
.to_vec(),
))
}
#[cfg(feature = "jsonschema")]
fn json_match_schema(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
strict: bool,
) -> Result<Value> {
let name = "json.match_schema";
ensure_args_count(span, name, params, args, 2)?;
// The following is expected to succeed.
let document: serde_json::Value = serde_json::from_str(&args[0].to_json_str()?)?;
Ok(Value::from_array(
match compile_json_schema(&params[1], &args[1]) {
Ok(schema) => match schema.validate(&document) {
Ok(_) => [Value::Bool(true), Value::Null],
Err(e) => [
Value::Bool(false),
Value::from_array(e.map(|e| Value::String(e.to_string().into())).collect()),
],
},
Err(e) if strict => bail!(params[1]
.span()
.error(format!("invalid schema: {e}").as_str())),
Err(e) => [Value::Bool(false), Value::String(e.to_string().into())],
}
.to_vec(),
))
}

View File

@@ -8,12 +8,15 @@ use crate::parser::*;
use crate::scheduler::*;
use crate::utils::gather_functions;
use crate::value::*;
use crate::QueryResults;
use std::convert::AsRef;
use std::path::Path;
use anyhow::Result;
/// The Rego evaluation engine.
///
#[derive(Clone)]
pub struct Engine {
modules: Vec<Ref<Module>>,
@@ -21,6 +24,7 @@ pub struct Engine {
prepared: bool,
}
/// Create a default engine.
impl Default for Engine {
fn default() -> Self {
Self::new()
@@ -28,6 +32,7 @@ impl Default for Engine {
}
impl Engine {
/// Create an instance of [Engine].
pub fn new() -> Self {
Self {
modules: vec![],
@@ -36,6 +41,29 @@ impl Engine {
}
}
/// Add a policy.
///
/// The policy file will be parsed and converted to AST representation.
/// Multiple policy files may be added to the engine.
///
/// * `path`: A filename to be associated with the policy.
/// * `rego`: The rego policy code.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// engine.add_policy(
/// "test.rego".to_string(),
/// r#"
/// package test
/// allow = input.user == "root"
/// "#.to_string())?;
/// # Ok(())
/// # }
/// ```
///
pub fn add_policy(&mut self, path: String, rego: String) -> Result<()> {
let source = Source::new(path, rego);
let mut parser = Parser::new(&source)?;
@@ -45,6 +73,22 @@ impl Engine {
Ok(())
}
/// Add a policy from a given file.
///
/// The policy file will be parsed and converted to AST representation.
/// Multiple policy files may be added to the engine.
///
/// * `path`: Path to the policy file (.rego).
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// engine.add_policy_from_file("tests/aci/framework.rego")?;
/// # Ok(())
/// # }
/// ```
pub fn add_policy_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let source = Source::from_file(path)?;
let mut parser = Parser::new(&source)?;
@@ -53,28 +97,163 @@ impl Engine {
Ok(())
}
/// Set the input document.
///
/// * `input`: Input documented. Typically this [Value] is constructed from JSON or YAML.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// let input = Value::from_json_str(r#"
/// {
/// "role" : "admin",
/// "action": "delete"
/// }"#)?;
///
/// engine.set_input(input);
/// # Ok(())
/// # }
/// ```
pub fn set_input(&mut self, input: Value) {
self.interpreter.set_input(input);
}
/// Clear the data document.
///
/// The data document will be reset to an empty object.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// engine.clear_data();
///
/// // Evaluate data.
/// let results = engine.eval_query("data".to_string(), false)?;
///
/// // Assert that it is empty object.
/// assert_eq!(results.result.len(), 1);
/// assert_eq!(results.result[0].expressions.len(), 1);
/// assert_eq!(results.result[0].expressions[0].value, Value::new_object());
/// # Ok(())
/// # }
/// ```
pub fn clear_data(&mut self) {
self.interpreter.set_data(Value::new_object());
self.prepared = false;
}
/// Add data document.
///
/// The specified data document is merged into existing data document.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// // Only objects can be added.
/// assert!(engine.add_data(Value::from_json_str("[]")?).is_err());
///
/// // Merge { "x" : 1, "y" : {} }
/// assert!(engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?).is_ok());
///
/// // Merge { "z" : 2 }
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?).is_ok());
///
/// // Merge { "z" : 3 }. Conflict error.
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 3 }"#)?).is_err());
///
/// assert_eq!(
/// engine.eval_query("data".to_string(), false)?.result[0].expressions[0].value,
/// Value::from_json_str(r#"{ "x": 1, "y": {}, "z": 2}"#)?
/// );
/// # Ok(())
/// # }
/// ```
pub fn add_data(&mut self, data: Value) -> Result<()> {
self.prepared = false;
self.interpreter.get_data_mut().merge(data)
}
pub fn get_modules(&mut self) -> &Vec<Ref<Module>> {
&self.modules
}
/// Set whether builtins should raise errors strictly or not.
///
/// Regorus differs from OPA in that by default builtins will
/// raise errors instead of returning Undefined.
///
/// ----
/// **_NOTE:_** Currently not all builtins honor this flag and will always strictly raise errors.
/// ----
pub fn set_strict_builtin_errors(&mut self, b: bool) {
self.interpreter.set_strict_builtin_errors(b)
}
#[doc(hidden)]
pub fn get_modules(&mut self) -> &Vec<Ref<Module>> {
&self.modules
}
/// Evaluate a Rego query.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// // Add policies
/// engine.add_policy_from_file("tests/aci/framework.rego")?;
/// engine.add_policy_from_file("tests/aci/api.rego")?;
/// engine.add_policy_from_file("tests/aci/policy.rego")?;
///
/// // Add data document (if any).
/// // If multiple data documents can be added, they will be merged together.
/// engine.add_data(Value::from_json_file("tests/aci/data.json")?)?;
///
/// // At this point the policies and data have been loaded.
/// // Either the same engine can be used to make multiple queries or the engine
/// // can be cloned to avoid having the reload the policies and data.
/// let _clone = engine.clone();
///
/// // Evaluate a query.
/// // Load input and make query.
/// engine.set_input(Value::new_object());
/// let results = engine.eval_query("data.framework.mount_overlay.allowed".to_string(), false)?;
/// assert!(results.result.is_empty());
///
/// // Evaluate query with different inputs.
/// engine.set_input(Value::from_json_file("tests/aci/input.json")?);
/// let results = engine.eval_query("data.framework.mount_overlay.allowed".to_string(), false)?;
/// assert_eq!(results.result[0].expressions[0].value, Value::from(true));
/// # Ok(())
/// # }
pub fn eval_query(&mut self, query: String, enable_tracing: bool) -> Result<QueryResults> {
self.eval_modules(enable_tracing)?;
let query_module = {
let source = Source::new(
"<query_module.rego>".to_owned(),
"package __internal_query_module".to_owned(),
);
Ref::new(Parser::new(&source)?.parse()?)
};
// Parse the query.
let query_source = Source::new("<query.rego>".to_string(), query);
let mut parser = Parser::new(&query_source)?;
let query_node = parser.parse_user_query()?;
let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
self.interpreter.eval_user_query(
&query_module,
&query_node,
&query_schedule,
enable_tracing,
)
}
#[doc(hidden)]
fn prepare_for_eval(&mut self, enable_tracing: bool) -> Result<()> {
self.interpreter.set_traces(enable_tracing);
@@ -107,6 +286,7 @@ impl Engine {
Ok(())
}
#[doc(hidden)]
pub fn eval_rule(
&mut self,
module: &Ref<Module>,
@@ -121,6 +301,7 @@ impl Engine {
Ok(self.interpreter.get_data_mut().clone())
}
#[doc(hidden)]
pub fn eval_modules(&mut self, enable_tracing: bool) -> Result<Value> {
self.prepare_for_eval(enable_tracing)?;
self.interpreter.clean_internal_evaluation_state();
@@ -164,30 +345,4 @@ impl Engine {
self.interpreter.create_rule_prefixes()?;
Ok(self.interpreter.get_data_mut().clone())
}
pub fn eval_query(&mut self, query: String, enable_tracing: bool) -> Result<QueryResults> {
self.eval_modules(false)?;
let query_module = {
let source = Source::new(
"<query_module.rego>".to_owned(),
"package __internal_query_module".to_owned(),
);
Ref::new(Parser::new(&source)?.parse()?)
};
// Parse the query.
let query_source = Source::new("<query.rego>".to_string(), query);
let mut parser = Parser::new(&query_source)?;
let query_node = parser.parse_user_query()?;
let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
let results = self.interpreter.eval_user_query(
&query_module,
&query_node,
&query_schedule,
enable_tracing,
)?;
Ok(results)
}
}

View File

@@ -9,10 +9,10 @@ use crate::parser::Parser;
use crate::scheduler::*;
use crate::utils::*;
use crate::value::*;
use crate::{Expression, Location, QueryResult, QueryResults};
use anyhow::{anyhow, bail, Result};
use log::info;
use serde::Serialize;
use std::collections::btree_map::Entry as BTreeMapEntry;
use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap};
use std::ops::Bound::*;
@@ -73,42 +73,6 @@ impl Default for Interpreter {
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Location {
pub row: u16,
pub col: u16,
}
#[derive(Debug, Clone, Serialize)]
pub struct Expression {
pub value: Value,
pub text: Rc<str>,
pub location: Location,
}
#[derive(Debug, Clone, Serialize)]
pub struct QueryResult {
// Expressions is shown first to match OPA.
pub expressions: Vec<Expression>,
#[serde(skip_serializing_if = "Value::is_empty_object")]
pub bindings: Value,
}
impl Default for QueryResult {
fn default() -> Self {
Self {
bindings: Value::new_object(),
expressions: vec![],
}
}
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct QueryResults {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub result: Vec<QueryResult>,
}
#[derive(Debug, Clone)]
struct Context {
key_expr: Option<ExprRef>,
@@ -271,6 +235,7 @@ impl Interpreter {
self.loop_var_values.clear();
self.scopes = vec![Scope::new()];
self.contexts = vec![];
self.rule_values.clear();
}
fn current_module(&self) -> Result<Ref<Module>> {
@@ -383,7 +348,7 @@ impl Interpreter {
&& get_root_var(refr)?.text() == "data"
{
let index = index.to_string();
v = obj[&index].clone();
v = obj[index].clone();
}
return Ok(Self::get_value_chained(v, &path[..]));
}
@@ -1346,6 +1311,13 @@ impl Interpreter {
r
}
fn clear_scope(scope: &mut Scope) {
// Set each value to undefined. This is equivalent to removing the key.
for (_, v) in scope.iter_mut() {
*v = Value::Undefined;
}
}
fn eval_stmts_in_loop(&mut self, stmts: &[&LiteralStmt], loops: &[LoopExpr]) -> Result<bool> {
if loops.is_empty() {
if !stmts.is_empty() {
@@ -1409,9 +1381,8 @@ impl Interpreter {
}
}
// Save the current scope and restore it after evaluating the statements so
// that the effects of the current loop iteration are cleared.
let scope_saved = self.current_scope()?.clone();
// Create a new scope.
self.scopes.push(Scope::default());
let query_result = self.get_current_context()?.result.clone();
match loop_expr_value {
@@ -1437,12 +1408,13 @@ impl Interpreter {
result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result;
}
self.loop_var_values.remove(&loop_expr.expr());
*self.current_scope_mut()? = scope_saved.clone();
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result = query_result.clone();
}
}
self.loop_var_values.remove(&loop_expr.expr());
}
Value::Set(items) => {
for v in items.iter() {
@@ -1460,12 +1432,12 @@ impl Interpreter {
result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result;
}
self.loop_var_values.remove(&loop_expr.expr());
*self.current_scope_mut()? = scope_saved.clone();
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result = query_result.clone();
}
}
self.loop_var_values.remove(&loop_expr.expr());
}
Value::Object(obj) => {
for (k, v) in obj.iter() {
@@ -1481,12 +1453,13 @@ impl Interpreter {
if exec {
result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result;
}
self.loop_var_values.remove(&loop_expr.expr());
*self.current_scope_mut()? = scope_saved.clone();
Self::clear_scope(self.current_scope_mut()?);
if let Some(ctx) = self.contexts.last_mut() {
ctx.result = query_result.clone();
}
}
self.loop_var_values.remove(&loop_expr.expr());
}
Value::Undefined => {
result = false;
@@ -1497,6 +1470,8 @@ impl Interpreter {
}
}
self.scopes.pop();
// Return true if at least on iteration returned true
Ok(result)
}
@@ -1728,7 +1703,7 @@ impl Interpreter {
if result
.expressions
.iter()
.all(|v| v.value != Value::Undefined)
.all(|v| v.value != Value::Undefined && v.value != Value::Bool(false))
&& !result.expressions.is_empty()
{
ctx.results.result.push(result);
@@ -1847,7 +1822,7 @@ impl Interpreter {
if result
.expressions
.iter()
.all(|v| v.value != Value::Undefined)
.all(|v| v.value != Value::Undefined && v.value != Value::Bool(false))
&& !result.expressions.is_empty()
{
ctx.results.result.push(result);
@@ -2063,10 +2038,12 @@ impl Interpreter {
// Handle trace function.
// TODO: with modifier.
if let (Some(traces), Value::String(msg)) = (&mut self.traces, &v) {
traces.push(msg.clone());
return Ok(Value::Bool(true));
};
if name == "trace" {
if let (Some(traces), Value::String(msg)) = (&mut self.traces, &v) {
traces.push(msg.clone());
return Ok(Value::Bool(true));
}
}
if let Some(name) = cache {
self.builtins_cache.insert((name, args), v.clone());
@@ -2410,6 +2387,14 @@ impl Interpreter {
self.eval_rule(&module, rule)?;
}
}
let prev_module = self.set_current_module(Some(module.clone()))?;
for rule in &module.policy {
if !self.processed.contains(rule) {
self.eval_default_rule(rule)?;
}
}
self.set_current_module(prev_module)?;
}
}
Ok(())
@@ -3222,7 +3207,7 @@ impl Interpreter {
self.set_current_module(prev_module)?;
if let Some(r) = results.result.last() {
if r.bindings.is_empty_object()
if matches!(&r.bindings, Value::Object(obj) if obj.is_empty())
&& r.expressions.iter().any(|e| e.value == Value::Bool(false))
{
results = QueryResults::default();

View File

@@ -4,6 +4,8 @@
// Use README.md as crate documentation.
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
use serde::Serialize;
mod ast;
mod builtins;
mod engine;
@@ -16,10 +18,250 @@ mod utils;
mod value;
pub use engine::Engine;
pub use interpreter::{QueryResult, QueryResults};
pub use value::Value;
/// Location of an [`Expression`] in a Rego query.
///
/// ```
/// # use regorus::Engine;
/// # fn main() -> anyhow::Result<()> {
/// // Create engine and evaluate " \n 1 + 2".
/// let results = Engine::new().eval_query(" \n 1 + 2".to_string(), false)?;
///
/// // Fetch the location for the expression.
/// let loc = &results.result[0].expressions[0].location;
///
/// assert_eq!(loc.row, 2);
/// assert_eq!(loc.col, 3);
/// # Ok(())
/// # }
/// ````
/// See also [`QueryResult`].
#[derive(Debug, Clone, Serialize)]
pub struct Location {
/// Line number. Starts at 1.
pub row: u16,
/// Column number. Starts at 1.
pub col: u16,
}
/// An expression in a Rego query.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// // Create engine and evaluate "1 + 2".
/// let results = Engine::new().eval_query("1 + 2".to_string(), false)?;
///
/// // Fetch the expression from results.
/// let expr = &results.result[0].expressions[0];
///
/// assert_eq!(expr.value, Value::from(3u64));
/// assert_eq!(expr.text.as_ref(), "1 + 2");
/// # Ok(())
/// # }
/// ```
/// See also [`QueryResult`].
#[derive(Debug, Clone, Serialize)]
pub struct Expression {
/// Computed value of the expression.
pub value: Value,
/// The Rego expression.
pub text: std::rc::Rc<str>,
/// Location of the expression in the query string.
pub location: Location,
}
/// Result of evaluating a Rego query.
///
/// A query containing single expression.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// // Create engine and evaluate "1 + 2".
/// let results = Engine::new().eval_query("1 + 2".to_string(), false)?;
///
/// // Fetch the first (sole) result.
/// let result = &results.result[0];
///
/// assert_eq!(result.expressions[0].value, Value::from(3u64));
/// assert_eq!(result.expressions[0].text.as_ref(), "1 + 2");
/// # Ok(())
/// # }
/// ```
///
/// A query containing multiple expressions.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// // Create engine and evaluate "1 + 2; 3.5 * 4".
/// let results = Engine::new().eval_query("1 + 2; 3.55 * 4".to_string(), false)?;
///
/// // Fetch the first (sole) result.
/// let result = &results.result[0];
///
/// // First expression.
/// assert_eq!(result.expressions[0].value, Value::from(3u64));
/// assert_eq!(result.expressions[0].text.as_ref(), "1 + 2");
///
/// // Second expression.
/// assert_eq!(result.expressions[1].value, Value::from(14.2));
/// assert_eq!(result.expressions[1].text.as_ref(), "3.55 * 4");
/// # Ok(())
/// # }
/// ```
///
/// Expressions that create bindings (i.e. associate names to values) evaluate to
/// either true or false. The value of bindings are available in the `bindings` field.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// // Create engine and evaluate "x = 1; y = x > 0".
/// let results = Engine::new().eval_query("x = 1; y = x > 0".to_string(), false)?;
///
/// // Fetch the first (sole) result.
/// let result = &results.result[0];
///
/// // First expression is true.
/// assert_eq!(result.expressions[0].value, Value::from(true));
/// assert_eq!(result.expressions[0].text.as_ref(), "x = 1");
///
/// // Second expression is true.
/// assert_eq!(result.expressions[1].value, Value::from(true));
/// assert_eq!(result.expressions[1].text.as_ref(), "y = x > 0");
///
/// // bindings contains the value for each named expession.
/// assert_eq!(result.bindings[&Value::from("x")], Value::from(1u64));
/// assert_eq!(result.bindings[&Value::from("y")], Value::from(true));
/// # Ok(())
/// # }
/// ```
///
/// If any expression evaluates to false, then no results are produced.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// // Create engine and evaluate "true; true; false".
/// let results = Engine::new().eval_query("true; true; false".to_string(), false)?;
///
/// assert!(results.result.is_empty());
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone, Serialize)]
pub struct QueryResult {
/// Expressions in the query.
///
/// Each statement in the query is treated as a separte expression.
///
pub expressions: Vec<Expression>,
/// Bindings created in the query.
#[serde(skip_serializing_if = "Value::is_empty_object")]
pub bindings: Value,
}
impl Default for QueryResult {
fn default() -> Self {
Self {
bindings: Value::new_object(),
expressions: vec![],
}
}
}
/// Results of evaluating a Rego query.
///
/// Generates the same `json` representation as `opa eval`.
///
/// Queries typically produce a single result.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// // Create engine and evaluate "true; true; false".
/// let results = Engine::new().eval_query("1 + 1".to_string(), false)?;
///
/// assert!(results.result.len() == 1);
/// assert_eq!(results.result[0].expressions[0].value, Value::from(2u64));
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "1 + 1");
/// # Ok(())
/// # }
/// ```
///
/// If any expression evaluates to false, then no results are produced.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// // Create engine and evaluate "true; true; false".
/// let results = Engine::new().eval_query("true; true; false".to_string(), false)?;
///
/// assert!(results.result.is_empty());
/// # Ok(())
/// # }
/// ```
///
/// Queries containing loops produce multiple results.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let results = Engine::new().eval_query("x = [1, 2, 3][_]".to_string(), false)?;
///
/// // Three results are produced, one of each value of x.
/// assert_eq!(results.result.len(), 3);
///
/// // Assert expressions and bindings of results.
/// assert_eq!(results.result[0].expressions[0].value, Value::Bool(true));
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
/// assert_eq!(results.result[0].bindings[&Value::from("x")], Value::from(1u64));
///
/// assert_eq!(results.result[1].expressions[0].value, Value::Bool(true));
/// assert_eq!(results.result[1].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
/// assert_eq!(results.result[1].bindings[&Value::from("x")], Value::from(2u64));
///
/// assert_eq!(results.result[2].expressions[0].value, Value::Bool(true));
/// assert_eq!(results.result[2].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
/// assert_eq!(results.result[2].bindings[&Value::from("x")], Value::from(3u64));
/// # Ok(())
/// # }
/// ```
///
/// Loop iterations that evaluate to false or undefined don't produce results.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let results = Engine::new().eval_query("x = [1, 2, 3][_]; x >= 2".to_string(), false)?;
///
/// // Two results are produced, one for x = 2 and another for x = 3.
/// assert_eq!(results.result.len(), 2);
///
/// // Assert expressions and bindings of results.
/// assert_eq!(results.result[0].expressions[0].value, Value::Bool(true));
/// assert_eq!(results.result[0].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
/// assert_eq!(results.result[0].expressions[0].value, Value::Bool(true));
/// assert_eq!(results.result[0].expressions[1].text.as_ref(), "x >= 2");
/// assert_eq!(results.result[0].bindings[&Value::from("x")], Value::from(2u64));
///
/// assert_eq!(results.result[1].expressions[0].value, Value::Bool(true));
/// assert_eq!(results.result[1].expressions[0].text.as_ref(), "x = [1, 2, 3][_]");
/// assert_eq!(results.result[1].expressions[0].value, Value::Bool(true));
/// assert_eq!(results.result[1].expressions[1].text.as_ref(), "x >= 2");
/// assert_eq!(results.result[1].bindings[&Value::from("x")], Value::from(3u64));
/// # Ok(())
/// # }
/// ```
///
/// See [QueryResult] for examples of different kinds of results.
#[derive(Debug, Clone, Default, Serialize)]
pub struct QueryResults {
/// Collection of results of evaluting a query.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub result: Vec<QueryResult>,
}
/// Items in `unstable` are likely to change.
#[doc(hidden)]
pub mod unstable {
pub use crate::ast::*;
pub use crate::lexer::*;

View File

@@ -132,6 +132,26 @@ impl From<f64> for Number {
}
impl Number {
pub fn as_u128(&self) -> Option<u128> {
match self {
Big(b) if b.is_integer() => match u128::try_from(&b.d) {
Ok(v) => Some(v),
_ => None,
},
_ => None,
}
}
pub fn as_i128(&self) -> Option<i128> {
match self {
Big(b) if b.is_integer() => match i128::try_from(&b.d) {
Ok(v) => Some(v),
_ => None,
},
_ => None,
}
}
pub fn as_u64(&self) -> Option<u64> {
match self {
Big(b) if b.is_integer() => match u64::try_from(&b.d) {

View File

@@ -3,7 +3,6 @@
use crate::ast::Expr::*;
use crate::ast::*;
use crate::builtins;
use crate::lexer::*;
use crate::utils::*;
@@ -629,6 +628,7 @@ impl Analyzer {
let mut used_vars = vec![];
let mut comprs = vec![];
let full_expr = expr;
std::convert::identity(&full_expr);
traverse(expr, &mut |e| match e.as_ref() {
Var(v) if !matches!(v.text(), "_" | "input" | "data") => {
let name = v.source_str();
@@ -645,15 +645,18 @@ impl Analyzer {
first_use.entry(name).or_insert(v.clone());
}
} else if !scope.inputs.contains(&name) {
match get_path_string(full_expr, None) {
Ok(path)
if builtins::BUILTINS.contains_key(path.as_str())
|| builtins::deprecated::DEPRECATED.contains_key(path.as_str()) => {
#[cfg(feature = "deprecated")]
{
if let Ok(path) = get_path_string(full_expr, None) {
if crate::builtins::BUILTINS.contains_key(path.as_str())
|| crate::builtins::deprecated::DEPRECATED
.contains_key(path.as_str())
{
return Ok(false);
}
}
_ => bail!(v.error(
format!("use of undefined variable `{name}` is unsafe").as_str()
)),
}
bail!(v.error(format!("use of undefined variable `{name}` is unsafe").as_str()));
}
Ok(false)
}

File diff suppressed because it is too large Load Diff

View File

@@ -45,7 +45,7 @@ fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> {
let mut values = vec![];
for qr in query_results.result {
values.push(if !qr.bindings.is_empty_object() {
values.push(if !qr.bindings.as_object()?.is_empty() {
qr.bindings.clone()
} else if let Some(v) = qr.expressions.last() {
v.value.clone()
@@ -53,7 +53,7 @@ fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> {
Value::Undefined
});
}
let result = Value::from_array(values);
let result = Value::from(values);
// Make result json compatible. (E.g: avoid sets).
Value::from_json_str(&result.to_string())
}

View File

@@ -84,7 +84,7 @@ fn eval_test_case(case: &TestCase) -> Result<Value> {
let mut values = vec![];
for qr in query_results.result {
values.push(if !qr.bindings.is_empty_object() {
values.push(if !qr.bindings.as_object()?.is_empty() {
if case.sort_bindings == Some(true) {
let mut v = qr.bindings.clone();
let bindings = v.as_object_mut()?;
@@ -105,15 +105,15 @@ fn eval_test_case(case: &TestCase) -> Result<Value> {
});
}
let result = Value::from_array(values);
let result = Value::from(values);
// Make result json compatible. (E.g: avoid sets).
Value::from_json_str(&result.to_string())
}
fn json_schema_tests_check(actual: &Value, expected: &Value) -> bool {
// Fetch `x` binding.
let actual = &actual[0][&Value::String("x".into())];
let expected = &expected[0][&Value::String("x".into())];
let actual = &actual[0]["x"];
let expected = &expected[0]["x"];
match (actual, expected) {
(Value::Array(actual), Value::Array(expected))

View File

@@ -15,7 +15,7 @@ fn non_string_key() -> Result<()> {
obj.as_object_mut()?
.insert(Value::from(std::f64::consts::PI), Value::Null);
obj.as_object_mut()?.insert(
Value::from_array(vec![
Value::from(vec![
Value::Bool(true),
Value::Null,
Value::from(std::f64::consts::PI),
@@ -115,14 +115,14 @@ fn value_as_index() -> Result<()> {
fn string_as_index() -> Result<()> {
let obj = Value::from_json_str(r#"{ "a" : 5, "b" : 6 }"#)?;
assert_eq!(&obj["a"], &Value::from(5.0));
assert_eq!(&obj[&"b".to_owned()], &Value::from(6.0));
assert_eq!(&obj["b".to_owned()], &Value::from(6.0));
Ok(())
}
#[test]
fn usize_as_index() -> Result<()> {
assert_eq!(&Value::from_json_str("[1, 2, 3]")?[0], &Value::from(1.0));
assert_eq!(&Value::from_json_str("[1, 2, 3]")?[5], &Value::Undefined);
assert_eq!(&Value::from_json_str("[1, 2, 3]")?[0u64], &Value::from(1.0));
assert_eq!(&Value::from_json_str("[1, 2, 3]")?[5u64], &Value::Undefined);
Ok(())
}
@@ -135,9 +135,6 @@ fn api() -> Result<()> {
assert_eq!(v["a"], Value::from(3.145));
assert_eq!(v.as_object()?.len(), 1);
// Null
assert!(Value::Null.is_null());
let v = Value::new_set();
assert_eq!(v.as_set()?.len(), 0);