Code from github.com/anakrish/rego-rs

Authored by anakrish and mingweishih

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-02-09 10:56:54 -08:00
parent 8f67aeecb0
commit cb0b3a1790
85 changed files with 11144 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
// Copyright (c) Rego-Rs Authors.
// Licensed under the Apache 2.0 license.
#![cfg(test)]
use crate::interpreter::*;
use anyhow::Result;
#[test]
fn basic() -> Result<()> {
let rego = r#"
package test
add {
1 + 2 == 3
}
sub {
5 - 1 == 4
}
mul {
3 * 4 == 12
}
# Lock down float operation.
div {
21 / 5 == 4.2
}
"#;
let expected = Value::from_json_str(
r#" {
"add" : true,
"sub" : true,
"mul" : true,
"div" : true
}"#,
)?;
assert_eq!(
eval_file(&[rego.to_owned()], None, None, "data.test")?,
expected
);
Ok(())
}
+32
View File
@@ -0,0 +1,32 @@
# Copyright (c) Rego-Rs Authors.
# Licensed under the Apache 2.0 license.
cases:
- data: {}
modules:
- |
package test
add {
1 + 2 == 3
}
sub {
5 - 1 == 4
}
mul {
3 * 4 == 12
}
# Lock down float operation.
div {
21 / 5 == 4.2
}
note: arithmetic/basic
query: data.test
sort_bindings: true
want_result:
add: true
sub: true
mul: true
div: true
@@ -0,0 +1,234 @@
# Copyright (c) Rego-Rs Authors.
# Licensed under the Apache 2.0 license.
cases:
- note: number-equals
data: {}
modules:
- |
package test
v = 1
r = [
# Comparison with number
v == 1,
v == 2,
# Comparison with primitives
v == null,
v == "hello",
v == true,
v == false,
# Comparison with arrays
v == [],
v == [v],
# Comparison with sets
v == set(),
v == { v },
# Comparison with objects
v == {},
v == { "a": v},
]
sort_bindings: false
query: data.test
want_result:
v: 1
r: [ true, false, false, false, false, false,
false, false, false, false, false, false]
- note: null-equals
data: {}
modules:
- |
package test
v = null
r = [
# Comparison with number
v == 1,
v == 2,
# Comparison with primitives
v == null,
v == "hello",
v == true,
v == false,
# Comparison with arrays
v == [],
v == [v],
# Comparison with sets
v == set(),
v == { v },
# Comparison with objects
v == {},
v == { "a": v},
]
sort_bindings: false
query: data.test
want_result:
v: null
r: [ false, false, true, false, false, false,
false, false, false, false, false, false]
- note: string-equals
data: {}
modules:
- |
package test
v = "hello"
r = [
# Comparison with number
v == 1,
v == 2,
# Comparison with primitives
v == null,
v == "hello",
v == true,
v == false,
# Comparison with arrays
v == [],
v == [v],
# Comparison with sets
v == set(),
v == { v },
# Comparison with objects
v == {},
v == { "a": v},
]
sort_bindings: false
query: data.test
want_result:
v: "hello"
r: [ false, false, false, true, false, false,
false, false, false, false, false, false]
- note: true-equals
data: {}
modules:
- |
package test
v = true
r = [
# Comparison with number
v == 1,
v == 2,
# Comparison with primitives
v == null,
v == "hello",
v == true,
v == false,
# Comparison with arrays
v == [],
v == [v],
# Comparison with sets
v == set(),
v == { v },
# Comparison with objects
v == {},
v == { "a": v},
]
sort_bindings: false
query: data.test
want_result:
v: true
r: [ false, false, false, false, true, false,
false, false, false, false, false, false]
- note: false-equals
data: {}
modules:
- |
package test
v = false
r = [
# Comparison with number
v == 1, # true
v == 1, # false
# Comparison with primitives
v == null,
v == "hello",
v == true,
v == false,
# Comparison with arrays
v == [],
v == [v],
# Comparison with sets
v == set(),
v == { v },
# Comparison with objects
v == {},
v == { "a": v},
]
sort_bindings: false
query: data.test
want_result:
v: false
r: [ false, false, false, false, false, true,
false, false, false, false, false, false]
- note: undefined-equals
data: {}
modules:
- |
package test
import future.keywords
v = 1 if false
r = [
# Comparison with number
v == 1,
v == 2,
# Comparison with primitives
v == null,
v == "hello",
v == true,
v == false,
# Comparison with arrays
v == [],
v == [v],
# Comparison with sets
v == set(),
v == { v },
# Comparison with objects
v == {},
v == { "a": v},
]
r2 = 1 if v
# This variable should appear in output since it is not undefined.
ok = true
sort_bindings: false
query: data.test
want_result:
ok: true
+44
View File
@@ -0,0 +1,44 @@
cases:
- note: basic
data: {}
modules:
- |
package test
inc(x) = x + 1
a1 = inc(5)
query: data.test
want_result:
a1: 6
- note: call-in-arg
data: {}
modules:
- |
package test
inc(x) = x + 1
a1 = inc(inc(5))
query: data.test
want_result:
a1: 7
- note: call-nested
data: {}
modules:
- |
package test
sub(a, b) := a - b
foo(a, b) := r {
r =(a + b) * sub(a, b)
}
a = foo(5, 6)
query: data.test
want_result:
a: -11
@@ -0,0 +1,132 @@
# Copyright (c) Rego-Rs Authors.
# Licensed under the Apache 2.0 license.
# Captures tricky cases where similar looking expressions will be
# treated as arrays or array-comprs.
cases:
- note: case1-compr
data: {}
modules:
- |
package test
x = {1}
y = {2}
# If there is one term and the term has an | operator, it will be parsed
# as a compr.
z = [ x | y ] #, 5] # in {5}, 6]
query: data.test
sort_bindings: true
want_result:
x:
set!: [1]
y:
set!: [2]
z:
- set!: [1]
- note: case2-array
data: {}
modules:
- |
package test
x = {1}
y = {2}
# If there is a comma following the expression to the right of |
# ie "y ," then enclosing expression will be parsed as array.
z = [ x | y, 5] # in {5}, 6]
query: data.test
sort_bindings: true
want_result:
x:
set!: [1]
y:
set!: [2]
z:
- set!: [2, 1]
- 5
- note: case3-compr
data: {}
modules:
- |
package test
import future.keywords.in
x = {1}
y = {2}
# In the following case, the "y, 5 in {5}" which is to the right of |
# will be parsed as a membership expression. Since this expression is not
# followed by a ",", the enclosing expression will be parsed as a compr.
z = [ x | y, 5 in {5}] #, 6]
query: data.test
sort_bindings: true
want_result:
x:
set!: [1]
y:
set!: [2]
z: []
- note: case4-array
data: {}
modules:
- |
package test
import future.keywords.in
x = {1}
y = {2}
# Unlike the previous case, the "y, 5 in {5}" is followed by a ",".
# Therefore, the enclosing expression will be parsed as an array.
z = [ x | y, 5 in {5}, 6]
query: data.test
sort_bindings: true
want_result:
x:
set!: [1]
y:
set!: [2]
z:
- set!: [2, 1]
- true
- 6
- note: case5-array
data: {}
modules:
- |
package test
import future.keywords.in
x = {1}
y = {2}
# If the first expression "x - {10}"is something higher than an in-expr
# (e.g arithexpr) then the enclosing expression will be parsed as an array.
z = [ x - {10} | y, 5 in {5}] #, 6]
query: data.test
sort_bindings: true
want_result:
x:
set!: [1]
y:
set!: [2]
z:
- set!: [2, 1]
- true
- note: case6-compr
data: {}
modules:
- |
package test
import future.keywords.in
x = {1}
y = {2}
# The way to get the previous case parsed as a compr is to enclose the first
# expression in parentheses.
z = [ (x - {10}) | y, 5 in {5}] #, 6]
query: data.test
sort_bindings: true
want_result:
x:
set!: [1]
y:
set!: [2]
z: []
+123
View File
@@ -0,0 +1,123 @@
// Copyright (c) Rego-Rs Authors.
// Licensed under the Apache 2.0 license.
#![cfg(test)]
use crate::interpreter::*;
#[test]
fn basic_array() -> Result<()> {
let rego = r#"
package test
array = [1, 2, 3]
array_compr_0 = [ x | x = 1 ]
array_compr_1 = [ array | true ]
array_compr_2 = [ x | x = array[_] ]
array_compr_3 = [ x | x = array[_]; x != 2 ]
# This produces 3 values
array_compr_4 = [ 1 | [1, 2, 3][_] ]
# This produces 6 values.
array_compr_5 = [ 1 | [1, 2, 3][_]; {1, 2}[_] ]
# This also produces 6 values.
array_compr_6 = [ 1 | [1, 2, 3][_]; {"a":1, "b":2}[_] ]
# This produces 3 values.
array_compr_7 = [ 1 | [1, 2, 3][_]; [1, 2][_] >= 2 ]
"#;
let expected = Value::from_json_str(
r#" {
"array": [1, 2, 3],
"array_compr_0": [1],
"array_compr_1": [[1, 2, 3]],
"array_compr_2": [1, 2, 3],
"array_compr_3": [1, 3],
"array_compr_4": [1, 1, 1],
"array_compr_5": [1, 1, 1, 1, 1, 1],
"array_compr_6": [1, 1, 1, 1, 1, 1],
"array_compr_7": [1, 1, 1]
}"#,
)?;
assert_match(
eval_file(&[rego.to_owned()], None, None, "data.test")?,
expected,
);
Ok(())
}
#[test]
fn basic_set() -> Result<()> {
let rego = r#"
package test
set = { 1, "string", 1, [2, 3, 4], 567, false, 1 }
set_compr_0 = { x | x = 1 }
set_compr_1 = { set | true }
set_compr_2 = { x | x = set[_] }
set_compr_3 = { x | x = set[_]; x != [2, 3, 4] }
# This produces 1 value
set_compr_4 = { 1 | [1, 2, 3][_] }
# This produces 4 values.
set_compr_5 = { (a+b) | a=[1, 2, 3][_]; b={1, 2}[_] }
# This also produces 2 values.
set_compr_6 = { a | [1, 2, 3][_]; a={"a":1, "b":2}[_] }
# This produces 3 values.
set_compr_7 = { a | a = [1, 2, 3][_]; [1, 2][_] >= 2 }
"#;
let expected = Value::from_json_str(
r#" {
"set": {
"set!": [1, "string", [2, 3, 4], 567, false]
},
"set_compr_0": {
"set!": [1]
},
"set_compr_1": {
"set!" : [{
"set!": [1, "string", [2, 3, 4], 567, false]
}]
},
"set_compr_2": {
"set!": [1, "string", [2, 3, 4], 567, false]
},
"set_compr_3": {
"set!": [1, "string", 567, false]
},
"set_compr_4": {
"set!": [1]
},
"set_compr_5": {
"set!": [2, 3, 4, 5]
},
"set_compr_6": {
"set!": [1, 2]
},
"set_compr_7": {
"set!": [1, 2, 3]
}
}"#,
)?;
assert_match(
eval_file(&[rego.to_owned()], None, None, "data.test")?,
expected,
);
Ok(())
}
+67
View File
@@ -0,0 +1,67 @@
# Copyright (c) Rego-Rs Authors.
# Licensed under the Apache 2.0 license.
cases:
- note: simple
data: {}
modules:
- |
package test
x = { k:1 | k = ["Hello", "world", 1][_] }
query: data.test
want_result:
x:
object!:
- key: "Hello"
value: 1
- key: "world"
value: 1
- key: 1
value: 1
- note: key-loop
data: {}
modules:
- |
package test
x = { ["Hello", "world", 1][_]:1 | true }
query: data.test
want_result:
x:
object!:
- key: "Hello"
value: 1
- key: "world"
value: 1
- key: 1
value: 1
- note: multiple-occurance-of-same-key-value-pair
data: {}
modules:
- |
package test
x = { k:v | k = ["Hello", "world", 1][_]; v = [1, 1][_] }
query: data.test
want_result:
x:
object!:
- key: "Hello"
value: 1
- key: "world"
value: 1
- key: 1
value: 1
- note: different-values-for-same-key
data: {}
modules:
- |
package test
x = { k:v | k = ["Hello", "world", 1][_]; v = [1, 2][_] }
query: data.test
error: "value for key `\"Hello\"` generated multiple times: `1` and `2`"
want_result:
@@ -0,0 +1,56 @@
# Copyright (c) Rego-Rs Authors.
# Licensed under the Apache 2.0 license.
cases:
- note: basic
data: {}
modules:
- |
package test
default x = 5
a = b
default b = 6
c = d
d {
x == 3
}
d {
x == 4
}
default d = "has_default"
default object["key"] = "string"
default complex[true] = "bool_true"
default complex[false] = "bool_false"
complex["hello"] = "world"
query: data.test
want_result:
x: 5
a: 6
b: 6
c: "has_default"
d: "has_default"
object:
object!:
- key: "key"
value: "string"
complex:
object!:
- key: true
value: "bool_true"
- key: false
value: "bool_false"
- key: "hello"
value: "world"
+126
View File
@@ -0,0 +1,126 @@
// Copyright (c) Rego-Rs Authors.
// Licensed under the Apache 2.0 license.
#![cfg(test)]
use crate::interpreter::*;
#[test]
fn basic() -> Result<()> {
let rego = r#"
package test
import future.keywords.in
array = [1, 2, 3]
in_array_key_value {
0, 1 in array
}
in_array_key_value_negative {
0, 2 in array
}
some_decl_array_key_value {
some 0, 1 in array
}
some_decl_array_key_value_negative {
some 0, 2 in array
}
some_decl_array_value {
some 1 in array
}
some_decl_array_value_negative {
some 4 in array
}
in_array_value {
1 in array
}
in_array_value_negative {
4 in array
}
object = { "number": 1, "array": [2, 3], "string": "test", "bool": true }
in_object_key_value {
"number", 1 in object
}
in_object_key_value_negative {
"non-exist", 1 in object
}
some_decl_object_key_value {
some "number", 1 in object
}
some_decl_object_key_value_negative {
some "non-exist", 1 in object
}
in_object_value {
[2, 3] in object
}
in_object_value_negative {
false in object
}
some_decl_object_value {
some [2, 3] in object
}
some_decl_object_value_negative {
some false in object
}
set = { "string", [2, 3, 4], 567, false }
in_set_value {
"string" in set
}
some_decl_set_value {
some "string" in set
}
in_set_value_negative {
"non-exist" in set
}
some_decl_set_value_negative {
some "non-exist" in set
}
"#;
let expected = Value::from_json_str(
r#" {
"array": [1, 2, 3],
"in_array_key_value": true,
"some_decl_array_key_value": true,
"in_array_value": true,
"some_decl_array_value": true,
"object": { "number": 1, "array": [2, 3], "string": "test", "bool": true},
"in_object_key_value": true,
"some_decl_object_key_value": true,
"in_object_value": true,
"some_decl_object_value": true,
"set": {
"set!": ["string", [2, 3, 4], 567, false]
},
"in_set_value": true,
"some_decl_set_value": true
}"#,
)?;
assert_match(
eval_file(&[rego.to_owned()], None, None, "data.test")?,
expected,
);
Ok(())
}
+7
View File
@@ -0,0 +1,7 @@
// Copyright (c) Rego-Rs Authors.
// Licensed under the Apache 2.0 license.
mod arithmetic;
mod compr;
mod r#in;
mod variables;
+32
View File
@@ -0,0 +1,32 @@
cases:
- note: simple-no-cross-ref
data: {}
modules:
- |
package a
b = 1
- |
package b
c = 1
query: data
want_result:
a:
b: 1
b:
c: 1
- note: simple-cross-ref-in-order
data: {}
modules:
- |
package a
b = 1
- |
package b
c = data.a.b
query: data
want_result:
a:
b: 1
b:
c: 1
@@ -0,0 +1,32 @@
# Copyright (c) Rego-Rs Authors.
# Licensed under the Apache 2.0 license.
cases:
- note: simple
data: {}
modules:
- |
package test
import future.keywords
tbl contains name if {
name = ["a", "b", "c"][_]
}
query: data.test
want_result:
tbl:
set!: ["c", "b", "a"]
- note: arithmetic
data: {}
modules:
- |
package test
import future.keywords
tbl contains x + 10 if {
x = [1, 2, 3][_]
}
query: data.test
want_result:
tbl:
set!: [11, 12, 13]
@@ -0,0 +1,41 @@
cases:
- note: forward-ref
data: {}
modules:
- |
package test
a = b
b = 5
query: data.test
want_result:
a: 5
b: 5
- note: recursive
data: {}
modules:
- |
package test
a = b
b = c
c = a
query: data.test
error: recursion detected
- note: cross-module
data: {}
modules:
- |
package a
x = data.b.y * 2
- |
package b
y = 10
z = data.a.x + 5
query: data
want_result:
a:
x: 20
b:
y: 10
z: 25
+35
View File
@@ -0,0 +1,35 @@
# Copyright (c) Rego-Rs Authors.
# Licensed under the Apache 2.0 license.
cases:
- note: simple
data: {}
modules:
- |
package test
x[a] = b {
a = "hello"
b = "world"
}
query: data.test
want_result:
x:
"hello": "world"
- note: loop
data: {}
modules:
- |
package test
x[a] = b {
a = ["hello", "world"][_]
b = 1
}
query: data.test
want_result:
x:
"hello": 1
"world": 1
+31
View File
@@ -0,0 +1,31 @@
# Copyright (c) Rego-Rs Authors.
# Licensed under the Apache 2.0 license.
cases:
- note: index-syntax
data: {}
modules:
- |
package test
x[a] {
a = ["hello", "world"][_]
}
query: data.test
want_result:
x:
set!: ["hello", "world"]
- note: special-case-field-syntax
data: {}
modules:
- |
package test
x.a {
a = ["hello", "world"][_]
true
}
query: data.test
want_result:
x:
set!: ["a"]
@@ -0,0 +1,249 @@
cases:
- note: snippet1
data: {}
modules:
- |
package test
a := [ 1 | {3} ]
q:= {4}
e1 := q & {1} | {true}
e2 := (q & {1}) | {true}
e3 := [ q & {1} | {true} ]
e4 := [ (q & {1}) | {true} ]
query: data.test
want_result:
a: [1]
e1:
set!: [true]
e2:
set!: [true]
e3:
- set!: [true]
e4:
- set!: []
q:
set!: [4]
- note: snippet2.1
data: {}
modules:
- |
package test
import future.keywords
b = 15
get_b(a) := v { v := b }
x = a {
a = b with data.test.b as 10
}
query: data.test
want_result:
b: 15
x: 10
- note: snippet2
data: {}
skip: true
modules:
- |
package test
a := {4}
mydoc(x) := path {
path := "data.test.a"
}
x := [ y |
y := data.test.a | data.test.b with data.test.a as {5} with data.test.b as {6}
]
r := [ m | m := data.test.p with data.test.p as 5 + 6; true ]
allow {
input.x
== 5
input.y == 5
input.y
== 5
}
query: data.test
want_result:
a:
set!: [4]
r: [11]
x:
- set!: [5, 6]
- note: snippet3
data: {}
modules:
- |
package test
a(b) { true }
b(a) { false }
p := 5
allow {
# TODO: ude data.test
q := 5 #data.test["p"]
y := data.test.a(
5)
q == 5
y
}
r := allow
x = a(5)
y = b(5)
query: data.test
want_result:
allow: true
p: 5
r: true
x: true
- note: snippet4
data: {}
modules:
- |
package test
sum (a,b) := c {
c := a + b
}
obj(v) := o {
o := { "a" : 5 + v }
}
p := sum(5, 6) # There must not be space between sum and (
#q := { "a" : "sum"}[a](5, 6)
q := obj(1).a
query: data.test
want_result:
p: 11
q: 6
- note: snippet5
data: {}
modules:
- |
package test
import future.keywords.if
import future.keywords.in
b := {2, 3}
double(x) := y { y := [x, x] }
# Membership needs to be enclosed in parens
x := [ (y in {2,3}) |
y := 5 in { "z" : 5}
]
query: data.test
want_result:
b:
set!: [2, 3]
x: [false]
- note: snippet6
data: {}
modules:
- |
package test
import future.keywords.in
import future.keywords.every
import future.keywords.if
import future.keywords.contains
# expr top; membership
x := false in {a, b} {
a := 1
b := 0, 2 in [5, 6]
0, 2 in [5, 6]
}
a.y(a) := b { b := a }
p["q"](a1) := b { b:= a1 }
c := a.y(5)
d := p.q(5)
r["s"] p1 {
s1 := "1"
p1 := s1
}
query: data.test
want_result:
a: {}
c: 5
d: 5
p: {}
p1: true
r:
set!: ["s"]
- note: snippet8
data: {}
modules:
- |
package test
import future.keywords.if
import future.keywords.in
import future.keywords.contains
# Try removing parenthesis below
ref["p"] contains [(x | {5})] # [a]
# Uncomment [a]. Why doesn't rego complain?
# Then add a := 5 to the body.
{
x:= {6}
}
arg := 5
ref1[arg] {
arg := {5, 6}
}
ref2.arg := {5, 6}
a[b] {
b := {5,6}[_]
}
#x["b"] := b {
# b := {5,6}[_]
#}
query: data.test
want_result:
a:
set!: [5, 6]
arg: 5
ref:
p:
set!:
-
- set!: [5, 6]
ref1:
set!:
- set!: [5, 6]
ref2:
arg:
set!: [5, 6]
@@ -0,0 +1,75 @@
# Copyright (c) Rego-Rs Authors.
# Licensed under the Apache 2.0 license.
cases:
- note: basic
data: {}
modules:
- |
package test
array = [1, 2, 3]
nested_array = [1, [2, 3, 4], 5, 6]
object = { "key0": "value0" }
key = "key"
object_var = { key: array }
local_0 = x {
x = 10
}
local_1 = x {
x = "test_local"
}
# Set with nested object, array and set.
set = { 1, 2,
{"a": 3, "b" : 4}, [5, 6], {7, 8}}
# Object with non-string as keys
complex_object = {
{1, 2, 3} : [4, 5, 6],
true: false,
[1, 3] : {"hello", "world"}
}
query: data.test
want_result:
array: [1, 2, 3]
nested_array: [1, [2, 3, 4], 5, 6]
object:
key0: value0
key: key
object_var:
key: [1, 2, 3]
local_0: 10
local_1: test_local
set:
# Specify set using special encoding.
# Order of elements shouldn't matter for set.
set!:
- 2
- 1
- a : 3
b : 4
- [5, 6]
- set!: [8, 7]
complex_object:
# Specify object using special encoding.
object!:
- key:
set!: [3, 2, 1]
value: [4, 5, 6]
- key: true
value: false
- key: [1, 3]
value:
set!:
- "hello"
- "world"
+54
View File
@@ -0,0 +1,54 @@
// Copyright (c) Rego-Rs Authors.
// Licensed under the Apache 2.0 license.
#![cfg(test)]
use crate::interpreter::*;
#[test]
fn basic() -> Result<()> {
let rego = r#"
package test
array = [1, 2, 3]
nested_array = [1, [2, 3, 4], 5, 6]
object = { "key0": "value0" }
key = "key"
object_var = { key: array }
local_0 = x {
x = 10
}
local_1 = x {
some x
x = "test_local"
}
set = {1, 2, 3}
"#;
let expected = Value::from_json_str(
r#" {
"array": [1, 2, 3],
"nested_array": [1, [2, 3, 4], 5, 6],
"object": { "key0": "value0" },
"key": "key",
"object_var": { "key": [1, 2, 3] },
"set" : {
"set!" : [3, 2, 1]
},
"local_0": 10,
"local_1": "test_local"
}"#,
)?;
assert_match(
eval_file(&[rego.to_owned()], None, None, "data.test")?,
expected,
);
Ok(())
}
+386
View File
@@ -0,0 +1,386 @@
// Copyright (c) Rego-Rs Authors.
// Licensed under the Apache 2.0 license.
#![cfg(test)]
use std::env;
use anyhow::{bail, Result};
use rego_rs::*;
use serde::{Deserialize, Serialize};
use test_generator::test_resources;
//use walkdir::WalkDir;
mod cases;
// Process test value specified in json/yaml to interpret special encodings.
pub fn process_value(v: &Value) -> Result<Value> {
match v {
// Handle Undefined encoded as a string "#undefined"
Value::String(s) if s == "#undefined" => Ok(Value::Undefined),
// Handle set encoded as an object
// set! :
// - item1
// - item2
// ...
Value::Object(ref fields) if fields.len() == 1 && matches!(&v["set!"], Value::Array(_)) => {
let mut set_value = Value::new_set();
let set = set_value.as_set_mut()?;
for item in v["set!"].as_array()? {
set.insert(process_value(item)?);
}
Ok(set_value)
}
// Handle complex object specified explicitly:
// object! :
// - key: ...
// value: ...
Value::Object(fields) if fields.len() == 1 && matches!(&v["object!"], Value::Array(_)) => {
let mut object_value = Value::new_object();
let object = object_value.as_object_mut()?;
for item in v["object!"].as_array()? {
object.insert(process_value(&item["key"])?, process_value(&item["value"])?);
}
Ok(object_value)
}
// Recursively process arrays
Value::Array(items) => {
let mut array_value = Value::new_array();
let array = array_value.as_array_mut()?;
for item in items.iter() {
array.push(process_value(item)?);
}
Ok(array_value)
}
// Recursively process objects
Value::Object(fields) => {
let mut object_value = Value::new_object();
let object = object_value.as_object_mut()?;
for (key, value) in fields.iter() {
object.insert(process_value(key)?, process_value(value)?);
}
Ok(object_value)
}
Value::Set(_) => bail!("unexpected set in value read from json/yaml"),
// Simple variants
_ => Ok(v.clone()),
}
}
fn display_values(c: &Value, e: &Value) -> Result<String> {
Ok(format!(
"\nleft = {}\nright = {}\n",
serde_json::to_string_pretty(c)?,
serde_json::to_string_pretty(e)?
))
}
// Helper function to match computed and expecte values.
// On mismatch, prints the failing sub-value instead of the whole value.
fn match_values_impl(computed: &Value, expected: &Value) -> Result<()> {
match (&computed, &expected) {
(Value::Array(a1), Value::Array(a2)) => {
if a1.len() != a2.len() {
bail!(
"array length mismatch: {} != {}{}",
a1.len(),
a2.len(),
display_values(computed, expected)?
);
}
for (idx, v1) in a1.iter().enumerate() {
match_values_impl(v1, &a2[idx])?;
}
Ok(())
}
(Value::Set(s1), Value::Set(s2)) => {
if s1.len() != s2.len() {
bail!(
"set length mismatch: {} != {}{}",
s1.len(),
s2.len(),
display_values(computed, expected)?
);
}
let mut itr2 = s2.iter();
for v1 in s1.iter() {
match_values_impl(v1, itr2.next().unwrap())?;
}
Ok(())
}
(Value::Object(o1), Value::Object(o2)) => {
if o1.len() != o2.len() {
bail!(
"object length mismatch: {} != {}{}",
o1.len(),
o2.len(),
display_values(computed, expected)?
);
}
let mut itr2 = o2.iter();
for (k1, v1) in o1.iter() {
let (k2, v2) = itr2.next().unwrap();
match_values_impl(k1, k2)?;
match_values_impl(v1, v2)?;
}
Ok(())
}
(Value::Number(n1), Value::Number(n2)) if n1 == n2 => Ok(()),
(Value::String(s1), Value::String(s2)) if s1 == s2 => Ok(()),
(Value::Bool(b1), Value::Bool(b2)) if b1 == b2 => Ok(()),
(Value::Null, Value::Null) => Ok(()),
(Value::Undefined, Value::Undefined) => Ok(()),
_ => bail!("value mismatch: {}", display_values(computed, expected)?),
}
}
fn match_values(computed: &Value, expected: &Value) -> Result<()> {
match match_values_impl(computed, expected) {
Ok(()) => Ok(()),
Err(e) => bail!("\nmismatch in {}{}", display_values(computed, expected)?, e),
}
}
pub fn assert_match(computed: Value, expected: Value) {
let expected = match process_value(&expected) {
Ok(e) => e,
_ => panic!("unable to process value :\n {expected:?}"),
};
match match_values(&computed, &expected) {
Ok(()) => (),
Err(e) => panic!("{}", e),
}
}
pub fn eval_file(
regos: &[String],
data: Option<Value>,
input: Option<Value>,
query: &str,
) -> Result<Value> {
let mut files = vec![];
let mut sources = vec![];
let mut modules = vec![];
let mut modules_ref = vec![];
for (idx, _) in regos.iter().enumerate() {
files.push(format!("rego_{idx}"));
}
for (idx, file) in files.iter().enumerate() {
let contents = regos[idx].as_str();
sources.push(Source {
file,
contents,
lines: contents.split('\n').collect(),
});
}
for source in &sources {
let mut parser = Parser::new(source)?;
modules.push(parser.parse()?);
}
for m in &modules {
modules_ref.push(m);
}
// First eval the modules.
let mut interpreter = interpreter::Interpreter::new(modules_ref)?;
interpreter.eval(&data, &input)?;
// Now eval the query.
let source = Source {
file: "<query.rego>",
contents: query,
lines: query.split('\n').collect(),
};
let mut parser = Parser::new(&source)?;
let expr = parser.parse_membership_expr()?;
interpreter.eval_query_snippet(&expr)
}
#[test]
#[ignore = "intended for use by scripts/rego-eval"]
fn one_file() -> Result<()> {
env_logger::init();
let mut file = String::default();
let mut input = None;
for a in env::args() {
if a.ends_with(".rego") {
file = a;
} else if a.ends_with(".json") {
let input_json = std::fs::read_to_string(&a)?;
let value = Value::from_json_str(input_json.as_str())?;
input = Some(value);
}
}
if file.is_empty() {
bail!("missing <policy.rego>");
}
let contents = std::fs::read_to_string(&file)?;
let source = Source {
file: file.as_str(),
contents: contents.as_str(),
lines: contents.split('\n').collect(),
};
let mut parser = Parser::new(&source)?;
let tree = parser.parse()?;
let mut interpreter = interpreter::Interpreter::new(vec![&tree])?;
let results = interpreter.eval(&None, &input)?;
println!("eval results:\n{}", serde_json::to_string_pretty(&results)?);
Ok(())
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestCase {
data: Value,
input: Option<Value>,
modules: Vec<String>,
note: String,
query: String,
sort_bindings: Option<bool>,
want_result: Option<Value>,
skip: Option<bool>,
error: Option<String>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct YamlTest {
cases: Vec<TestCase>,
}
fn yaml_test_impl(file: &str) -> Result<()> {
let yaml_str = std::fs::read_to_string(file)?;
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
println!("running {}", file);
for case in test.cases {
print!("case {} ", case.note);
if case.skip == Some(true) {
println!("skipped");
continue;
}
match (&case.want_result, &case.error) {
(Some(_), None) | (None, Some(_)) => (),
_ => panic!("either want_result or error must be specified in test case."),
}
// First eval the modules.
match eval_file(
&case.modules,
Some(case.data),
case.input,
case.query.as_str(),
) {
Ok(results) => match case.want_result {
Some(want_result) => assert_match(results, want_result),
_ => panic!("eval succeeded and did not produce any errors"),
},
Err(actual) => match &case.error {
Some(expected) => {
let actual = actual.to_string();
if !actual.contains(expected) {
bail!(
"Error message\n`{}\n`\ndoes not contain `{}`",
actual,
expected
);
}
println!("{actual}");
}
_ => return Err(actual),
},
}
println!("passed");
}
Ok(())
}
fn yaml_test(file: &str) -> Result<()> {
match yaml_test_impl(file) {
Ok(_) => Ok(()),
Err(e) => {
// If Err is returned, it doesn't always get printed by cargo test.
// Therefore, panic with the error.
panic!("{}", e);
}
}
}
#[test]
fn yaml_test_basic() -> Result<()> {
yaml_test("tests/interpreter/cases/basic_001.yaml")
}
#[test]
#[ignore = "intended for use by scripts/yaml-test-eval"]
fn one_yaml() -> Result<()> {
let mut file = String::default();
for a in env::args() {
if a.ends_with(".yaml") {
file = a;
break;
}
}
if file.is_empty() {
bail!("missing <policy.rego>");
}
yaml_test(file.as_str())
}
/*
fn run_yaml_tests_in(folder: &str) -> Result<()> {
let mut total = 0;
for entry in WalkDir::new(folder)
.follow_links(true)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry
.path()
.to_str()
.ok_or_else(|| anyhow!("failed to convert path to utf8 {:?}", entry.path()))?;
if !path.ends_with(".yaml") {
continue;
}
total += 1;
yaml_test(path)?;
}
println!("{} yaml tests passed.", total);
Ok(())
}
#[test]
fn run_yaml_tests() -> Result<()> {
run_yaml_tests_in("tests/interpreter")
}
*/
#[test_resources("tests/interpreter/**/*.yaml")]
fn run(path: &str) {
yaml_test(path).unwrap()
}