mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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:
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "rego-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.66"
|
||||
ordered-float = "3.4.0"
|
||||
serde = {version = "1.0.150", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.89"
|
||||
log = "0.4.17"
|
||||
env_logger="0.10.0"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_yaml = "0.9.16"
|
||||
test-generator = "0.3.1"
|
||||
walkdir = "2.3.2"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.66"
|
||||
10
build.rs
Normal file
10
build.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Rego-Rs Authors.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
use anyhow::Result;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Copy hooks to appropriate location so that git will run them.
|
||||
std::fs::copy("./scripts/pre-commit", "./.git/hooks/pre-commit")?;
|
||||
std::fs::copy("./scripts/pre-push", "./.git/hooks/pre-push")?;
|
||||
Ok(())
|
||||
}
|
||||
174
docs/grammar.md
Normal file
174
docs/grammar.md
Normal file
@@ -0,0 +1,174 @@
|
||||
|
||||
```
|
||||
module: package imports { rule }
|
||||
|
||||
package: "package" path-ref
|
||||
|
||||
imports: { "import" path-ref [ "as" var ] }
|
||||
|
||||
path-ref: path-ref NO_WS "." NO_WS IDENT
|
||||
| path-ref NO_WS "[" STRING "]"
|
||||
| IDENT
|
||||
|
||||
rule: default-rule
|
||||
| spec-rule
|
||||
|
||||
default-rule: "default" rule-ref assign-op term
|
||||
|
||||
spec-rule: rule-head rule-bodies
|
||||
|
||||
rule-head: func-rule
|
||||
| contains-rule
|
||||
| object-rule
|
||||
| set-rule
|
||||
| compr-rule
|
||||
|
||||
func-rule: rule-ref "(" term { "," term } [","] ")" [ rule-assign ]
|
||||
|
||||
contains-rule: rule-ref "contains" or-expr
|
||||
|
||||
object-rule: rule-ref NO_WS "[" membership-expr "]" rule-assign
|
||||
|
||||
set-rule: rule-ref NO_WS "[" membership-expr "]"
|
||||
|
||||
compr-rule: rule-ref [ rule-assign ]
|
||||
|
||||
rule-ref: rule-ref NO_WS "." NO_WS var
|
||||
| path-ref NO_WS "[" membership-expr "]"
|
||||
| var
|
||||
|
||||
rule-assign: assign-op membership-expr
|
||||
|
||||
rule-bodies: "if" "{" query "}" alternatives
|
||||
| "if" literal-stmt alternatives
|
||||
| "{" query "}" alternatives
|
||||
|
||||
alternatives: query-blocks
|
||||
| else-blocks
|
||||
|
||||
query-blocks: { "{" query "}" }
|
||||
|
||||
else-blocks: { else-block }
|
||||
|
||||
else-block: "else" [rule-assign] "if" "{" query "}"
|
||||
| "else" [rule-assign] "if" literal-stmt
|
||||
| "else" [rule-assign] "{" query "}"
|
||||
|
||||
assign-op: "=" | ":="
|
||||
|
||||
query: literal-stmt { sep literal-stmt }
|
||||
|
||||
sep: ";" | "\n" | "\r\n"
|
||||
|
||||
literal-stmt: literal with-modifiers
|
||||
|
||||
with-modifiers: { "with" path-ref "as" in-expr }
|
||||
|
||||
literal: some
|
||||
| every
|
||||
| expr
|
||||
| not-expr
|
||||
|
||||
some: some-vars
|
||||
| some-in
|
||||
|
||||
some-vars: "some" var { "," var }
|
||||
|
||||
some-in: "some" ref [ "," ref ] "in"
|
||||
|
||||
every: "every" var [ "," var ] "in" bool-expr "{" query "}"
|
||||
|
||||
expr: assign-expr
|
||||
|
||||
not-expr: "not" assign-expr
|
||||
|
||||
assign-expr: ref assign-op membership-expr
|
||||
|
||||
membership-expr: membership-expr "in" bool-expr
|
||||
| bool-expr "," bool-expr
|
||||
| bool-expr
|
||||
|
||||
in-expr: in-expr "in" bool-expr
|
||||
| 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
|
||||
| arith-expr
|
||||
|
||||
arith-expr: arith-expr ("+" | "-") mul-div-expr
|
||||
| mul-div-expr
|
||||
|
||||
mul-div-expr: mul-div-expr ("*" | "/") term
|
||||
| term
|
||||
|
||||
term: ref
|
||||
|
||||
ref: scalar-or-var
|
||||
| compr-set-or-object
|
||||
| compr-or-array
|
||||
| unary-expr
|
||||
| parens-expr
|
||||
| ref-dot
|
||||
| ref-brack
|
||||
| call-expr
|
||||
|
||||
ref-dot: ref NO_WS "." NO_WS var
|
||||
|
||||
ref-brack: ref NO_WS "[" in-expr "]"
|
||||
|
||||
call-expr: path-ref NO_WS "(" call-args [","] ")"
|
||||
|
||||
call-args: in-expr { "," in-expr }
|
||||
|
||||
parens-expr: "(" membership-expr ")"
|
||||
|
||||
unary-expr: "-" in-expr
|
||||
|
||||
compr-set-or-object: set-compr
|
||||
| set
|
||||
| object-compr
|
||||
| object
|
||||
|
||||
set-compr: "{" compr "}"
|
||||
|
||||
# Set must have at least one item.
|
||||
set: "{" in-expr { "," in-expr } [","] "}"
|
||||
| "set(" ")" # empty set
|
||||
|
||||
object: "{" field { "," field } [","] "}"
|
||||
| "{" "}" # empty object
|
||||
|
||||
field: in-expr ":" in-expr
|
||||
|
||||
# Comprehension or array
|
||||
compr-or-array: array-compr
|
||||
| array
|
||||
|
||||
array-compr: "[" compr "]"
|
||||
|
||||
array: "[" in-expr { "," in-expr } [","] "]"
|
||||
| "[" "]" # Empty array
|
||||
|
||||
# Comprehension
|
||||
compr: ref "|" query
|
||||
|
||||
scalar-or-var: var
|
||||
| NUMBER
|
||||
| STRING
|
||||
| RAWSTRING
|
||||
| "null"
|
||||
| "true"
|
||||
| "false"
|
||||
|
||||
var: IDENT
|
||||
| non-imported-future-keyword
|
||||
|
||||
non-imported-future-future-keyword: "contains" | "every" | "if" | "in"
|
||||
```
|
||||
49
scripts/coverage
Executable file
49
scripts/coverage
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
set -e
|
||||
|
||||
if ! command -v grcov > /dev/null; then
|
||||
cargo install grcov
|
||||
fi
|
||||
|
||||
if ! command -v llvm-profdata > /dev/null; then
|
||||
rustup component add llvm-tools-preview
|
||||
fi
|
||||
|
||||
#export LLVM_PROFILE_FILE='target/cargo-test-%p-%m.profraw'
|
||||
#export CARGO_INCREMENTAL=1
|
||||
#export RUSTFLAGS='-Cinstrument-coverage'
|
||||
|
||||
echo "Building with instrumentation"
|
||||
cargo build --all-targets
|
||||
|
||||
if [ "$1" == "--no-run" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Remove existing coverage information.
|
||||
rm -f target/*.profraw
|
||||
rm -rf target/coverage
|
||||
mkdir -p target/coverage
|
||||
|
||||
echo "Running tests"
|
||||
cargo test
|
||||
|
||||
# Generate html
|
||||
grcov target/ --binary-path ./target/x86_64-unknown-linux-musl/debug/deps -s src/ -t html \
|
||||
--branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
|
||||
|
||||
if [ "$1" == "--show" ]; then
|
||||
echo "Opening report in browser"
|
||||
xdg-open target/coverage/html/src/index.html 2>/dev/null
|
||||
echo "Done"
|
||||
fi
|
||||
|
||||
# Generate markdown
|
||||
grcov target/ --binary-path ./target/x86_64-unknown-linux-musl/debug/deps -s src/ -t markdown \
|
||||
--branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/markdown
|
||||
cat target/coverage/markdown
|
||||
|
||||
#TODO: Maybe use coveralls format (json) and query data to lockdown code coverage.
|
||||
15
scripts/make-docs
Executable file
15
scripts/make-docs
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
git stash
|
||||
cargo doc --no-deps
|
||||
git checkout docs
|
||||
rm -rf docs
|
||||
cp -r target/x86_64-unknown-linux-musl/doc ./docs
|
||||
echo "<meta http-equiv=\"refresh\" content=\"0; url=rego_rs/index.html\">" > docs/index.html
|
||||
git add docs
|
||||
git commit -s
|
||||
git push
|
||||
git checkout -
|
||||
git stash pop
|
||||
25
scripts/pre-commit
Executable file
25
scripts/pre-commit
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
if [ -f Cargo.toml ]; then
|
||||
# Ensure that all targets can be built.
|
||||
scripts/coverage --no-run
|
||||
|
||||
#Ensure that code is correctly formatted.
|
||||
cargo fmt --check || (echo "Run cargo fmt to fix formatting" && exit 1)
|
||||
|
||||
# Ensure that clippy warnings are addressed.
|
||||
cargo clippy --all-targets --no-deps -- -Dwarnings
|
||||
|
||||
# Ensure that all modifications are included.
|
||||
# TODO refine status checking.
|
||||
if git status -s | grep -e "MM " -e "??" -e "AM " -e " M " > /dev/null; then
|
||||
printf "\nUnstaged changes found:\n"
|
||||
git status -s | grep -e "MM " -e "??" -e "AM " -e " M "
|
||||
echo "Stage them and try again"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
15
scripts/pre-push
Executable file
15
scripts/pre-push
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
if [ -f Cargo.toml ]; then
|
||||
# Run precommit checks
|
||||
dir=$(dirname "${BASH_SOURCE[0]}")
|
||||
"$dir/pre-commit"
|
||||
|
||||
# Ensure that all tests pass
|
||||
# Also generate coverage information.
|
||||
scripts/coverage
|
||||
fi
|
||||
13
scripts/rego-eval
Executable file
13
scripts/rego-eval
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
set -e
|
||||
rego=$(realpath -e $1)
|
||||
|
||||
if [ ! -z "$2" ]; then
|
||||
input=$(realpath -e $2)
|
||||
cargo test interpreter::one_file -- --include-ignored --nocapture "$rego" "$input"
|
||||
else
|
||||
cargo test interpreter::one_file -- --include-ignored --nocapture "$rego"
|
||||
fi
|
||||
27
scripts/rego-lex
Executable file
27
scripts/rego-lex
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
set -e
|
||||
|
||||
usage="usage: rego-lex <policy.rego> [-v]"
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "$usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rego=$(realpath -e $1)
|
||||
|
||||
case "$2" in
|
||||
"-v")
|
||||
verbose="verbose"
|
||||
;;
|
||||
*)
|
||||
if [ ! -z "$2" ]; then
|
||||
echo "$usage"
|
||||
exit 1
|
||||
fi
|
||||
esac
|
||||
|
||||
eval "cargo test lexer::one_file -- --include-ignored --nocapture $rego $verbose"
|
||||
8
scripts/rego-parse
Executable file
8
scripts/rego-parse
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
set -e
|
||||
|
||||
rego=$(realpath -e $1)
|
||||
cargo test parser::one_file -- --include-ignored --nocapture "$rego"
|
||||
8
scripts/yaml-test-eval
Executable file
8
scripts/yaml-test-eval
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
set -e
|
||||
yaml=$(realpath -e $1)
|
||||
|
||||
RUST_BACKTRACE=1 cargo test interpreter::one_yaml -- --include-ignored --nocapture "$yaml"
|
||||
8
scripts/yaml-test-parse
Executable file
8
scripts/yaml-test-parse
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
set -e
|
||||
yaml=$(realpath -e $1)
|
||||
|
||||
RUST_BACKTRACE=1 cargo test parser::one_yaml -- --include-ignored --nocapture "$yaml"
|
||||
23
snippets/2.rego
Normal file
23
snippets/2.rego
Normal file
@@ -0,0 +1,23 @@
|
||||
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
|
||||
}
|
||||
284
src/ast.rs
Normal file
284
src/ast.rs
Normal file
@@ -0,0 +1,284 @@
|
||||
// Copyright (c) Rego-Rs Authors.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
|
||||
use crate::lexer::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum BinOp {
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum ArithOp {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum BoolOp {
|
||||
Lt,
|
||||
Le,
|
||||
Eq,
|
||||
Ge,
|
||||
Gt,
|
||||
Ne,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum AssignOp {
|
||||
Eq,
|
||||
ColEq,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum Expr<'source> {
|
||||
// Simple items that only have a span as content.
|
||||
String(Span<'source>),
|
||||
RawString(Span<'source>),
|
||||
Number(Span<'source>),
|
||||
True(Span<'source>),
|
||||
False(Span<'source>),
|
||||
Null(Span<'source>),
|
||||
Var(Span<'source>),
|
||||
|
||||
// array
|
||||
Array {
|
||||
span: Span<'source>,
|
||||
items: Vec<Expr<'source>>,
|
||||
},
|
||||
|
||||
// set
|
||||
Set {
|
||||
span: Span<'source>,
|
||||
items: Vec<Expr<'source>>,
|
||||
},
|
||||
|
||||
Object {
|
||||
span: Span<'source>,
|
||||
fields: Vec<(Span<'source>, Expr<'source>, Expr<'source>)>,
|
||||
},
|
||||
|
||||
// Comprehensions
|
||||
ArrayCompr {
|
||||
span: Span<'source>,
|
||||
term: Box<Expr<'source>>,
|
||||
query: Query<'source>,
|
||||
},
|
||||
|
||||
SetCompr {
|
||||
span: Span<'source>,
|
||||
term: Box<Expr<'source>>,
|
||||
query: Query<'source>,
|
||||
},
|
||||
|
||||
ObjectCompr {
|
||||
span: Span<'source>,
|
||||
key: Box<Expr<'source>>,
|
||||
value: Box<Expr<'source>>,
|
||||
query: Query<'source>,
|
||||
},
|
||||
|
||||
Call {
|
||||
span: Span<'source>,
|
||||
fcn: Box<Expr<'source>>,
|
||||
params: Vec<Expr<'source>>,
|
||||
},
|
||||
|
||||
UnaryExpr {
|
||||
span: Span<'source>,
|
||||
expr: Box<Expr<'source>>,
|
||||
},
|
||||
|
||||
// ref
|
||||
RefDot {
|
||||
span: Span<'source>,
|
||||
refr: Box<Expr<'source>>,
|
||||
field: Span<'source>,
|
||||
},
|
||||
|
||||
RefBrack {
|
||||
span: Span<'source>,
|
||||
refr: Box<Expr<'source>>,
|
||||
index: Box<Expr<'source>>,
|
||||
},
|
||||
|
||||
// Infix expressions
|
||||
BinExpr {
|
||||
span: Span<'source>,
|
||||
op: BinOp,
|
||||
lhs: Box<Expr<'source>>,
|
||||
rhs: Box<Expr<'source>>,
|
||||
},
|
||||
BoolExpr {
|
||||
span: Span<'source>,
|
||||
op: BoolOp,
|
||||
lhs: Box<Expr<'source>>,
|
||||
rhs: Box<Expr<'source>>,
|
||||
},
|
||||
|
||||
ArithExpr {
|
||||
span: Span<'source>,
|
||||
op: ArithOp,
|
||||
lhs: Box<Expr<'source>>,
|
||||
rhs: Box<Expr<'source>>,
|
||||
},
|
||||
|
||||
AssignExpr {
|
||||
span: Span<'source>,
|
||||
op: AssignOp,
|
||||
lhs: Box<Expr<'source>>,
|
||||
rhs: Box<Expr<'source>>,
|
||||
},
|
||||
|
||||
Membership {
|
||||
span: Span<'source>,
|
||||
key: Box<Expr<'source>>,
|
||||
value: Box<Option<Expr<'source>>>,
|
||||
collection: Box<Expr<'source>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'source> Expr<'source> {
|
||||
pub fn span(&self) -> &Span<'source> {
|
||||
use Expr::*;
|
||||
match self {
|
||||
String(s) | RawString(s) | Number(s) | True(s) | False(s) | Null(s) | Var(s) => s,
|
||||
Array { span, .. }
|
||||
| Set { span, .. }
|
||||
| Object { span, .. }
|
||||
| ArrayCompr { span, .. }
|
||||
| SetCompr { span, .. }
|
||||
| ObjectCompr { span, .. }
|
||||
| Call { span, .. }
|
||||
| UnaryExpr { span, .. }
|
||||
| RefDot { span, .. }
|
||||
| RefBrack { span, .. }
|
||||
| BinExpr { span, .. }
|
||||
| BoolExpr { span, .. }
|
||||
| ArithExpr { span, .. }
|
||||
| AssignExpr { span, .. }
|
||||
| Membership { span, .. } => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum Literal<'source> {
|
||||
SomeVars {
|
||||
span: Span<'source>,
|
||||
vars: Vec<Span<'source>>,
|
||||
},
|
||||
SomeIn {
|
||||
span: Span<'source>,
|
||||
key: Expr<'source>,
|
||||
value: Option<Expr<'source>>,
|
||||
collection: Expr<'source>,
|
||||
},
|
||||
Expr {
|
||||
span: Span<'source>,
|
||||
expr: Expr<'source>,
|
||||
},
|
||||
NotExpr {
|
||||
span: Span<'source>,
|
||||
expr: Expr<'source>,
|
||||
},
|
||||
Every {
|
||||
span: Span<'source>,
|
||||
key: Span<'source>,
|
||||
value: Option<Span<'source>>,
|
||||
domain: Expr<'source>,
|
||||
query: Query<'source>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct WithModifier<'source> {
|
||||
pub span: Span<'source>,
|
||||
pub refr: Expr<'source>,
|
||||
pub r#as: Expr<'source>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct LiteralStmt<'source> {
|
||||
pub span: Span<'source>,
|
||||
pub literal: Literal<'source>,
|
||||
pub with_mods: Vec<WithModifier<'source>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct Query<'source> {
|
||||
pub span: Span<'source>,
|
||||
pub stmts: Vec<LiteralStmt<'source>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct RuleAssign<'source> {
|
||||
pub span: Span<'source>,
|
||||
pub op: AssignOp,
|
||||
pub value: Expr<'source>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct RuleBody<'source> {
|
||||
pub span: Span<'source>,
|
||||
pub assign: Option<RuleAssign<'source>>,
|
||||
pub query: Query<'source>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum RuleHead<'source> {
|
||||
Compr {
|
||||
span: Span<'source>,
|
||||
refr: Expr<'source>,
|
||||
assign: Option<RuleAssign<'source>>,
|
||||
},
|
||||
Set {
|
||||
span: Span<'source>,
|
||||
refr: Expr<'source>,
|
||||
key: Option<Expr<'source>>,
|
||||
},
|
||||
Func {
|
||||
span: Span<'source>,
|
||||
refr: Expr<'source>,
|
||||
args: Vec<Expr<'source>>,
|
||||
assign: Option<RuleAssign<'source>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum Rule<'source> {
|
||||
Spec {
|
||||
span: Span<'source>,
|
||||
head: RuleHead<'source>,
|
||||
bodies: Vec<RuleBody<'source>>,
|
||||
},
|
||||
Default {
|
||||
span: Span<'source>,
|
||||
refr: Expr<'source>,
|
||||
op: AssignOp,
|
||||
value: Expr<'source>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct Package<'source> {
|
||||
pub span: Span<'source>,
|
||||
pub refr: Expr<'source>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct Import<'source> {
|
||||
pub span: Span<'source>,
|
||||
pub refr: Expr<'source>,
|
||||
pub r#as: Option<Span<'source>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct Module<'source> {
|
||||
pub package: Package<'source>,
|
||||
pub imports: Vec<Import<'source>>,
|
||||
pub policy: Vec<Rule<'source>>,
|
||||
}
|
||||
27
src/builtins/compare.rs
Normal file
27
src/builtins/compare.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::ast::*;
|
||||
use crate::value::{Value::*, *};
|
||||
|
||||
pub fn eq(v1: &Value, v2: &Value) -> Value {
|
||||
match (v1, v2) {
|
||||
(Undefined, _) | (_, Undefined) => Undefined,
|
||||
_ => Bool(v1 == v2),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ne(v1: &Value, v2: &Value) -> Value {
|
||||
match (v1, v2) {
|
||||
(Undefined, _) | (_, Undefined) => Undefined,
|
||||
_ => Bool(v1 != v2),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compare(op: &BoolOp, v1: &Value, v2: &Value) -> Value {
|
||||
match op {
|
||||
BoolOp::Eq => eq(v1, v2),
|
||||
BoolOp::Ne => ne(v1, v2),
|
||||
BoolOp::Ge => Bool(v1 >= v2),
|
||||
BoolOp::Gt => Bool(v1 > v2),
|
||||
BoolOp::Le => Bool(v1 <= v2),
|
||||
BoolOp::Lt => Bool(v1 < v2),
|
||||
}
|
||||
}
|
||||
3
src/builtins/mod.rs
Normal file
3
src/builtins/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod compare;
|
||||
|
||||
pub use self::compare::*;
|
||||
1618
src/interpreter.rs
Normal file
1618
src/interpreter.rs
Normal file
File diff suppressed because it is too large
Load Diff
506
src/lexer.rs
Normal file
506
src/lexer.rs
Normal file
@@ -0,0 +1,506 @@
|
||||
// Copyright (c) Rego-Rs Authors.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
|
||||
use core::fmt::{Debug, Formatter};
|
||||
use core::iter::Peekable;
|
||||
use core::str::CharIndices;
|
||||
|
||||
use crate::value::Value;
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct Source<'source> {
|
||||
pub file: &'source str,
|
||||
pub contents: &'source str,
|
||||
pub lines: Vec<&'source str>,
|
||||
}
|
||||
|
||||
impl<'source> Source<'source> {
|
||||
pub fn message(&self, line: u16, col: u16, kind: &str, msg: &str) -> String {
|
||||
if line as usize > self.lines.len() {
|
||||
return format!("{}: invalid line {} specified", self.file, line);
|
||||
}
|
||||
|
||||
let line_str = format!("{}", line);
|
||||
let line_num_width = line_str.len() + 1;
|
||||
let col_spaces = col as usize - 1;
|
||||
|
||||
format!(
|
||||
"\n-->{}:{}:{}\n{:<line_num_width$}|\n\
|
||||
{:<line_num_width$}| {}\n\
|
||||
{:<line_num_width$}| {:<col_spaces$}^\n\
|
||||
{}: {}",
|
||||
self.file,
|
||||
line,
|
||||
col,
|
||||
"",
|
||||
line,
|
||||
self.lines[line as usize - 1],
|
||||
"",
|
||||
"",
|
||||
kind,
|
||||
msg
|
||||
)
|
||||
}
|
||||
|
||||
pub fn error(&self, line: u16, col: u16, msg: &str) -> anyhow::Error {
|
||||
anyhow!(self.message(line, col, "error", msg))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct Span<'source> {
|
||||
pub source: &'source Source<'source>,
|
||||
pub line: u16,
|
||||
pub col: u16,
|
||||
pub start: u16,
|
||||
pub end: u16,
|
||||
}
|
||||
|
||||
impl<'source> Span<'source> {
|
||||
pub fn text(&self) -> &'source str {
|
||||
&self.source.contents[self.start as usize..self.end as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'source> Debug for Span<'source> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
let t = self.text().escape_debug().to_string();
|
||||
let max = 32;
|
||||
let (txt, trailer) = if t.len() > max {
|
||||
(&t[0..max], "...")
|
||||
} else {
|
||||
(t.as_str(), "")
|
||||
};
|
||||
|
||||
f.write_fmt(format_args!(
|
||||
"{}:{}:{}:{}, \"{}{}\"",
|
||||
self.line, self.col, self.start, self.end, txt, trailer
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum TokenKind {
|
||||
Symbol,
|
||||
String,
|
||||
RawString,
|
||||
Number,
|
||||
Ident,
|
||||
Eof,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Token<'source>(pub TokenKind, pub Span<'source>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Lexer<'source> {
|
||||
source: &'source Source<'source>,
|
||||
iter: Peekable<CharIndices<'source>>,
|
||||
line: u16,
|
||||
col: u16,
|
||||
}
|
||||
|
||||
impl<'source> Lexer<'source> {
|
||||
pub fn new(source: &'source Source<'source>) -> Self {
|
||||
Self {
|
||||
source,
|
||||
iter: source.contents.char_indices().peekable(),
|
||||
line: 1,
|
||||
col: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&mut self) -> (usize, char) {
|
||||
match self.iter.peek() {
|
||||
Some((index, chr)) => (*index, *chr),
|
||||
_ => (self.source.contents.len(), '\x00'),
|
||||
}
|
||||
}
|
||||
|
||||
fn peekahead(&mut self, n: usize) -> (usize, char) {
|
||||
match self.iter.clone().nth(n) {
|
||||
Some((index, chr)) => (index, chr),
|
||||
_ => (self.source.contents.len(), '\x00'),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_ident(&mut self) -> Result<Token<'source>> {
|
||||
let start = self.peek().0;
|
||||
let col = self.col;
|
||||
loop {
|
||||
let ch = self.peek().1;
|
||||
if ch.is_ascii_alphanumeric() || ch == '_' {
|
||||
self.iter.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let end = self.peek().0;
|
||||
self.col += (end - start) as u16;
|
||||
Ok(Token(
|
||||
TokenKind::Ident,
|
||||
Span {
|
||||
source: self.source,
|
||||
line: self.line,
|
||||
col,
|
||||
start: start as u16,
|
||||
end: end as u16,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn read_digits(&mut self) {
|
||||
while self.peek().1.is_ascii_digit() {
|
||||
self.iter.next();
|
||||
}
|
||||
}
|
||||
|
||||
// See https://www.json.org/json-en.html for number's grammar
|
||||
fn read_number(&mut self) -> Result<Token<'source>> {
|
||||
let (start, chr) = self.peek();
|
||||
let col = self.col;
|
||||
self.iter.next();
|
||||
|
||||
// Read integer part.
|
||||
if chr != '0' {
|
||||
// Starts with 1.. or 9. Read digits.
|
||||
self.read_digits();
|
||||
}
|
||||
|
||||
// Read fraction part
|
||||
// . must be followed by at least 1 digit.
|
||||
if self.peek().1 == '.' && self.peekahead(1).1.is_ascii_digit() {
|
||||
self.iter.next(); // .
|
||||
self.read_digits();
|
||||
}
|
||||
|
||||
// Read exponent part
|
||||
let ch = self.peek().1;
|
||||
if ch == 'e' || ch == 'E' {
|
||||
self.iter.next();
|
||||
// e must be followed by an optional sign and digits
|
||||
if matches!(self.peek().1, '+' | '-') {
|
||||
self.iter.next();
|
||||
}
|
||||
// Read digits. Absence of digit will be validated by serde later.
|
||||
self.read_digits();
|
||||
}
|
||||
|
||||
let end = self.peek().0;
|
||||
self.col += (end - start) as u16;
|
||||
|
||||
// Check for invalid number.Valid number cannot be followed by
|
||||
// these characters:
|
||||
let ch = self.peek().1;
|
||||
if ch == '_' || ch == '.' || ch.is_ascii_alphanumeric() {
|
||||
return Err(self.source.error(self.line, self.col, "invalid number"));
|
||||
}
|
||||
|
||||
// Ensure that the number is parsable in Rust.
|
||||
match serde_json::from_str::<'source, Value>(&self.source.contents[start..end]) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
let serde_msg = &e.to_string();
|
||||
let msg = match &serde_msg {
|
||||
m if m.contains("out of range") => "out of range",
|
||||
m if m.contains("invalid number") => "invalid number",
|
||||
m if m.contains("expected value") => "expected value",
|
||||
m if m.contains("trailing characters") => "trailing characters",
|
||||
m => m.to_owned(),
|
||||
};
|
||||
|
||||
bail!(
|
||||
"{} {}",
|
||||
self.source.error(
|
||||
self.line,
|
||||
col,
|
||||
"invalid number. serde_json cannot parse number:"
|
||||
),
|
||||
msg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Token(
|
||||
TokenKind::Number,
|
||||
Span {
|
||||
source: self.source,
|
||||
line: self.line,
|
||||
col,
|
||||
start: start as u16,
|
||||
end: end as u16,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn read_raw_string(&mut self) -> Result<Token<'source>> {
|
||||
self.iter.next();
|
||||
self.col += 1;
|
||||
let (start, _) = self.peek();
|
||||
let (line, col) = (self.line, self.col);
|
||||
loop {
|
||||
let (_, ch) = self.peek();
|
||||
self.iter.next();
|
||||
match ch {
|
||||
'`' => {
|
||||
self.col += 1;
|
||||
break;
|
||||
}
|
||||
'\x00' => {
|
||||
return Err(self.source.error(line, col, "unmatched `"));
|
||||
}
|
||||
'\t' => self.col += 4,
|
||||
'\n' => {
|
||||
self.line += 1;
|
||||
self.col = 1;
|
||||
}
|
||||
_ => self.col += 1,
|
||||
}
|
||||
}
|
||||
let end = self.peek().0;
|
||||
Ok(Token(
|
||||
TokenKind::RawString,
|
||||
Span {
|
||||
source: self.source,
|
||||
line,
|
||||
col,
|
||||
start: start as u16,
|
||||
end: end as u16 - 1,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> Result<Token<'source>> {
|
||||
let (line, col) = (self.line, self.col);
|
||||
self.iter.next();
|
||||
self.col += 1;
|
||||
let (start, _) = self.peek();
|
||||
loop {
|
||||
let (offset, ch) = self.peek();
|
||||
let col = self.col + (offset - start) as u16;
|
||||
match ch {
|
||||
'"' | '#' | '\x00' => {
|
||||
break;
|
||||
}
|
||||
'\\' => {
|
||||
self.iter.next();
|
||||
let (_, ch) = self.peek();
|
||||
self.iter.next();
|
||||
match ch {
|
||||
// json escape sequence
|
||||
'"' | '\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't' => (),
|
||||
'u' => {
|
||||
for _i in 0..4 {
|
||||
let (offset, ch) = self.peek();
|
||||
let col = self.col + (offset - start) as u16;
|
||||
if !ch.is_ascii_hexdigit() {
|
||||
return Err(self.source.error(
|
||||
line,
|
||||
col,
|
||||
"invalid hex escape sequence",
|
||||
));
|
||||
}
|
||||
self.iter.next();
|
||||
}
|
||||
}
|
||||
_ => return Err(self.source.error(line, col, "invalid escape sequence")),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// check for valid json chars
|
||||
let col = self.col + (offset - start) as u16;
|
||||
if !('\u{0020}'..='\u{10FFFF}').contains(&ch) {
|
||||
return Err(self.source.error(line, col, "invalid character in string"));
|
||||
}
|
||||
self.iter.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.peek().1 != '"' {
|
||||
return Err(self.source.error(line, col, "unmatched \""));
|
||||
}
|
||||
|
||||
self.iter.next();
|
||||
let end = self.peek().0;
|
||||
self.col += (end - start) as u16;
|
||||
|
||||
// Ensure that the string is parsable in Rust.
|
||||
match serde_json::from_str::<'source, String>(&self.source.contents[start - 1..end]) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
let serde_msg = &e.to_string();
|
||||
let msg = serde_msg;
|
||||
bail!(
|
||||
"{} {}",
|
||||
self.source
|
||||
.error(self.line, col, "serde_json cannot parse string:"),
|
||||
msg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Token(
|
||||
TokenKind::String,
|
||||
Span {
|
||||
source: self.source,
|
||||
line,
|
||||
col: col + 1,
|
||||
start: start as u16,
|
||||
end: end as u16 - 1,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn skip_ws(&mut self) -> Result<()> {
|
||||
// Only the 4 json whitespace characters are recognized.
|
||||
// https://www.crockford.com/mckeeman.html.
|
||||
// Additionally, comments are also skipped.
|
||||
// A tab is considered 4 space characters.
|
||||
'outer: loop {
|
||||
match self.peek().1 {
|
||||
' ' => self.col += 1,
|
||||
'\t' => self.col += 4,
|
||||
'\r' => {
|
||||
if self.peekahead(1).1 != '\n' {
|
||||
return Err(self.source.error(
|
||||
self.line,
|
||||
self.col,
|
||||
"\\r must be followed by \\n",
|
||||
));
|
||||
}
|
||||
}
|
||||
'\n' => {
|
||||
self.col = 1;
|
||||
self.line += 1;
|
||||
}
|
||||
'#' => {
|
||||
self.iter.next();
|
||||
loop {
|
||||
match self.peek().1 {
|
||||
'\n' | '\x00' => continue 'outer,
|
||||
_ => self.iter.next(),
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
self.iter.next();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_token(&mut self) -> Result<Token<'source>> {
|
||||
self.skip_ws()?;
|
||||
|
||||
let (start, chr) = self.peek();
|
||||
let col = self.col;
|
||||
|
||||
match chr {
|
||||
// Special case for - followed by digit which is a
|
||||
// negative json number.
|
||||
// . followed by digit is invalid number.
|
||||
'-' | '.' if self.peekahead(1).1.is_ascii_digit() => {
|
||||
self.read_number()
|
||||
}
|
||||
// grouping characters
|
||||
'{' | '}' | '[' | ']' | '(' | ')' |
|
||||
// arith operator
|
||||
'+' | '-' | '*' | '/' |
|
||||
// bin operator
|
||||
'&' | '|' |
|
||||
// separators
|
||||
',' | ';' | '.' => {
|
||||
self.col += 1;
|
||||
self.iter.next();
|
||||
Ok(Token(TokenKind::Symbol, Span {
|
||||
source: self.source,
|
||||
line: self.line,
|
||||
col,
|
||||
start: start as u16,
|
||||
end: start as u16 + 1,
|
||||
}))
|
||||
}
|
||||
':' => {
|
||||
self.col += 1;
|
||||
self.iter.next();
|
||||
let mut end = start as u16 + 1;
|
||||
if self.peek().1 == '=' {
|
||||
self.col += 1;
|
||||
self.iter.next();
|
||||
end += 1;
|
||||
}
|
||||
Ok(Token(TokenKind::Symbol, Span {
|
||||
source: self.source,
|
||||
line: self.line,
|
||||
col,
|
||||
start: start as u16,
|
||||
end
|
||||
}))
|
||||
}
|
||||
// < <= > >= = ==
|
||||
'<' | '>' | '=' => {
|
||||
self.col += 1;
|
||||
self.iter.next();
|
||||
if self.peek().1 == '=' {
|
||||
self.col += 1;
|
||||
self.iter.next();
|
||||
};
|
||||
Ok(Token(TokenKind::Symbol, Span {
|
||||
source: self.source,
|
||||
line: self.line,
|
||||
col,
|
||||
start: start as u16,
|
||||
end: self.peek().0 as u16,
|
||||
}))
|
||||
}
|
||||
'!' if self.peekahead(1).1 == '=' => {
|
||||
self.col += 2;
|
||||
self.iter.next();
|
||||
self.iter.next();
|
||||
Ok(Token(TokenKind::Symbol, Span {
|
||||
source: self.source,
|
||||
line: self.line,
|
||||
col,
|
||||
start: start as u16,
|
||||
end: self.peek().0 as u16,
|
||||
}))
|
||||
}
|
||||
'"' => self.read_string(),
|
||||
'`' => self.read_raw_string(),
|
||||
'\x00' => Ok(Token(TokenKind::Eof, Span {
|
||||
source: self.source,
|
||||
line:self.line,
|
||||
col,
|
||||
start: start as u16,
|
||||
end: start as u16
|
||||
})),
|
||||
_ if chr.is_ascii_digit() => self.read_number(),
|
||||
_ if chr.is_ascii_alphabetic() || chr == '_' => {
|
||||
let mut ident = self.read_ident()?;
|
||||
if ident.1.text() == "set" && self.peek().1 == '(' {
|
||||
// set immediately followed by ( is treated as set( if
|
||||
// the next token is ).
|
||||
let state = (self.iter.clone(), self.line, self.col);
|
||||
self.iter.next();
|
||||
|
||||
// Check it next token is ).
|
||||
let next_tok = self.next_token()?;
|
||||
let is_setp = next_tok.1.text() == ")";
|
||||
|
||||
// Restore state
|
||||
(self.iter, self.line, self.col) = state;
|
||||
|
||||
if is_setp {
|
||||
self.iter.next();
|
||||
self.col += 1;
|
||||
ident.1.end += 1;
|
||||
}
|
||||
}
|
||||
Ok(ident)
|
||||
}
|
||||
_ => Err(self.source.error(self.line, self.col, "invalid character"))
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/lib.rs
Normal file
15
src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Rego-Rs Authors.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
|
||||
pub mod ast;
|
||||
pub mod builtins;
|
||||
pub mod interpreter;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
pub mod value;
|
||||
|
||||
pub use ast::*;
|
||||
pub use interpreter::*;
|
||||
pub use lexer::*;
|
||||
pub use parser::*;
|
||||
pub use value::*;
|
||||
1572
src/parser.rs
Normal file
1572
src/parser.rs
Normal file
File diff suppressed because it is too large
Load Diff
325
src/value.rs
Normal file
325
src/value.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
// Copyright (c) Rego-Rs Authors.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
|
||||
use core::fmt;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ops;
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use ordered_float::OrderedFloat;
|
||||
use serde::de::{self, Deserializer};
|
||||
use serde::ser::{SerializeMap, Serializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// TODO: rego uses BigNum which has arbitrary precision. But there seems
|
||||
// to be some bugs with it e.g ((a + b) -a) == b doesn't return true for large
|
||||
// values of a and b.
|
||||
// Json doesn't specify a limit on precision, but in practice double (f64) seems
|
||||
// to be enough to support most use cases and portability too.
|
||||
// See discussions in jq's repository.
|
||||
// For now we use OrderedFloat<f64>. We can't use f64 directly since it doesn't
|
||||
// implement Ord trait.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Number(pub OrderedFloat<f64>);
|
||||
|
||||
impl Serialize for Number {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let n_f64 = self.0 .0;
|
||||
let n_i64 = n_f64 as i64;
|
||||
let n_u64 = n_f64 as u64;
|
||||
|
||||
if n_u64 as f64 == n_f64 {
|
||||
serializer.serialize_u64(n_u64)
|
||||
} else if n_i64 as f64 == n_f64 {
|
||||
serializer.serialize_i64(n_i64)
|
||||
} else {
|
||||
serializer.serialize_f64(n_f64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NumberVisitor;
|
||||
impl<'de> de::Visitor<'de> for NumberVisitor {
|
||||
type Value = Number;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "a json number")
|
||||
}
|
||||
|
||||
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E> {
|
||||
Ok(Number(OrderedFloat(v)))
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
|
||||
Ok(Number(OrderedFloat(v as f64)))
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
|
||||
Ok(Number(OrderedFloat(v as f64)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Number {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Number, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_f64(NumberVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Number {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// We cannot use serde_json::Value because Rego has set type and object's key can be
|
||||
// other rego values.
|
||||
// BTree is more efficient that a hast table. Another alternative is a sorted vector.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Value {
|
||||
// Json data types. serde will automatically map json to these variants.
|
||||
Null,
|
||||
Bool(bool),
|
||||
String(String),
|
||||
Number(Number),
|
||||
Array(Rc<Vec<Value>>),
|
||||
Object(Rc<BTreeMap<Value, Value>>),
|
||||
|
||||
// Extra rego data type
|
||||
Set(Rc<BTreeSet<Value>>),
|
||||
|
||||
// Indicate that a value is undefined
|
||||
Undefined,
|
||||
}
|
||||
|
||||
impl Serialize for Value {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
use serde::ser::Error;
|
||||
match self {
|
||||
Value::Null => serializer.serialize_none(),
|
||||
Value::Bool(b) => serializer.serialize_bool(*b),
|
||||
Value::String(s) => serializer.serialize_str(s.as_str()),
|
||||
Value::Number(n) => n.serialize(serializer),
|
||||
Value::Array(a) => a.serialize(serializer),
|
||||
Value::Object(fields) => {
|
||||
let mut map = serializer.serialize_map(Some(fields.len()))?;
|
||||
for (k, v) in fields.iter() {
|
||||
match k {
|
||||
Value::String(_) => map.serialize_entry(k, v)?,
|
||||
_ => {
|
||||
let key_str = serde_json::to_string(k).map_err(Error::custom)?;
|
||||
map.serialize_entry(&key_str, v)?
|
||||
}
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
|
||||
// display set as an array
|
||||
Value::Set(s) => s.serialize(serializer),
|
||||
|
||||
// display undefined as a special string
|
||||
Value::Undefined => serializer.serialize_str("<undefined>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Value {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match serde_json::to_string(self) {
|
||||
Ok(s) => write!(f, "{}", s),
|
||||
Err(_e) => Err(std::fmt::Error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn new_object() -> Value {
|
||||
Value::from_map(BTreeMap::new())
|
||||
}
|
||||
|
||||
pub fn new_set() -> Value {
|
||||
Value::from_set(BTreeSet::new())
|
||||
}
|
||||
|
||||
pub fn new_array() -> Value {
|
||||
Value::from_array(vec![])
|
||||
}
|
||||
|
||||
pub fn from_json_str(json: &str) -> Result<Value> {
|
||||
Ok(serde_json::from_str(json)?)
|
||||
}
|
||||
|
||||
pub fn to_json_str(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string_pretty(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn from_f64(v: f64) -> Value {
|
||||
Value::Number(Number(OrderedFloat(v)))
|
||||
}
|
||||
|
||||
pub fn from_array(a: Vec<Value>) -> Value {
|
||||
Value::Array(Rc::new(a))
|
||||
}
|
||||
|
||||
pub fn from_set(s: BTreeSet<Value>) -> Value {
|
||||
Value::Set(Rc::new(s))
|
||||
}
|
||||
|
||||
pub fn from_map(m: BTreeMap<Value, Value>) -> Value {
|
||||
Value::Object(Rc::new(m))
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
matches!(self, Value::Null)
|
||||
}
|
||||
|
||||
pub fn is_undefined(&self) -> bool {
|
||||
matches!(self, Value::Null)
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Result<&bool> {
|
||||
match self {
|
||||
Value::Bool(b) => Ok(b),
|
||||
_ => Err(anyhow!("not a bool")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bool_mut(&mut self) -> Result<&mut bool> {
|
||||
match self {
|
||||
Value::Bool(b) => Ok(b),
|
||||
_ => Err(anyhow!("not a bool")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Result<&String> {
|
||||
match self {
|
||||
Value::String(s) => Ok(s),
|
||||
_ => Err(anyhow!("not a string")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string_mut(&mut self) -> Result<&mut String> {
|
||||
match self {
|
||||
Value::String(s) => Ok(s),
|
||||
_ => Err(anyhow!("not a string")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_number(&self) -> Result<&Number> {
|
||||
match self {
|
||||
Value::Number(n) => Ok(n),
|
||||
_ => Err(anyhow!("not a number")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_number_mut(&mut self) -> Result<&mut Number> {
|
||||
match self {
|
||||
Value::Number(n) => Ok(n),
|
||||
_ => Err(anyhow!("not a number")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_array(&self) -> Result<&Vec<Value>> {
|
||||
match self {
|
||||
Value::Array(a) => Ok(a),
|
||||
_ => Err(anyhow!("not an array")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_array_mut(&mut self) -> Result<&mut Vec<Value>> {
|
||||
match self {
|
||||
Value::Array(a) => Ok(Rc::make_mut(a)),
|
||||
_ => Err(anyhow!("not an array")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_set(&self) -> Result<&BTreeSet<Value>> {
|
||||
match self {
|
||||
Value::Set(s) => Ok(s),
|
||||
_ => Err(anyhow!("not a set")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_set_mut(&mut self) -> Result<&mut BTreeSet<Value>> {
|
||||
match self {
|
||||
Value::Set(s) => Ok(Rc::make_mut(s)),
|
||||
_ => Err(anyhow!("not a set")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_object(&self) -> Result<&BTreeMap<Value, Value>> {
|
||||
match self {
|
||||
Value::Object(m) => Ok(m),
|
||||
_ => Err(anyhow!("not an object")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_object_mut(&mut self) -> Result<&mut BTreeMap<Value, Value>> {
|
||||
match self {
|
||||
Value::Object(m) => Ok(Rc::make_mut(m)),
|
||||
_ => Err(anyhow!("not an object")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<usize> for Value {
|
||||
type Output = Value;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
match self.as_array() {
|
||||
Ok(a) if index < a.len() => &a[index],
|
||||
_ => &Value::Undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<&str> for Value {
|
||||
type Output = Value;
|
||||
|
||||
fn index(&self, key: &str) -> &Self::Output {
|
||||
&self[&Value::String(key.to_owned())]
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<&String> for Value {
|
||||
type Output = Value;
|
||||
|
||||
fn index(&self, key: &String) -> &Self::Output {
|
||||
&self[&Value::String(key.clone())]
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Index<&Value> for Value {
|
||||
type Output = Value;
|
||||
|
||||
fn index(&self, key: &Value) -> &Self::Output {
|
||||
match (self, &key) {
|
||||
(Value::Object(o), _) => match &o.get(key) {
|
||||
Some(v) => v,
|
||||
_ => &Value::Undefined,
|
||||
},
|
||||
(Value::Array(a), Value::Number(n)) => {
|
||||
let index = n.0 .0 as usize;
|
||||
if index < a.len() {
|
||||
&a[index]
|
||||
} else {
|
||||
&Value::Undefined
|
||||
}
|
||||
}
|
||||
_ => &Value::Undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
45
tests/interpreter/cases/arithmetic/mod.rs
Normal file
45
tests/interpreter/cases/arithmetic/mod.rs
Normal 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
tests/interpreter/cases/basic_001.yaml
Normal file
32
tests/interpreter/cases/basic_001.yaml
Normal 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
|
||||
234
tests/interpreter/cases/builtins/compare.yaml
Normal file
234
tests/interpreter/cases/builtins/compare.yaml
Normal file
@@ -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
tests/interpreter/cases/call/basic.yaml
Normal file
44
tests/interpreter/cases/call/basic.yaml
Normal 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
|
||||
|
||||
132
tests/interpreter/cases/compr/array-vs-compr-tricky.yaml
Normal file
132
tests/interpreter/cases/compr/array-vs-compr-tricky.yaml
Normal file
@@ -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
tests/interpreter/cases/compr/mod.rs
Normal file
123
tests/interpreter/cases/compr/mod.rs
Normal 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
tests/interpreter/cases/compr/object.yaml
Normal file
67
tests/interpreter/cases/compr/object.yaml
Normal 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:
|
||||
56
tests/interpreter/cases/default/basic.yaml
Normal file
56
tests/interpreter/cases/default/basic.yaml
Normal file
@@ -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
tests/interpreter/cases/in/mod.rs
Normal file
126
tests/interpreter/cases/in/mod.rs
Normal 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
tests/interpreter/cases/mod.rs
Normal file
7
tests/interpreter/cases/mod.rs
Normal 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
tests/interpreter/cases/multi/basic.yaml
Normal file
32
tests/interpreter/cases/multi/basic.yaml
Normal 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
|
||||
32
tests/interpreter/cases/rule/contains.yaml
Normal file
32
tests/interpreter/cases/rule/contains.yaml
Normal file
@@ -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]
|
||||
|
||||
41
tests/interpreter/cases/rule/dependency.yaml
Normal file
41
tests/interpreter/cases/rule/dependency.yaml
Normal file
@@ -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
tests/interpreter/cases/rule/object.yaml
Normal file
35
tests/interpreter/cases/rule/object.yaml
Normal 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
tests/interpreter/cases/rule/old_set.yaml
Normal file
31
tests/interpreter/cases/rule/old_set.yaml
Normal 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"]
|
||||
|
||||
249
tests/interpreter/cases/snippets/snippets.yaml
Normal file
249
tests/interpreter/cases/snippets/snippets.yaml
Normal file
@@ -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]
|
||||
75
tests/interpreter/cases/variables/basic.yaml
Normal file
75
tests/interpreter/cases/variables/basic.yaml
Normal file
@@ -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
tests/interpreter/cases/variables/mod.rs
Normal file
54
tests/interpreter/cases/variables/mod.rs
Normal 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
tests/interpreter/mod.rs
Normal file
386
tests/interpreter/mod.rs
Normal 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()
|
||||
}
|
||||
34
tests/lexer/cases/all.yaml
Normal file
34
tests/lexer/cases/all.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- rego: |
|
||||
+ - * # This is a comment
|
||||
/
|
||||
[ ] {} ( ) & |
|
||||
, .
|
||||
< <= = == > >=
|
||||
tIS+t[#
|
||||
#[ ] {} ( ) & |
|
||||
#, .
|
||||
#< <= = == > >=*/
|
||||
a]
|
||||
1 0 1230 0.1 0.123 123.0 123.12345 1.023e308
|
||||
1 . #
|
||||
"Hello" "Hello\tWorld" ""
|
||||
"abc"
|
||||
`This is
|
||||
a raw string`
|
||||
. 5 set ( ) set( ) set( 5 )
|
||||
# The following will be lexed as 'set' '(' since there is no matching ')'.
|
||||
set(
|
||||
note: all-tokens
|
||||
tokens:
|
||||
[ "+", "-", "*", "/", "[", "]", "{", "}", "(", ")", "&", "|", ",",
|
||||
".", "<", "<=", "=", "==", ">", ">=",
|
||||
"tIS", "+", "t", "[", "a", "]", "1", "0", "1230", "0.1",
|
||||
"0.123", "123.0", "123.12345", "1.023e308", "1", ".",
|
||||
"Hello", "Hello\\tWorld", "", "abc", "This is\na raw string",
|
||||
".", "5", "set", "(", ")", "set(", ")", "set", "(", "5", ")",
|
||||
"set", "(",
|
||||
""]
|
||||
10
tests/lexer/cases/boolean.yaml
Normal file
10
tests/lexer/cases/boolean.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
# To Booleans are parsed as identifiers.
|
||||
# It is up to the parser to handle them as appropriate.
|
||||
- note: boolean/all
|
||||
rego: true false
|
||||
tokens: [ "true", "false", "" ]
|
||||
kinds: [Ident, Ident, Eof]
|
||||
71
tests/lexer/cases/comment.yaml
Normal file
71
tests/lexer/cases/comment.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: comments-immediately-following/1
|
||||
rego: |
|
||||
#This is a comment #Nested comment
|
||||
ident#a
|
||||
123.4e5#c
|
||||
6#c
|
||||
+#c
|
||||
-#c
|
||||
*#c
|
||||
/#c
|
||||
&#c
|
||||
|#c
|
||||
[#c
|
||||
]#c
|
||||
(#c
|
||||
)#c
|
||||
{#c
|
||||
}#c
|
||||
,#c
|
||||
;#c
|
||||
.#c
|
||||
:#c
|
||||
<#c
|
||||
<=#c
|
||||
>#c
|
||||
>=#c
|
||||
=#c
|
||||
==#c
|
||||
# Ensure that <, =, > are read separately.
|
||||
<#c
|
||||
=#c
|
||||
>#c
|
||||
=#c
|
||||
=#c
|
||||
tokens: ["ident", "123.4e5", "6", "+", "-", "*", "/", "&", "|",
|
||||
"[", "]", "(", ")", "{", "}", ",", ";", ".", ":",
|
||||
"<", "<=", ">", ">=", "=", "==",
|
||||
"<", "=", ">", "=", "=",
|
||||
""]
|
||||
|
||||
# Non-ascii chars can appear in comments.
|
||||
- note: comments-non-ascii
|
||||
rego: # சிக்கி
|
||||
tokens: [""]
|
||||
|
||||
- note: comments-within-raw-string
|
||||
rego: |
|
||||
#Comments aren't lexed within raw strings.
|
||||
`raw#This is not a comment
|
||||
string`#c
|
||||
tokens: ["raw#This is not a comment\nstring", ""]
|
||||
|
||||
- note: comments-within-rstring
|
||||
rego: |
|
||||
#Comments aren't allowed within strings,
|
||||
"#This is not a comment
|
||||
"#c
|
||||
tokens:
|
||||
error: unmatched "
|
||||
|
||||
- note: comment-integer-break
|
||||
rego: |
|
||||
12#
|
||||
.3
|
||||
tokens:
|
||||
error: invalid number
|
||||
|
||||
35
tests/lexer/cases/eof.yaml
Normal file
35
tests/lexer/cases/eof.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
# All other yamls lock down eof.
|
||||
# Specific eof tests:
|
||||
# Empty file.
|
||||
- note: empty-file
|
||||
rego:
|
||||
tokens: [""]
|
||||
kinds: [Eof]
|
||||
|
||||
# File with comments.
|
||||
- note: only-comments
|
||||
rego: |
|
||||
# This is a comment.
|
||||
# This is another comment.
|
||||
tokens: [""]
|
||||
kinds: [Eof]
|
||||
|
||||
# File with eof and trailing chars.
|
||||
# Trailing chars are ignored.
|
||||
- note: eof-trailing-chars
|
||||
rego: "true (\x0000false + hello"
|
||||
tokens: ["true", "(", ""]
|
||||
kinds: [Ident, Symbol, Eof]
|
||||
|
||||
# Empty string must be distinguishable from Eof eventhough
|
||||
# both result in an empty string as the span.
|
||||
- note: empty-string-eof
|
||||
rego: "\"\""
|
||||
tokens: ["", ""]
|
||||
kinds: [String, Eof]
|
||||
|
||||
|
||||
38
tests/lexer/cases/identifier.yaml
Normal file
38
tests/lexer/cases/identifier.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: all
|
||||
rego: a ab abc a1 a1b1 a1b1c _ _a _ab _1 _abc1
|
||||
tokens: ["a", "ab", "abc", "a1", "a1b1", "a1b1c",
|
||||
"_", "_a", "_ab", "_1", "_abc1",
|
||||
""]
|
||||
|
||||
- note: placeholder
|
||||
rego: _
|
||||
tokens: ["_", ""]
|
||||
|
||||
# Identifiers can only be accii.
|
||||
- note: invalid-char/1
|
||||
rego: சிக்கி
|
||||
tokens:
|
||||
error: invalid character
|
||||
- note: invalid-char/2
|
||||
rego: aசி_க்கி
|
||||
tokens:
|
||||
error: invalid character
|
||||
|
||||
# set( is a special case.
|
||||
- note: set(
|
||||
rego: |
|
||||
# This is a function call.
|
||||
set ()
|
||||
# This is lexed as "set(" ")"
|
||||
set()
|
||||
# This is lexed as a function call.
|
||||
set(5)
|
||||
tokens: [
|
||||
"set", "(", ")",
|
||||
"set(", ")",
|
||||
"set", "(", "5", ")",
|
||||
""]
|
||||
10
tests/lexer/cases/keyword.yaml
Normal file
10
tests/lexer/cases/keyword.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
cases:
|
||||
# Keywords are parsed as identifiers.
|
||||
# It is up to the parser to handle them as appropriate.
|
||||
- note: keywords/all
|
||||
rego: as default else false import package not null some true with
|
||||
tokens: [ "as", "default", "else", "false", "import",
|
||||
"package", "not", "null", "some", "true", "with",
|
||||
"" ]
|
||||
kinds: [Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
|
||||
Ident, Ident, Ident, Eof]
|
||||
13
tests/lexer/cases/newline.yaml
Normal file
13
tests/lexer/cases/newline.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: carriage-return/ok
|
||||
rego: "\n\n\r\n\n\n "
|
||||
tokens: [""]
|
||||
|
||||
- note: carriage-return/ok
|
||||
rego: "\r \n"
|
||||
tokens: [""]
|
||||
error: \r must be followed by \n
|
||||
|
||||
184
tests/lexer/cases/number.yaml
Normal file
184
tests/lexer/cases/number.yaml
Normal file
@@ -0,0 +1,184 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
# Valid integers
|
||||
- note: integers
|
||||
rego: |
|
||||
# Integers
|
||||
0 1 11 22 12345678
|
||||
0 -1 -11 -22 -12345678
|
||||
tokens:
|
||||
[ "0", "1", "11", "22", "12345678",
|
||||
"0", "-1", "-11", "-22", "-12345678",
|
||||
""]
|
||||
|
||||
# Large integers are supported.
|
||||
# Note, currently integers that can't fit in an f64 may nto evaluate correctly.
|
||||
- note: large-integers
|
||||
rego: |
|
||||
12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
-12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
tokens:
|
||||
["12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
"-12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
""]
|
||||
|
||||
# Negative zero seems to be supported in json.
|
||||
# - followed by a space and digit will be parsed as two tokens.
|
||||
- note: negative-zero
|
||||
rego: -0 - 0
|
||||
tokens: [ "-0", "-", "0", "" ]
|
||||
|
||||
# 0 cannot be followed by 0 or other digits.
|
||||
- note: invalid-leading-0/a
|
||||
rego: 00
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-leading-0/b
|
||||
rego: 01
|
||||
tokens:
|
||||
error: invalid number
|
||||
|
||||
# Number cannot be followed by . _ or alphanumeric
|
||||
- note: invalid-suffix/0.
|
||||
rego: 0.
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-suffix/1_
|
||||
rego: 1_
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-suffix/9a
|
||||
rego: 9a
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-suffix/0.1.
|
||||
rego: 0.1.
|
||||
tokens:
|
||||
error: invalid number
|
||||
|
||||
# Floats
|
||||
- note: floats
|
||||
rego: |
|
||||
0.1 1.09 11.000000001 22.333 12345678.6789
|
||||
0.1 -1.09 -11.000000001 -22.3333 -12345678.6789
|
||||
tokens: ["0.1", "1.09", "11.000000001", "22.333", 12345678.6789,
|
||||
"0.1", "-1.09", "-11.000000001", "-22.3333", "-12345678.6789",
|
||||
""]
|
||||
|
||||
# Large integers are supported.
|
||||
# Note, currently integers that can't fit in an f64 may not evaluate correctly.
|
||||
- note: large-floats
|
||||
rego: |
|
||||
12345678901234567890123456789012345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
-12345678901234567890123456789012345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
tokens:
|
||||
["12345678901234567890123456789012345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
"-12345678901234567890123456789012345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||
""]
|
||||
|
||||
# Specific floats
|
||||
- note: specific-floats
|
||||
rego: |
|
||||
123.456e-789 0.4e0066 -1e+308 -123456e303 123456e303
|
||||
123456e-10000000 -123456789123456789123456789123
|
||||
900000000000000000000
|
||||
-237462374673276894279832749832423479823246327846
|
||||
123e65 0e9 -0 -1 2E34 2E-3 2E+3
|
||||
-0.000000000000000000000000000000000000000000000000000000000000000000000000000009
|
||||
tokens: ["123.456e-789", "0.4e0066", "-1e+308",
|
||||
"-123456e303", "123456e303", "123456e-10000000",
|
||||
"-123456789123456789123456789123",
|
||||
"900000000000000000000",
|
||||
"-237462374673276894279832749832423479823246327846",
|
||||
"123e65", "0e9", "-0", "-1", "2E34", "2E-3", "2E+3",
|
||||
"-0.000000000000000000000000000000000000000000000000000000000000000000000000000009",
|
||||
""]
|
||||
|
||||
# + sign is parsed as separate token
|
||||
- note: plus-number
|
||||
rego: +1 +1.2 +1e2 +1E2
|
||||
tokens: [ "+", "1", "+", "1.2", "+", "1e2", "+", "1E2", "" ]
|
||||
|
||||
# Bunch of invalid numbers
|
||||
- note: invalid-number/0.1.1
|
||||
rego: 0.1.1
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/0e1.1
|
||||
rego: 0e1.1
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/01
|
||||
rego: 01
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/0.4e
|
||||
rego: 0.4e
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/0.4E
|
||||
rego: 0.4E
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/0.4e-
|
||||
rego: 0.4e-
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/0.Ee-
|
||||
rego: 0.4E-
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/0.4e+
|
||||
rego: 0.4e+
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/0.Ee+
|
||||
rego: 0.4E+
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/8a
|
||||
rego: 8a
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/8eA
|
||||
rego: 8eA
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-hex-number/0x1
|
||||
rego: 0x1
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-octal-number/01
|
||||
rego: 01
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/8_
|
||||
rego: 8_
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/8.1_
|
||||
rego: 8.1_
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/.8
|
||||
rego: .8
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/8.
|
||||
rego: 8.
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/2.e3
|
||||
rego: 2.e3
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/2.e+3
|
||||
rego: 2.e3
|
||||
tokens:
|
||||
error: invalid number
|
||||
- note: invalid-number/2.e-3
|
||||
rego: 2.e3
|
||||
tokens:
|
||||
error: invalid number
|
||||
37
tests/lexer/cases/rawstring.yaml
Normal file
37
tests/lexer/cases/rawstring.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: positive
|
||||
rego: |
|
||||
#Empty
|
||||
``
|
||||
# Multiline
|
||||
` This is m
|
||||
u
|
||||
lti
|
||||
line`
|
||||
|
||||
`"String within rawstring"`
|
||||
|
||||
# Comment retained
|
||||
`
|
||||
Hello #retained comment!
|
||||
`
|
||||
# Utf-8
|
||||
`Hello, சிக்கி
|
||||
`
|
||||
tokens: [ "", " This is m\n u\n lti\nline",
|
||||
"\"String within rawstring\"",
|
||||
"\nHello #retained comment!\n",
|
||||
"Hello, சிக்கி\n",
|
||||
""]
|
||||
|
||||
kinds: [RawString, RawString, RawString, RawString, RawString, Eof]
|
||||
|
||||
- note: unclosed
|
||||
rego: |
|
||||
`
|
||||
Peekoo Maharaaj ki jai ho
|
||||
tokens: []
|
||||
error: unmatched `
|
||||
114
tests/lexer/cases/string.yaml
Normal file
114
tests/lexer/cases/string.yaml
Normal file
@@ -0,0 +1,114 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: positive
|
||||
rego: |
|
||||
"Hello, World\n"
|
||||
|
||||
# Correctly parse double-quote
|
||||
"\""
|
||||
|
||||
#Escape sequences
|
||||
"\"\\\/\b\f\n\r\t"
|
||||
|
||||
# UTF-8 codes
|
||||
"\u0060\u012a\u12AB"
|
||||
|
||||
# Surrogate pairs
|
||||
"\uD801\udc37\ud83d\ude39\ud83d\udc8d\uDBFF\uDFFF"
|
||||
|
||||
# This shouldn't be parsed as an invalid escape.
|
||||
"\\upqrs"
|
||||
|
||||
# Control char
|
||||
"\u0012"
|
||||
|
||||
# Lowest possible code followed by chars.
|
||||
"\u0000 a"
|
||||
|
||||
# Largest possible code
|
||||
"\uFFFF" "\uffff"
|
||||
|
||||
# Unicode escape
|
||||
"\u0061\u30af\u30EA\u30b9"
|
||||
|
||||
"சிக்கி π, €𝄞㈴ é"
|
||||
tokens: [
|
||||
"Hello, World\\n",
|
||||
"\\\"",
|
||||
"\\\"\\\\\\/\\b\\f\\n\\r\\t",
|
||||
"\\u0060\\u012a\\u12AB",
|
||||
"\\uD801\\udc37\\ud83d\\ude39\\ud83d\\udc8d\\uDBFF\\uDFFF",
|
||||
"\\\\upqrs",
|
||||
"\\u0012",
|
||||
"\\u0000 a",
|
||||
"\\uFFFF", "\\uffff",
|
||||
"\\u0061\\u30af\\u30EA\\u30b9",
|
||||
#"\\uDADA",
|
||||
"சிக்கி π, €𝄞㈴ é",
|
||||
""]
|
||||
|
||||
- note: invalid-char
|
||||
rego: |
|
||||
"world\n\r\b "
|
||||
tokens: [""]
|
||||
error: invalid character in string
|
||||
|
||||
- note: unmatched
|
||||
rego: "\"Chikki Kuttiiii"
|
||||
tokens: []
|
||||
error: unmatched "
|
||||
|
||||
- note: newline
|
||||
rego: "\"Chikki Kuttiiii\n"
|
||||
tokens: []
|
||||
error: invalid character in string
|
||||
|
||||
- note: invalid/capital-U
|
||||
rego: |
|
||||
"\U1234"
|
||||
tokens: []
|
||||
error: invalid escape sequence
|
||||
|
||||
- note: invalid/unclosed-escape
|
||||
rego: "\"\\"
|
||||
tokens: []
|
||||
error: invalid escape sequence
|
||||
|
||||
- note: invalid/solo-double-quote
|
||||
rego: "\""
|
||||
tokens: []
|
||||
error: unmatched "
|
||||
|
||||
- note: invalid/unicode-escape
|
||||
rego: |
|
||||
"\uabcg"
|
||||
tokens: []
|
||||
error: invalid hex escape sequence
|
||||
|
||||
- note: invalid/ascii-escape
|
||||
rego: |
|
||||
"\a"
|
||||
tokens: []
|
||||
error: invalid escape sequence
|
||||
|
||||
- note: invalid/hex-escape
|
||||
rego: |
|
||||
"\uabc"
|
||||
tokens: []
|
||||
error: invalid hex escape sequence
|
||||
|
||||
- note: invalid/escape-slash
|
||||
rego: |
|
||||
"\\\ a"
|
||||
tokens: []
|
||||
error: invalid escape sequence
|
||||
|
||||
- note: unparsable
|
||||
rego: |
|
||||
"\uD805"
|
||||
tokens: [""]
|
||||
error: serde_json cannot parse string
|
||||
|
||||
|
||||
23
tests/lexer/cases/symbol.yaml
Normal file
23
tests/lexer/cases/symbol.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
# Two single char symbols in a row must be parsed correctly.
|
||||
- note: single-char-two-in-a-row
|
||||
rego: |
|
||||
++--**//&&||{{}}(())[[]];;,,..::<<>>
|
||||
tokens: [
|
||||
"+", "+", "-", "-", "*", "*", "/", "/", "&", "&",
|
||||
"|", "|", "{", "{", "}", "}", "(", "(", ")", ")",
|
||||
"[", "[", "]", "]", ";", ";", ",", ",", ".", ".",
|
||||
":", ":", "<", "<", ">", ">",
|
||||
""]
|
||||
|
||||
# Multi char operators must be correctly parsed.
|
||||
- note: multi-char-in-a-row
|
||||
rego: ">====<== ==="
|
||||
tokens: [
|
||||
">=", "==", "=", "<=", "=", "==", "=",
|
||||
""]
|
||||
|
||||
|
||||
96
tests/lexer/cases/whitespace.yaml
Normal file
96
tests/lexer/cases/whitespace.yaml
Normal file
@@ -0,0 +1,96 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: valid
|
||||
rego: "\n\t\r\n "
|
||||
tokens: [""]
|
||||
kinds: [Eof]
|
||||
|
||||
# Lock down unicode whitespace chars other than above.
|
||||
# https://en.wikipedia.org/wiki/Whitespace_character#Unicode
|
||||
- note: invalid/\u000b
|
||||
rego: "\u000b"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u000c
|
||||
rego: "\u000c"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u0085
|
||||
rego: "\u0085"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u00a0
|
||||
rego: "\u00a0"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u1680
|
||||
rego: "\u1680"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2000
|
||||
rego: "\u2000"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2001
|
||||
rego: "\u2001"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2002
|
||||
rego: "\u2002"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2003
|
||||
rego: "\u2003"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2004
|
||||
rego: "\u2004"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2005
|
||||
rego: "\u2005"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2006
|
||||
rego: "\u2006"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2007
|
||||
rego: "\u2007"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2008
|
||||
rego: "\u2008"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2009
|
||||
rego: "\u2009"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u200a
|
||||
rego: "\u200a"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2028
|
||||
rego: "\u2028"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u2029
|
||||
rego: "\u2029"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u202f
|
||||
rego: "\u202f"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u205f
|
||||
rego: "\u205f"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
- note: invalid/\u3000
|
||||
rego: "\u3000"
|
||||
tokens: []
|
||||
error: invalid character
|
||||
|
||||
335
tests/lexer/mod.rs
Normal file
335
tests/lexer/mod.rs
Normal file
@@ -0,0 +1,335 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use rego_rs::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use test_generator::test_resources;
|
||||
//use walkdir::WalkDir;
|
||||
|
||||
fn get_tokens<'source>(source: &'source Source<'source>) -> Result<Vec<Token<'source>>> {
|
||||
let mut tokens = vec![];
|
||||
let mut lex = Lexer::new(source);
|
||||
loop {
|
||||
let tok = lex.next_token()?;
|
||||
tokens.push(tok.clone());
|
||||
if tok.0 == TokenKind::Eof {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn check_loc(tok: &Token) -> Result<()> {
|
||||
let msg = tok.1.source.message(tok.1.line, tok.1.col, "", "");
|
||||
let lines: Vec<&str> = msg.split('\n').collect();
|
||||
let source_line = lines[3];
|
||||
let caret_line = lines[4];
|
||||
let mut idx = 0usize;
|
||||
let mut source_idx = idx;
|
||||
loop {
|
||||
match source_idx < source_line.len() && idx < caret_line.len() {
|
||||
true => (),
|
||||
// Handle Eof
|
||||
false if tok.0 == TokenKind::Eof && source_idx >= source_line.len() => return Ok(()),
|
||||
// Handle case where a raw string's first char is a newline.
|
||||
false if tok.0 == TokenKind::RawString && &tok.1.text()[0..1] == "\n" => return Ok(()),
|
||||
_ => bail!("could not find caret for {tok:#?} {msg}"),
|
||||
}
|
||||
match &caret_line[idx..idx + 1] {
|
||||
"^" => {
|
||||
let span_str = tok.1.text();
|
||||
let span_str = span_str.split('\n').collect::<Vec<&str>>()[0];
|
||||
let source_str = &source_line[source_idx..];
|
||||
assert!(
|
||||
source_str.starts_with(span_str) || span_str.starts_with(source_str),
|
||||
"location mismatch for {tok:#?} {msg}\n{span_str}\n{source_str}"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
_ if &source_line[source_idx..source_idx + 1] == "\t" => idx += 4,
|
||||
_ => idx += 1,
|
||||
}
|
||||
source_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "intended for use by scripts/lex-file"]
|
||||
fn one_file() -> Result<()> {
|
||||
let mut file = String::default();
|
||||
let mut verbose = false;
|
||||
for a in env::args() {
|
||||
if a.ends_with(".rego") {
|
||||
file = a.clone();
|
||||
}
|
||||
if matches!(a.as_str(), "verbose") {
|
||||
verbose = true;
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
};
|
||||
|
||||
for tok in &get_tokens(&source)? {
|
||||
if tok.0 == TokenKind::Eof {
|
||||
break;
|
||||
}
|
||||
check_loc(tok)?;
|
||||
if verbose {
|
||||
println!("{}", tok.1.source.message(tok.1.line, tok.1.col, "", ""));
|
||||
}
|
||||
println!("{:?}", tok);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct Case {
|
||||
pub rego: String,
|
||||
pub note: String,
|
||||
pub tokens: Vec<String>,
|
||||
pub kinds: Option<Vec<String>>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct Test {
|
||||
cases: Vec<Case>,
|
||||
}
|
||||
|
||||
fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
println!("\nrunning {}", file);
|
||||
|
||||
let yaml = std::fs::read_to_string(file)?;
|
||||
let test: Test = serde_yaml::from_str(&yaml)?;
|
||||
|
||||
for case in &test.cases {
|
||||
let source = Source {
|
||||
file: "case.rego",
|
||||
contents: case.rego.as_str(),
|
||||
lines: case.rego.as_str().split('\n').collect(),
|
||||
};
|
||||
|
||||
print!("case {} ", &case.note);
|
||||
|
||||
match get_tokens(&source) {
|
||||
Ok(tokens) => {
|
||||
for (idx, tok) in tokens.iter().enumerate() {
|
||||
if idx >= case.tokens.len() {
|
||||
break;
|
||||
}
|
||||
assert_eq!(
|
||||
tok.1.text(),
|
||||
case.tokens[idx],
|
||||
"{} Expected token `{}` not found",
|
||||
source.message(tok.1.line, tok.1.col, "mismatch-error", &case.tokens[idx]),
|
||||
&case.tokens[idx]
|
||||
);
|
||||
|
||||
if let Some(k) = &case.kinds {
|
||||
if idx >= k.len() {
|
||||
break;
|
||||
}
|
||||
assert_eq!(
|
||||
format!("{:?}", tok.0),
|
||||
k[idx],
|
||||
"{}",
|
||||
source.message(
|
||||
tok.1.line,
|
||||
tok.1.col,
|
||||
"mismatch-error",
|
||||
"token kind mismatch"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
check_loc(tok)?;
|
||||
}
|
||||
assert_eq!(
|
||||
tokens.len(),
|
||||
case.tokens.len(),
|
||||
"\n. Token count mismatch.\nLexed tokens:{:?}",
|
||||
tokens
|
||||
);
|
||||
if let Some(k) = &case.kinds {
|
||||
assert_eq!(
|
||||
tokens.len(),
|
||||
k.len(),
|
||||
"\n. Kind count mismatch.\nLexed tokens:{:?}",
|
||||
tokens
|
||||
);
|
||||
}
|
||||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => return Err(actual),
|
||||
},
|
||||
}
|
||||
|
||||
println!("passed");
|
||||
}
|
||||
println!("{} cases passed.", test.cases.len());
|
||||
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]
|
||||
#[ignore = "intended for use by scripts/yaml-test-lex"]
|
||||
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 yaml test file");
|
||||
}
|
||||
|
||||
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!("{} lexer yaml tests passed.", total);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lexer_yaml_tests() -> Result<()> {
|
||||
run_yaml_tests_in("tests/lexer")
|
||||
}*/
|
||||
|
||||
#[test_resources("tests/lexer/**/*.yaml")]
|
||||
fn run(path: &str) {
|
||||
yaml_test(path).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug() -> Result<()> {
|
||||
let rego = "\"This string is 35 characters long.\"\"short string\"";
|
||||
let source = Source {
|
||||
file: "case.rego",
|
||||
contents: rego,
|
||||
lines: rego.split('\n').collect(),
|
||||
};
|
||||
|
||||
let mut lexer = Lexer::new(&source);
|
||||
let tok = lexer.next_token()?;
|
||||
check_loc(&tok)?;
|
||||
|
||||
assert_eq!(
|
||||
format!("{:?}", tok.1),
|
||||
"1:2:1:35, \"This string is 35 characters lon...\"",
|
||||
"long span not truncated correctly"
|
||||
);
|
||||
|
||||
let tok = lexer.next_token()?;
|
||||
check_loc(&tok)?;
|
||||
assert_eq!(format!("{:?}", tok.1), "1:38:37:49, \"short string\"");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tab() -> Result<()> {
|
||||
let rego = r#" "This string is 35 characters long."`raw string`p"#;
|
||||
let source = Source {
|
||||
file: "case.rego",
|
||||
contents: rego,
|
||||
lines: rego.split('\n').collect(),
|
||||
};
|
||||
|
||||
let mut lexer = Lexer::new(&source);
|
||||
|
||||
// read first tab and string.
|
||||
let tok = lexer.next_token()?;
|
||||
check_loc(&tok)?;
|
||||
assert_eq!(tok.1.col, 6, "tab not accounted correctly.");
|
||||
|
||||
// read raw string which contains tab.
|
||||
let tok = lexer.next_token()?;
|
||||
check_loc(&tok)?;
|
||||
assert_eq!(tok.1.col, 42, "raw string not positioned correctly");
|
||||
|
||||
// read next token (ident)
|
||||
let tok = lexer.next_token()?;
|
||||
check_loc(&tok)?;
|
||||
println!("{:?}", &tok);
|
||||
println!("{}", source.message(tok.1.line, tok.1.col, "", ""));
|
||||
assert_eq!(
|
||||
tok.1.col, 56,
|
||||
"tab within rawstring not accounted correctly"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_line() -> Result<()> {
|
||||
let rego = "";
|
||||
let source = Source {
|
||||
file: "case.rego",
|
||||
contents: rego,
|
||||
lines: rego.split('\n').collect(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
source.message(2, 0, "", ""),
|
||||
"case.rego: invalid line 2 specified"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
60
tests/parser/cases/every/every.yaml
Normal file
60
tests/parser/cases/every/every.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: basic
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords
|
||||
|
||||
y = 8 {
|
||||
every x in [2] {
|
||||
x > 0
|
||||
every a, b in vals { check(a) }
|
||||
}
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: y
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
number: 8
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
every:
|
||||
key: x
|
||||
domain:
|
||||
array:
|
||||
- number: 2
|
||||
query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
boolexpr:
|
||||
op: ">"
|
||||
lhs:
|
||||
var: x
|
||||
rhs:
|
||||
number: 0
|
||||
- literal:
|
||||
every:
|
||||
key: a
|
||||
value: b
|
||||
domain:
|
||||
var: vals
|
||||
query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
call:
|
||||
fcn:
|
||||
var: check
|
||||
params:
|
||||
- var: a
|
||||
|
||||
40
tests/parser/cases/expressions/arithmetic.yaml
Normal file
40
tests/parser/cases/expressions/arithmetic.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: basic
|
||||
rego: |
|
||||
package test
|
||||
|
||||
x = 1 - 2 * 3 / 4 + 2
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
arithexpr:
|
||||
op: +
|
||||
lhs:
|
||||
arithexpr:
|
||||
op: "-"
|
||||
lhs:
|
||||
number: 1
|
||||
rhs:
|
||||
arithexpr:
|
||||
op: "/"
|
||||
lhs:
|
||||
arithexpr:
|
||||
op: "*"
|
||||
lhs:
|
||||
number: 2
|
||||
rhs:
|
||||
number: 3
|
||||
rhs:
|
||||
number: 4
|
||||
rhs:
|
||||
number: 2
|
||||
bodies: []
|
||||
209
tests/parser/cases/expressions/array-compr.yaml
Normal file
209
tests/parser/cases/expressions/array-compr.yaml
Normal file
@@ -0,0 +1,209 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: case1-compr
|
||||
rego: |
|
||||
package test
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = [ x | y ] #, 5] # in {5}, 6]
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
arraycompr:
|
||||
term:
|
||||
var: x
|
||||
query:
|
||||
stmts:
|
||||
- span: y
|
||||
literal:
|
||||
expr:
|
||||
var: y
|
||||
bodies: []
|
||||
|
||||
- note: case2-array
|
||||
rego: |
|
||||
package test
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = [ x | y, 5] # in {5}, 6]
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
array:
|
||||
- binexpr:
|
||||
op: "|"
|
||||
lhs:
|
||||
var: x
|
||||
rhs:
|
||||
var: y
|
||||
- number: 5
|
||||
bodies: []
|
||||
|
||||
- note: case3-compr
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = [ x | y, 5 in {5}] #, 6]
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
arraycompr:
|
||||
term:
|
||||
var: x
|
||||
query:
|
||||
stmts:
|
||||
- span: y, 5 in {5}
|
||||
literal:
|
||||
expr:
|
||||
inexpr:
|
||||
key:
|
||||
var: y
|
||||
value:
|
||||
number: 5
|
||||
collection:
|
||||
set:
|
||||
- number: 5
|
||||
bodies: []
|
||||
|
||||
- note: case4-array
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = [ x | y, 5 in {5}, 6]
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
array:
|
||||
- binexpr:
|
||||
op: "|"
|
||||
lhs:
|
||||
var: x
|
||||
rhs:
|
||||
var: y
|
||||
- inexpr:
|
||||
key:
|
||||
number: 5
|
||||
collection:
|
||||
set:
|
||||
- number: 5
|
||||
- number: 6
|
||||
bodies: []
|
||||
|
||||
- note: case5-array
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = [ x - {10} | y, 5 in {5}] #, 6]
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
array:
|
||||
- binexpr:
|
||||
op: "|"
|
||||
lhs:
|
||||
arithexpr:
|
||||
op: "-"
|
||||
lhs:
|
||||
var: x
|
||||
rhs:
|
||||
set:
|
||||
- number: 10
|
||||
rhs:
|
||||
var: y
|
||||
- inexpr:
|
||||
key:
|
||||
number: 5
|
||||
collection:
|
||||
set:
|
||||
- number: 5
|
||||
bodies: []
|
||||
|
||||
- note: case6-compr
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = [ (x - {10}) | y, 5 in {5}] #, 6]
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
arraycompr:
|
||||
term:
|
||||
arithexpr:
|
||||
op: "-"
|
||||
lhs:
|
||||
var: x
|
||||
rhs:
|
||||
set:
|
||||
- number: 10
|
||||
query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
inexpr:
|
||||
key:
|
||||
var: y
|
||||
value:
|
||||
number: 5
|
||||
collection:
|
||||
set:
|
||||
- number: 5
|
||||
bodies: []
|
||||
|
||||
108
tests/parser/cases/expressions/array.yaml
Normal file
108
tests/parser/cases/expressions/array.yaml
Normal file
@@ -0,0 +1,108 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: all
|
||||
rego: |
|
||||
package test
|
||||
# One item
|
||||
x = {1}
|
||||
|
||||
# Multiple items
|
||||
y = [
|
||||
2.5, "abc", [ 4, `raw`, # Trailing comma
|
||||
],
|
||||
# Empty array
|
||||
[],
|
||||
# Nested empty
|
||||
[[[[[]]]]]
|
||||
]
|
||||
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
span: = {1}
|
||||
op: "="
|
||||
value:
|
||||
set:
|
||||
- number: 1
|
||||
bodies: --skip--
|
||||
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: y
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
array:
|
||||
- number: 2.5
|
||||
- string: abc
|
||||
- array:
|
||||
- number: 4
|
||||
- rawstring: raw
|
||||
- array: []
|
||||
- array:
|
||||
- array:
|
||||
- array:
|
||||
- array:
|
||||
- array: []
|
||||
bodies: []
|
||||
|
||||
- note: trailing-comma
|
||||
rego: |
|
||||
package test
|
||||
x = [1,]
|
||||
y = [1,2
|
||||
,]
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
array:
|
||||
span: "[1,]"
|
||||
values:
|
||||
- number: 1
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: y
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
array:
|
||||
span: "[1,2\n,]"
|
||||
values:
|
||||
- number: 1
|
||||
- number: 2
|
||||
bodies: []
|
||||
|
||||
- note: no-comma
|
||||
rego: |
|
||||
package test
|
||||
x = [1 2]
|
||||
error: expecting `]` while parsing array
|
||||
|
||||
- note: two-trailing-commas
|
||||
rego: |
|
||||
package test
|
||||
x = [1,2,,]
|
||||
error: expecting expression
|
||||
|
||||
- note: unclosed
|
||||
rego: |
|
||||
package test
|
||||
x = [ 1
|
||||
error: expecting `]` while parsing array
|
||||
64
tests/parser/cases/expressions/bin.yaml
Normal file
64
tests/parser/cases/expressions/bin.yaml
Normal file
@@ -0,0 +1,64 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: basic
|
||||
rego: |
|
||||
package test
|
||||
|
||||
# & has higher precedence than |.
|
||||
# If not the following expressions would evaluate to empty set.
|
||||
x = {1, 2, 3} | {2} & {4}
|
||||
y = {2} & {4} | {1, 2, 3}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
binexpr:
|
||||
op: "|"
|
||||
lhs:
|
||||
set:
|
||||
- number: 1
|
||||
- number: 2
|
||||
- number: 3
|
||||
rhs:
|
||||
binexpr:
|
||||
op: "&"
|
||||
lhs:
|
||||
set:
|
||||
- number: 2
|
||||
rhs:
|
||||
set:
|
||||
- number: 4
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: y
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
binexpr:
|
||||
op: "|"
|
||||
rhs:
|
||||
set:
|
||||
- number: 1
|
||||
- number: 2
|
||||
- number: 3
|
||||
lhs:
|
||||
binexpr:
|
||||
op: "&"
|
||||
lhs:
|
||||
set:
|
||||
- number: 2
|
||||
rhs:
|
||||
set:
|
||||
- number: 4
|
||||
bodies: []
|
||||
|
||||
43
tests/parser/cases/expressions/bool.yaml
Normal file
43
tests/parser/cases/expressions/bool.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: basic
|
||||
rego: |
|
||||
package test
|
||||
|
||||
# bool ops have lower precedence than arithmetic operators
|
||||
x = 1 + 2 > 3 - 2
|
||||
|
||||
# TODO: lock down
|
||||
# different types against object
|
||||
# different types against set
|
||||
# strings etc
|
||||
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
boolexpr:
|
||||
op: ">"
|
||||
lhs:
|
||||
arithexpr:
|
||||
op: "+"
|
||||
lhs:
|
||||
number: 1
|
||||
rhs:
|
||||
number: 2
|
||||
rhs:
|
||||
arithexpr:
|
||||
op: "-"
|
||||
lhs:
|
||||
number: 3
|
||||
rhs:
|
||||
number: 2
|
||||
|
||||
bodies: []
|
||||
48
tests/parser/cases/expressions/call.yaml
Normal file
48
tests/parser/cases/expressions/call.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: basic
|
||||
rego: |
|
||||
package test
|
||||
|
||||
# A call in rule-ref. This creates an object.
|
||||
deny[sprintf("Hello %v", ["world"])] = 1
|
||||
|
||||
# Trailing comma
|
||||
x = inc(5,)
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
refbrack:
|
||||
refr:
|
||||
var: deny
|
||||
index:
|
||||
call:
|
||||
fcn:
|
||||
var: sprintf
|
||||
params:
|
||||
- string: "Hello %v"
|
||||
- array:
|
||||
- string: "world"
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
number: 1
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
call:
|
||||
fcn:
|
||||
var: inc
|
||||
params:
|
||||
- number: 5
|
||||
bodies: []
|
||||
59
tests/parser/cases/expressions/in.yaml
Normal file
59
tests/parser/cases/expressions/in.yaml
Normal file
@@ -0,0 +1,59 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: basic
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
|
||||
x = 5 in [4, 5]
|
||||
|
||||
# in-exprs are left associative and ahve lower-precedence than bin-expr.
|
||||
# The following will be parsed as
|
||||
# (5 in [4, 5]) in (set() | {true})
|
||||
z = 5 in [4, 5] in set() | {true}
|
||||
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
inexpr:
|
||||
key:
|
||||
number: 5
|
||||
collection:
|
||||
array:
|
||||
- number: 4
|
||||
- number: 5
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
inexpr:
|
||||
key:
|
||||
inexpr:
|
||||
key:
|
||||
number: 5
|
||||
collection:
|
||||
array:
|
||||
- number: 4
|
||||
- number: 5
|
||||
collection:
|
||||
binexpr:
|
||||
op: "|"
|
||||
lhs:
|
||||
set: []
|
||||
rhs:
|
||||
set:
|
||||
- true
|
||||
bodies: []
|
||||
53
tests/parser/cases/expressions/membership.yaml
Normal file
53
tests/parser/cases/expressions/membership.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: basic
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
|
||||
# key, value in collection
|
||||
x = 0, 5 in [5]
|
||||
|
||||
# Chained in-exprs
|
||||
y = 0, 5 in c in d
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
inexpr:
|
||||
key:
|
||||
number: 0
|
||||
value:
|
||||
number: 5
|
||||
collection:
|
||||
array:
|
||||
- number: 5
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: y
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
inexpr:
|
||||
key:
|
||||
inexpr:
|
||||
key:
|
||||
number: 0
|
||||
value:
|
||||
number: 5
|
||||
collection:
|
||||
var: c
|
||||
collection:
|
||||
var: d
|
||||
bodies: []
|
||||
|
||||
163
tests/parser/cases/expressions/object.yaml
Normal file
163
tests/parser/cases/expressions/object.yaml
Normal file
@@ -0,0 +1,163 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: all
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
|
||||
# Empty object
|
||||
x = {}
|
||||
|
||||
# Single field
|
||||
y = {
|
||||
"a" : 5
|
||||
}
|
||||
|
||||
# Multiple fields
|
||||
z = {
|
||||
"a" : 5,
|
||||
"b" : [ 1, 2, 3 ],
|
||||
"c" : { 4, 5, 6 },
|
||||
"d" : {
|
||||
"a" : 5,
|
||||
"b" : set()
|
||||
}
|
||||
# array as key
|
||||
, [1,
|
||||
2,
|
||||
3] : 4,
|
||||
|
||||
# set as key
|
||||
{ 1 } : 2,
|
||||
|
||||
# Object as key
|
||||
{
|
||||
"a" : 1,
|
||||
"b" : 2, # Trailing comma
|
||||
} : `hello,
|
||||
world`
|
||||
|
||||
# Null, boolean
|
||||
, null: false,
|
||||
true : true,
|
||||
|
||||
# Only single var in is supported as value
|
||||
"p" : "q", "r" in "d" : "e"
|
||||
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
span: x = {}
|
||||
head:
|
||||
compr:
|
||||
span: x = {}
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
span: = {}
|
||||
op: =
|
||||
value:
|
||||
object:
|
||||
fields: []
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: y
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
object:
|
||||
fields:
|
||||
- key:
|
||||
string: a
|
||||
value:
|
||||
number: 5
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
object:
|
||||
fields:
|
||||
- key:
|
||||
string: a
|
||||
value:
|
||||
number: 5
|
||||
- key:
|
||||
string: b
|
||||
value:
|
||||
array:
|
||||
- number: 1
|
||||
- number: 2
|
||||
- number: 3
|
||||
- key:
|
||||
string: c
|
||||
value:
|
||||
set:
|
||||
- number: 4
|
||||
- number: 5
|
||||
- number : 6
|
||||
- key:
|
||||
string: d
|
||||
value:
|
||||
object:
|
||||
fields:
|
||||
- key:
|
||||
string: a
|
||||
value:
|
||||
number: 5
|
||||
- key:
|
||||
string: b
|
||||
value:
|
||||
set: []
|
||||
- key:
|
||||
array:
|
||||
- number: 1
|
||||
- number: 2
|
||||
- number: 3
|
||||
value:
|
||||
number: 4
|
||||
- key:
|
||||
set:
|
||||
- number: 1
|
||||
value:
|
||||
number: 2
|
||||
- key:
|
||||
object:
|
||||
fields:
|
||||
- key:
|
||||
string: a
|
||||
value:
|
||||
number: 1
|
||||
- key:
|
||||
string: b
|
||||
value:
|
||||
number: 2
|
||||
value:
|
||||
rawstring: "hello,\n world"
|
||||
- key: null
|
||||
value: false
|
||||
- key: true
|
||||
value: true
|
||||
- key:
|
||||
string: p
|
||||
value:
|
||||
string: q
|
||||
- key:
|
||||
inexpr:
|
||||
key:
|
||||
string: r
|
||||
collection:
|
||||
string: d
|
||||
value:
|
||||
string: e
|
||||
|
||||
bodies: []
|
||||
209
tests/parser/cases/expressions/set-compr.yaml
Normal file
209
tests/parser/cases/expressions/set-compr.yaml
Normal file
@@ -0,0 +1,209 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: case1-compr
|
||||
rego: |
|
||||
package test
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = { x | y } #, 5} # in {5}, 6}
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
setcompr:
|
||||
term:
|
||||
var: x
|
||||
query:
|
||||
stmts:
|
||||
- span: y
|
||||
literal:
|
||||
expr:
|
||||
var: y
|
||||
bodies: []
|
||||
|
||||
- note: case2-set
|
||||
rego: |
|
||||
package test
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = { x | y, 5} # in {5}, 6}
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
set:
|
||||
- binexpr:
|
||||
op: "|"
|
||||
lhs:
|
||||
var: x
|
||||
rhs:
|
||||
var: y
|
||||
- number: 5
|
||||
bodies: []
|
||||
|
||||
- note: case3-compr
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = { x | y, 5 in {5}} #, 6}
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
setcompr:
|
||||
term:
|
||||
var: x
|
||||
query:
|
||||
stmts:
|
||||
- span: y, 5 in {5}
|
||||
literal:
|
||||
expr:
|
||||
inexpr:
|
||||
key:
|
||||
var: y
|
||||
value:
|
||||
number: 5
|
||||
collection:
|
||||
set:
|
||||
- number: 5
|
||||
bodies: []
|
||||
|
||||
- note: case4-set
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = { x | y, 5 in {5}, 6}
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
set:
|
||||
- binexpr:
|
||||
op: "|"
|
||||
lhs:
|
||||
var: x
|
||||
rhs:
|
||||
var: y
|
||||
- inexpr:
|
||||
key:
|
||||
number: 5
|
||||
collection:
|
||||
set:
|
||||
- number: 5
|
||||
- number: 6
|
||||
bodies: []
|
||||
|
||||
- note: case5-set
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = { x - {10} | y, 5 in {5}} #, 6}
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
set:
|
||||
- binexpr:
|
||||
op: "|"
|
||||
lhs:
|
||||
arithexpr:
|
||||
op: "-"
|
||||
lhs:
|
||||
var: x
|
||||
rhs:
|
||||
set:
|
||||
- number: 10
|
||||
rhs:
|
||||
var: y
|
||||
- inexpr:
|
||||
key:
|
||||
number: 5
|
||||
collection:
|
||||
set:
|
||||
- number: 5
|
||||
bodies: []
|
||||
|
||||
- note: case6-compr
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = {1}
|
||||
y = {2}
|
||||
z = { (x - {10}) | y, 5 in {5}} #, 6}
|
||||
policy:
|
||||
- --skip--
|
||||
- --skip--
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: z
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
setcompr:
|
||||
term:
|
||||
arithexpr:
|
||||
op: "-"
|
||||
lhs:
|
||||
var: x
|
||||
rhs:
|
||||
set:
|
||||
- number: 10
|
||||
query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
inexpr:
|
||||
key:
|
||||
var: y
|
||||
value:
|
||||
number: 5
|
||||
collection:
|
||||
set:
|
||||
- number: 5
|
||||
bodies: []
|
||||
|
||||
112
tests/parser/cases/expressions/set.yaml
Normal file
112
tests/parser/cases/expressions/set.yaml
Normal file
@@ -0,0 +1,112 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: all
|
||||
rego: |
|
||||
package test
|
||||
# One item
|
||||
x = {1}
|
||||
|
||||
# Multiple items
|
||||
y = {
|
||||
2.5, "abc", { 4, `raw`
|
||||
},
|
||||
# Empty set has a special syntax
|
||||
set( ),
|
||||
# Nested empty
|
||||
{{{{{}}}}}
|
||||
}
|
||||
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
span: = {1}
|
||||
op: "="
|
||||
value:
|
||||
set:
|
||||
- number: 1
|
||||
bodies: --skip--
|
||||
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: y
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
set:
|
||||
- number: 2.5
|
||||
- string: abc
|
||||
- set:
|
||||
- number: 4
|
||||
- rawstring: raw
|
||||
- set: []
|
||||
- set:
|
||||
- set:
|
||||
- set:
|
||||
- set:
|
||||
- object:
|
||||
span: "{}"
|
||||
fields: []
|
||||
bodies: []
|
||||
|
||||
- note: trailing-comma
|
||||
rego: |
|
||||
package test
|
||||
x = {1,}
|
||||
y = {1,2
|
||||
,}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
set:
|
||||
span: "{1,}"
|
||||
values:
|
||||
- number: 1
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: y
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
set:
|
||||
span: "{1,2\n,}"
|
||||
values:
|
||||
- number: 1
|
||||
- number: 2
|
||||
bodies: []
|
||||
|
||||
|
||||
- note: no-comma
|
||||
rego: |
|
||||
package test
|
||||
x = {1 2}
|
||||
error: expecting `}` while parsing set
|
||||
|
||||
- note: two-trailing-commas
|
||||
rego: |
|
||||
package test
|
||||
x = {1,2,,}
|
||||
error: expecting expression
|
||||
|
||||
- note: unclosed
|
||||
rego: |
|
||||
package test
|
||||
x = { 1
|
||||
error: expecting `}` while parsing set
|
||||
|
||||
154
tests/parser/cases/import/future.yaml
Normal file
154
tests/parser/cases/import/future.yaml
Normal file
@@ -0,0 +1,154 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: namespace
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords
|
||||
imports:
|
||||
- span: import future.keywords
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords
|
||||
refr:
|
||||
var: future
|
||||
field: keywords
|
||||
|
||||
- note: all
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.contains import future.keywords.every
|
||||
import future.keywords.if
|
||||
import
|
||||
future.keywords.in
|
||||
imports:
|
||||
- span: import future.keywords.contains
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords.contains
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords
|
||||
refr:
|
||||
var: future
|
||||
field: keywords
|
||||
field: contains
|
||||
- span: import future.keywords.every
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords.every
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords
|
||||
refr:
|
||||
var: future
|
||||
field: keywords
|
||||
field: every
|
||||
- span: import future.keywords.if
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords.if
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords
|
||||
refr:
|
||||
var: future
|
||||
field: keywords
|
||||
field: if
|
||||
- span: "import\nfuture.keywords.in"
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords.in
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords
|
||||
refr:
|
||||
var: future
|
||||
field: keywords
|
||||
field: in
|
||||
|
||||
- note: bracket
|
||||
rego: |
|
||||
package test
|
||||
import future["keywords"]["contains"] import future.keywords["every"]
|
||||
import
|
||||
future["keywords"].if
|
||||
imports:
|
||||
- span: import future["keywords"]["contains"]
|
||||
refr:
|
||||
refbrack:
|
||||
span: future["keywords"]["contains"]
|
||||
refr:
|
||||
refbrack:
|
||||
span: future["keywords"]
|
||||
refr:
|
||||
var: future
|
||||
index:
|
||||
string: keywords
|
||||
index:
|
||||
string: contains
|
||||
- span: import future.keywords["every"]
|
||||
refr:
|
||||
refbrack:
|
||||
span: future.keywords["every"]
|
||||
refr:
|
||||
refdot:
|
||||
span: future.keywords
|
||||
refr:
|
||||
var: future
|
||||
field: keywords
|
||||
index:
|
||||
string: every
|
||||
- span: "import\nfuture[\"keywords\"].if"
|
||||
refr:
|
||||
refdot:
|
||||
span: future["keywords"].if
|
||||
refr:
|
||||
refbrack:
|
||||
span: future["keywords"]
|
||||
refr:
|
||||
var: future
|
||||
index:
|
||||
string: keywords
|
||||
field: if
|
||||
|
||||
- note: as
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in as on
|
||||
error: "`future` imports cannot be aliased"
|
||||
|
||||
- note: shadow
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords
|
||||
import future["keywords"]
|
||||
error: "this import shadows previous import of `contains`"
|
||||
|
||||
- note: shadow/1
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords
|
||||
import future.keywords.if
|
||||
error: "this import shadows previous import of `if`"
|
||||
|
||||
- note: shadow/2
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.if
|
||||
import future.keywords
|
||||
error: "this import shadows previous import of `if`"
|
||||
|
||||
- note: in-as-var
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.if
|
||||
in = 5
|
||||
|
||||
- note: in-as-var-imported
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
in = 5
|
||||
error: unexpected keyword `in`
|
||||
308
tests/parser/cases/import/import.yaml
Normal file
308
tests/parser/cases/import/import.yaml
Normal file
@@ -0,0 +1,308 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: data
|
||||
rego: |
|
||||
package test
|
||||
import data
|
||||
import input
|
||||
imports:
|
||||
- span: import data
|
||||
refr:
|
||||
var: data
|
||||
- span: import input
|
||||
refr:
|
||||
var: input
|
||||
|
||||
- note: input
|
||||
rego: |
|
||||
package test
|
||||
import input
|
||||
imports:
|
||||
- span: import input
|
||||
refr:
|
||||
var: input
|
||||
|
||||
- note: dot
|
||||
rego: |
|
||||
package test
|
||||
import input.a
|
||||
import data.b
|
||||
imports:
|
||||
- span: import input.a
|
||||
refr:
|
||||
refdot:
|
||||
refr:
|
||||
var: input
|
||||
field: a
|
||||
- span: import data.b
|
||||
refr:
|
||||
refdot:
|
||||
refr:
|
||||
var: data
|
||||
field: b
|
||||
|
||||
- note: bracket
|
||||
rego: |
|
||||
package test
|
||||
import input["a"]
|
||||
import data["b"]
|
||||
imports:
|
||||
- span: import input["a"]
|
||||
refr:
|
||||
refbrack:
|
||||
refr:
|
||||
var: input
|
||||
index:
|
||||
string: a
|
||||
- span: import data["b"]
|
||||
refr:
|
||||
refbrack:
|
||||
refr:
|
||||
var: data
|
||||
index:
|
||||
string: b
|
||||
|
||||
- note: multi-dot
|
||||
rego: |
|
||||
package test
|
||||
import input.a.b
|
||||
import data.c.d
|
||||
imports:
|
||||
- span: import input.a.b
|
||||
refr:
|
||||
refdot:
|
||||
span: input.a.b
|
||||
refr:
|
||||
refdot:
|
||||
span: input.a
|
||||
refr:
|
||||
var: input
|
||||
field: a
|
||||
field: b
|
||||
- span: import data.c.d
|
||||
refr:
|
||||
refdot:
|
||||
span: data.c.d
|
||||
refr:
|
||||
refdot:
|
||||
span: data.c
|
||||
refr:
|
||||
var: data
|
||||
field: c
|
||||
field: d
|
||||
policy: []
|
||||
|
||||
|
||||
- note: complex
|
||||
rego: |
|
||||
package test
|
||||
import input["b.c"].d["e.f"].g
|
||||
import data.a["b.c"].d["e.f"]
|
||||
package: --skip--
|
||||
imports:
|
||||
- span: import input["b.c"].d["e.f"].g
|
||||
refr:
|
||||
refdot:
|
||||
span: input["b.c"].d["e.f"].g
|
||||
refr:
|
||||
refbrack:
|
||||
span: input["b.c"].d["e.f"]
|
||||
refr:
|
||||
refdot:
|
||||
span: input["b.c"].d
|
||||
refr:
|
||||
refbrack:
|
||||
span: input["b.c"]
|
||||
refr:
|
||||
var: input
|
||||
index:
|
||||
string: b.c
|
||||
field: d
|
||||
index:
|
||||
string: e.f
|
||||
field: g
|
||||
- span: import data.a["b.c"].d["e.f"]
|
||||
refr:
|
||||
refbrack:
|
||||
span: data.a["b.c"].d["e.f"]
|
||||
refr:
|
||||
refdot:
|
||||
span: data.a["b.c"].d
|
||||
refr:
|
||||
refbrack:
|
||||
span: data.a["b.c"]
|
||||
refr:
|
||||
refdot:
|
||||
span: data.a
|
||||
refr:
|
||||
var: data
|
||||
field: a
|
||||
index:
|
||||
string: b.c
|
||||
field: d
|
||||
index:
|
||||
string: e.f
|
||||
|
||||
- note: same-line
|
||||
rego: package test import input.a["b"] import data["c"].d
|
||||
package:
|
||||
span: package test
|
||||
refr:
|
||||
var: test
|
||||
imports:
|
||||
- span: import input.a["b"]
|
||||
refr:
|
||||
refbrack:
|
||||
span: input.a["b"]
|
||||
refr:
|
||||
refdot:
|
||||
span: input.a
|
||||
refr:
|
||||
var: input
|
||||
field: a
|
||||
index:
|
||||
string: b
|
||||
- span: import data["c"].d
|
||||
refr:
|
||||
refdot:
|
||||
span: data["c"].d
|
||||
refr:
|
||||
refbrack:
|
||||
span: data["c"]
|
||||
refr:
|
||||
var: data
|
||||
index:
|
||||
string: c
|
||||
field: d
|
||||
|
||||
- note: as
|
||||
rego: |
|
||||
package test
|
||||
import input.x as y
|
||||
imports:
|
||||
- span: import input.x as y
|
||||
refr:
|
||||
refdot:
|
||||
span: input.x
|
||||
refr:
|
||||
var: input
|
||||
field: x
|
||||
as: y
|
||||
|
||||
- note: as/newline
|
||||
rego: |
|
||||
package test
|
||||
import
|
||||
input.x
|
||||
as
|
||||
y
|
||||
imports:
|
||||
- span: "import\ninput.x\nas\ny"
|
||||
refr:
|
||||
refdot:
|
||||
span: input.x
|
||||
refr:
|
||||
var: input
|
||||
field: x
|
||||
as: y
|
||||
|
||||
- note: missing-ref
|
||||
rego: |
|
||||
package test
|
||||
import ( a)
|
||||
error: expecting identifier
|
||||
|
||||
- note: missing-ref-1
|
||||
rego: |
|
||||
package test
|
||||
import ["a"]
|
||||
error: expecting identifier
|
||||
|
||||
- note: invalid-beginning
|
||||
rego: |
|
||||
package test
|
||||
import foo
|
||||
error: "import path must begin with one of: {data, future, input}"
|
||||
|
||||
- note: invalid-beginning-1
|
||||
rego: |
|
||||
package test
|
||||
import foo.bar
|
||||
error: "import path must begin with one of: {data, future, input}"
|
||||
|
||||
- note: missing-field-1
|
||||
rego: |
|
||||
package test
|
||||
import data.a.
|
||||
error: expecting identifier
|
||||
|
||||
- note: missing-field-2
|
||||
rego: |
|
||||
package test
|
||||
import data.a.b.
|
||||
error: expecting identifier
|
||||
|
||||
- note: space-after-dot
|
||||
rego: |
|
||||
package test
|
||||
import input. a
|
||||
error: invalid whitespace between . and identifier
|
||||
|
||||
- note: space-before-dot
|
||||
rego: |
|
||||
package test
|
||||
import input .a.b
|
||||
error: invalid whitespace before .
|
||||
|
||||
- note: space-after-lbracket
|
||||
rego: package test import a ["b"]
|
||||
error: invalid whitespace before [
|
||||
|
||||
- note: non-string-index
|
||||
rego: package test import a[1]
|
||||
error: expected string
|
||||
|
||||
- note: shadow
|
||||
rego:
|
||||
package test
|
||||
import data.x import data.y import data["x"]
|
||||
error: import shadows following import defined earlier
|
||||
|
||||
- note: keyword/else
|
||||
rego:
|
||||
package test
|
||||
import data.x as else
|
||||
error: "unexpected keyword `else`"
|
||||
|
||||
- note: keyword/as
|
||||
rego:
|
||||
package test
|
||||
import data.x as as
|
||||
error: "unexpected keyword `as`"
|
||||
|
||||
- note: as/_
|
||||
rego:
|
||||
package test
|
||||
import data.x as _
|
||||
error: "`_` cannot be used as alias"
|
||||
|
||||
- note: as/_
|
||||
rego:
|
||||
package test
|
||||
import data.x as 1
|
||||
error: expecting identifier
|
||||
|
||||
- note: as/eof
|
||||
rego:
|
||||
package test
|
||||
import data.x as
|
||||
error: expecting identifier
|
||||
|
||||
- note: as/multiple
|
||||
rego:
|
||||
package test
|
||||
import data.x as y
|
||||
import data.y as y
|
||||
error: import shadows following import defined earlier
|
||||
129
tests/parser/cases/package/package.yaml
Normal file
129
tests/parser/cases/package/package.yaml
Normal file
@@ -0,0 +1,129 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: single-char
|
||||
rego: package a
|
||||
package:
|
||||
span: package a
|
||||
refr:
|
||||
var: a
|
||||
|
||||
- note: simple
|
||||
rego: package test
|
||||
package:
|
||||
span: package test
|
||||
refr:
|
||||
var: test
|
||||
|
||||
- note: dot
|
||||
rego: package a.b
|
||||
package:
|
||||
span: package a.b
|
||||
refr:
|
||||
refdot:
|
||||
span: a.b
|
||||
refr:
|
||||
var: a
|
||||
field: b
|
||||
|
||||
- note: multi-dot
|
||||
rego: package a.b.c
|
||||
package:
|
||||
span: package a.b.c
|
||||
refr:
|
||||
refdot:
|
||||
span: a.b.c
|
||||
refr:
|
||||
refdot:
|
||||
span: a.b
|
||||
refr:
|
||||
var: a
|
||||
field: b
|
||||
field: c
|
||||
|
||||
- note: bracket
|
||||
rego: package a["b"]
|
||||
package:
|
||||
span: package a["b"]
|
||||
refr:
|
||||
refbrack:
|
||||
span: a["b"]
|
||||
refr:
|
||||
var: a
|
||||
index:
|
||||
string: b
|
||||
|
||||
- note: multi-bracket
|
||||
rego: package a["b"]["c.d"]
|
||||
package:
|
||||
span: package a["b"]["c.d"]
|
||||
refr:
|
||||
refbrack:
|
||||
span: a["b"]["c.d"]
|
||||
refr:
|
||||
refbrack:
|
||||
span: a["b"]
|
||||
refr:
|
||||
var: a
|
||||
index:
|
||||
string: b
|
||||
index:
|
||||
string: c.d
|
||||
|
||||
- note: complex
|
||||
rego: package a["b.c"].d["e.f"].g
|
||||
package:
|
||||
span: package a["b.c"].d["e.f"].g
|
||||
refr:
|
||||
refdot:
|
||||
span: a["b.c"].d["e.f"].g
|
||||
refr:
|
||||
refbrack:
|
||||
span: a["b.c"].d["e.f"]
|
||||
refr:
|
||||
refdot:
|
||||
span: a["b.c"].d
|
||||
refr:
|
||||
refbrack:
|
||||
span: a["b.c"]
|
||||
refr:
|
||||
var: a
|
||||
index:
|
||||
string: "b.c"
|
||||
field: d
|
||||
index:
|
||||
string: e.f
|
||||
field: g
|
||||
|
||||
- note: missing-package-keyword
|
||||
rego: packge a
|
||||
error: expecting `package`
|
||||
|
||||
- note: missing-var
|
||||
rego: package 5
|
||||
error: expecting identifier
|
||||
|
||||
- note: missing-var-1
|
||||
rego: package (
|
||||
error: expecting identifier
|
||||
|
||||
- note: missing-field
|
||||
rego: package a.b.
|
||||
error: expecting identifier
|
||||
|
||||
- note: space-after-dot
|
||||
rego: package a. b
|
||||
error: invalid whitespace between . and identifier
|
||||
|
||||
- note: space-before-dot
|
||||
rego: package a .b
|
||||
error: invalid whitespace before .
|
||||
|
||||
- note: space-after-lbracket
|
||||
rego: package a ["b"]
|
||||
error: invalid whitespace before [
|
||||
|
||||
- note: non-string-index
|
||||
rego: package a[1]
|
||||
error: expected string
|
||||
44
tests/parser/cases/rules/basic.yaml
Normal file
44
tests/parser/cases/rules/basic.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: same-line-no-body
|
||||
rego: |
|
||||
package test
|
||||
add(x, y) := 5 sub(x, y) = 5
|
||||
package: --skip--
|
||||
imports:
|
||||
policy:
|
||||
- spec:
|
||||
span: add(x, y) := 5
|
||||
head:
|
||||
func:
|
||||
span: add(x, y) := 5
|
||||
refr:
|
||||
var: add
|
||||
args:
|
||||
- var: x
|
||||
- var: y
|
||||
assign:
|
||||
span: := 5
|
||||
op: :=
|
||||
value:
|
||||
number: 5
|
||||
bodies: []
|
||||
- spec:
|
||||
span: sub(x, y) = 5
|
||||
head:
|
||||
func:
|
||||
span: sub(x, y) = 5
|
||||
refr:
|
||||
var: sub
|
||||
args:
|
||||
- var: x
|
||||
- var: y
|
||||
assign:
|
||||
span: = 5
|
||||
op: =
|
||||
value:
|
||||
number: 5
|
||||
bodies: []
|
||||
|
||||
250
tests/parser/cases/rules/else.yaml
Normal file
250
tests/parser/cases/rules/else.yaml
Normal file
@@ -0,0 +1,250 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: no-query
|
||||
rego: |
|
||||
package test
|
||||
|
||||
x = 10 else {
|
||||
true
|
||||
}
|
||||
error: unexpected keyword `else`
|
||||
|
||||
- note: no-query
|
||||
rego: |
|
||||
package test
|
||||
|
||||
x = 10 {
|
||||
false
|
||||
} {
|
||||
true
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
number: 10
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
false
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- note: no-else
|
||||
rego: |
|
||||
package test
|
||||
|
||||
x = 10 {
|
||||
false
|
||||
} {
|
||||
true
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
number: 10
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
false
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- note: if-no-else
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.if
|
||||
|
||||
x = 10 if {
|
||||
false
|
||||
} {
|
||||
true
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
number: 10
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
false
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- note: rule-named-if
|
||||
rego: |
|
||||
package test
|
||||
|
||||
x = 10 if {
|
||||
false
|
||||
} {
|
||||
true
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
number: 10
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: if
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
false
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- note: query-else
|
||||
rego: |
|
||||
package test
|
||||
|
||||
x = 10 {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
number: 10
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
false
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- note: if-literal-else
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.if
|
||||
|
||||
x = 10 if 1 < 0 else {
|
||||
true
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
number: 10
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
boolexpr:
|
||||
op: "<"
|
||||
lhs:
|
||||
number: 1
|
||||
rhs:
|
||||
number: 0
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- note: if-literal-else-assign
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.if
|
||||
|
||||
# This will evaluate to 10
|
||||
x = 10 if 1 < 0 else := 20 {
|
||||
true
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: "="
|
||||
value:
|
||||
number: 10
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
boolexpr:
|
||||
op: "<"
|
||||
lhs:
|
||||
number: 1
|
||||
rhs:
|
||||
number: 0
|
||||
- assign:
|
||||
op: ":="
|
||||
value:
|
||||
number: 20
|
||||
query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
135
tests/parser/cases/rules/set.yaml
Normal file
135
tests/parser/cases/rules/set.yaml
Normal file
@@ -0,0 +1,135 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: contains
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords
|
||||
|
||||
deny.a contains 0, "bar" in ["bar1"] if true
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
set:
|
||||
refr:
|
||||
refdot:
|
||||
refr:
|
||||
var: deny
|
||||
field: a
|
||||
key:
|
||||
inexpr:
|
||||
key:
|
||||
number: 0
|
||||
value:
|
||||
string: bar
|
||||
collection:
|
||||
array:
|
||||
- string: bar1
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
- note: old-syntax
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.if
|
||||
|
||||
# The following are not sets
|
||||
x1 if true
|
||||
x2 { true }
|
||||
|
||||
# The following are sets
|
||||
y.a { true }
|
||||
y["b"] { true }
|
||||
|
||||
# The following are not sets
|
||||
z.a if { true }
|
||||
z["b"] if { true }
|
||||
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x1
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
var: x2
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- spec:
|
||||
head:
|
||||
set:
|
||||
refr:
|
||||
refdot:
|
||||
refr:
|
||||
var: y
|
||||
field: a
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- spec:
|
||||
head:
|
||||
set:
|
||||
refr:
|
||||
var: y
|
||||
key:
|
||||
string: b
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
refdot:
|
||||
refr:
|
||||
var: z
|
||||
field: a
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
refr:
|
||||
refbrack:
|
||||
refr:
|
||||
var: z
|
||||
index:
|
||||
string: b
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
expr:
|
||||
true
|
||||
|
||||
226
tests/parser/cases/some/some.in.yaml
Normal file
226
tests/parser/cases/some/some.in.yaml
Normal file
@@ -0,0 +1,226 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: all
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
|
||||
x = y {
|
||||
# Empty set. This evaluates to false.
|
||||
some a in {1}
|
||||
|
||||
# Key, value combo
|
||||
some a
|
||||
, b in r
|
||||
|
||||
# Key, value combo can be non-vars
|
||||
some 5, x in array
|
||||
|
||||
some "hello", "world" in map
|
||||
|
||||
some p, q in { r |
|
||||
some a, b in d
|
||||
}
|
||||
}
|
||||
|
||||
policy:
|
||||
- spec:
|
||||
head: --skip--
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- span: some a in {1}
|
||||
literal:
|
||||
some-decl:
|
||||
key:
|
||||
var: a
|
||||
collection:
|
||||
set:
|
||||
- number: 1
|
||||
- span: "some a\n , b in r"
|
||||
literal:
|
||||
some-decl:
|
||||
key:
|
||||
var: a
|
||||
value:
|
||||
var: b
|
||||
collection:
|
||||
var: r
|
||||
- span: "some 5, x in array"
|
||||
literal:
|
||||
some-decl:
|
||||
key:
|
||||
number: 5
|
||||
value:
|
||||
var: x
|
||||
collection:
|
||||
var: array
|
||||
- span: "some \"hello\", \"world\" in map"
|
||||
literal:
|
||||
some-decl:
|
||||
key:
|
||||
string: hello
|
||||
value:
|
||||
string: world
|
||||
collection:
|
||||
var: map
|
||||
- literal:
|
||||
some-decl:
|
||||
key:
|
||||
var: p
|
||||
value:
|
||||
var: q
|
||||
collection:
|
||||
setcompr:
|
||||
term:
|
||||
var: r
|
||||
query:
|
||||
stmts:
|
||||
- literal:
|
||||
some-decl:
|
||||
key:
|
||||
var: a
|
||||
value:
|
||||
var: b
|
||||
collection:
|
||||
var: d
|
||||
|
||||
|
||||
- note: unimported-in
|
||||
rego: |
|
||||
package test
|
||||
x = y {
|
||||
some a in b
|
||||
}
|
||||
error: expecting `}` while parsing query
|
||||
|
||||
- note: more-refs
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = y {
|
||||
some a, b, c in d
|
||||
}
|
||||
error: encountered `c` while expecting `in`
|
||||
|
||||
- note: eof
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = y {
|
||||
some a, b in
|
||||
error: expecting expression
|
||||
|
||||
- note: missing-expr
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = y {
|
||||
some a, b in
|
||||
}
|
||||
error: expecting expression
|
||||
|
||||
- note: missing-comma
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
x = y {
|
||||
some a b in c
|
||||
}
|
||||
error: expecting `}` while parsing query
|
||||
|
||||
- note: same-line
|
||||
rego: |
|
||||
package test
|
||||
x = y{
|
||||
some a b in {4, 5}
|
||||
[1, 2, 3][a] == 3
|
||||
y = a
|
||||
}
|
||||
error: expecting `}` while parsing query
|
||||
|
||||
- note: multi-line-parsed as membership
|
||||
rego: |
|
||||
package test
|
||||
import future.keywords.in
|
||||
b := 5
|
||||
x = y{
|
||||
some a
|
||||
b in {4, 5}
|
||||
# The following [ starting a line ought to get
|
||||
# parsed as a literal statement and not raise errors
|
||||
# regarding gap from previous refr.
|
||||
[1, 2, 3][a] == 3
|
||||
y = a
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
span: b := 5
|
||||
refr:
|
||||
var: b
|
||||
assign:
|
||||
op: :=
|
||||
value:
|
||||
number: 5
|
||||
bodies: []
|
||||
- spec:
|
||||
head:
|
||||
compr:
|
||||
span: x = y
|
||||
refr:
|
||||
var: x
|
||||
assign:
|
||||
op: =
|
||||
value:
|
||||
var: y
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- literal:
|
||||
some-vars:
|
||||
span: some a
|
||||
vars:
|
||||
- a
|
||||
- literal:
|
||||
expr:
|
||||
inexpr:
|
||||
span: b in {4, 5}
|
||||
key:
|
||||
var: b
|
||||
collection:
|
||||
set:
|
||||
- number: 4
|
||||
- number: 5
|
||||
- literal:
|
||||
expr:
|
||||
boolexpr:
|
||||
span: "[1, 2, 3][a] == 3"
|
||||
op: ==
|
||||
lhs:
|
||||
refbrack:
|
||||
span: "[1, 2, 3][a]"
|
||||
refr:
|
||||
array:
|
||||
- number: 1
|
||||
- number: 2
|
||||
- number: 3
|
||||
index:
|
||||
var: a
|
||||
rhs:
|
||||
number: 3
|
||||
- literal:
|
||||
expr:
|
||||
assignexpr:
|
||||
span: y = a
|
||||
op: =
|
||||
lhs:
|
||||
var: y
|
||||
rhs:
|
||||
var: a
|
||||
|
||||
|
||||
|
||||
74
tests/parser/cases/some/some.vars.yaml
Normal file
74
tests/parser/cases/some/some.vars.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
# Copyright (c) Rego-Rs Authors.
|
||||
# Licensed under the Apache 2.0 license.
|
||||
|
||||
cases:
|
||||
- note: single-multi-vars
|
||||
rego: |
|
||||
package test
|
||||
x = y {
|
||||
# Single var
|
||||
some a
|
||||
# Multiple var. With and without spaces.
|
||||
some
|
||||
d,e,
|
||||
f
|
||||
}
|
||||
policy:
|
||||
- spec:
|
||||
head: --skip--
|
||||
bodies:
|
||||
- query:
|
||||
stmts:
|
||||
- span: some a
|
||||
literal:
|
||||
some-vars:
|
||||
span: some a
|
||||
vars:
|
||||
- a
|
||||
- span: "some\n d,e,\n f"
|
||||
literal:
|
||||
some-vars:
|
||||
span: "some\n d,e,\n f"
|
||||
vars: [d, e, f]
|
||||
|
||||
- note: same-line-error
|
||||
rego: |
|
||||
package test
|
||||
x = y {
|
||||
some a some b
|
||||
}
|
||||
error: expecting `}` while parsing query
|
||||
|
||||
- note: keyword
|
||||
rego: |
|
||||
package test
|
||||
x = y {
|
||||
some as
|
||||
}
|
||||
error: unexpected keyword `as`
|
||||
|
||||
- note: top-level
|
||||
rego: |
|
||||
package test
|
||||
x = some y
|
||||
error: unexpected keyword `some`
|
||||
|
||||
- note: top-level1
|
||||
rego: |
|
||||
package test
|
||||
some y
|
||||
error: unexpected keyword `some`
|
||||
|
||||
- note: some-in-set
|
||||
rego: |
|
||||
package test
|
||||
x = { some y }
|
||||
error: unexpected keyword `some`
|
||||
|
||||
- note: non-var
|
||||
rego: |
|
||||
package test
|
||||
x = y {
|
||||
some a, 5
|
||||
}
|
||||
error: encountered `5` while expecting identifier
|
||||
795
tests/parser/mod.rs
Normal file
795
tests/parser/mod.rs
Normal file
@@ -0,0 +1,795 @@
|
||||
// Copyright (c) Rego-Rs Authors.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
#![cfg(test)]
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use rego_rs::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use test_generator::test_resources;
|
||||
//use walkdir::WalkDir;
|
||||
|
||||
macro_rules! my_assert_eq {
|
||||
($left:expr, $right:expr, $($arg:tt)+) => {
|
||||
match (&($left), &($right)) {
|
||||
(left_val, right_val) => {
|
||||
if !(*left_val == *right_val) {
|
||||
return Err(anyhow!("mismatch:\nleft = {}\nright = {}\n{}",
|
||||
&$left, &$right, format_args!($($arg)+)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "intended for use by scripts/rego-parse"]
|
||||
fn one_file() -> Result<()> {
|
||||
let mut file = String::default();
|
||||
for a in env::args() {
|
||||
if a.ends_with(".rego") {
|
||||
file = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 ast = parser.parse()?;
|
||||
println!("{:#?}", ast);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn skip_value(v: &Value) -> bool {
|
||||
matches!(v, Value::String(s) if s == "--skip--")
|
||||
}
|
||||
|
||||
fn match_span(s: &Span, v: &Value) -> Result<()> {
|
||||
match &v {
|
||||
Value::String(vs) => {
|
||||
my_assert_eq!(
|
||||
s.text(),
|
||||
vs,
|
||||
"{}",
|
||||
s.source
|
||||
.message(s.line, s.col, "match-error", "mismatch happened here.")
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
my_assert_eq!(
|
||||
s.text(),
|
||||
serde_json::to_string_pretty(v)?,
|
||||
"{}",
|
||||
s.source
|
||||
.message(s.line, s.col, "match-error", "mismatch happened here.")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn match_span_opt(s: &Span, v: &Value) -> Result<()> {
|
||||
if *v != Value::Undefined {
|
||||
match_span(s, v)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn match_vec(s: &Span, vec: &Vec<Expr>, v: &Value) -> Result<()> {
|
||||
if v.as_object().is_ok() {
|
||||
match_span_opt(s, &v["span"])?;
|
||||
return match_vec(s, vec, &v["values"]);
|
||||
}
|
||||
let v = v.as_array()?;
|
||||
my_assert_eq!(
|
||||
vec.len(),
|
||||
v.len(),
|
||||
"{}",
|
||||
s.source.message(
|
||||
s.line,
|
||||
s.col,
|
||||
"match-error",
|
||||
"mismatch in number of elements in sequence following this location"
|
||||
)
|
||||
);
|
||||
for i in 0..vec.len() {
|
||||
match_expr(&vec[i], &v[i])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn match_object(s: &Span, fields: &Vec<(Span, Expr, Expr)>, v: &Value) -> Result<()> {
|
||||
if skip_value(v) {
|
||||
return Ok(());
|
||||
}
|
||||
match_span_opt(s, &v["span"])?;
|
||||
match &v["fields"].as_array() {
|
||||
Ok(a) => {
|
||||
my_assert_eq!(fields.len(), a.len(), "field length mismatch");
|
||||
for (idx, (_, k, v)) in fields.iter().enumerate() {
|
||||
match_expr(k, &a[idx]["key"])?;
|
||||
match_expr(v, &a[idx]["value"])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => bail!("incorrect field specification in yaml. Must be array."),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_expr_impl(e: &Expr, v: &Value) -> Result<()> {
|
||||
if skip_value(v) {
|
||||
return Ok(());
|
||||
}
|
||||
match e {
|
||||
Expr::String(s) => match_span(s, &v["string"]),
|
||||
Expr::RawString(s) => match_span(s, &v["rawstring"]),
|
||||
Expr::Number(s) => match_span(s, &v["number"]),
|
||||
Expr::True(s) => match_span(s, v),
|
||||
Expr::False(s) => match_span(s, v),
|
||||
Expr::Null(s) => match_span(s, v),
|
||||
Expr::Var(s) => match_span(s, &v["var"]),
|
||||
Expr::Array { span, items } => match_vec(span, items, &v["array"]),
|
||||
Expr::Set { span, items } => match_vec(span, items, &v["set"]),
|
||||
Expr::Object { span, fields } => match_object(span, fields, &v["object"]),
|
||||
Expr::ArrayCompr { span, term, query } => {
|
||||
match_span_opt(span, &v["arraycompr"]["span"])?;
|
||||
match_expr(term, &v["arraycompr"]["term"])?;
|
||||
match_query(query, &v["arraycompr"]["query"])
|
||||
}
|
||||
Expr::SetCompr { span, term, query } => {
|
||||
match_span_opt(span, &v["setcompr"]["span"])?;
|
||||
match_expr(term, &v["setcompr"]["term"])?;
|
||||
match_query(query, &v["setcompr"]["query"])
|
||||
}
|
||||
Expr::ObjectCompr {
|
||||
span,
|
||||
key,
|
||||
value,
|
||||
query,
|
||||
} => {
|
||||
match_span_opt(span, &v["objectcompr"]["span"])?;
|
||||
match_expr(key, &v["objectcompr"]["key"])?;
|
||||
match_expr(value, &v["objectcompr"]["value"])?;
|
||||
match_query(query, &v["objectcompr"]["query"])
|
||||
}
|
||||
Expr::Call { span, fcn, params } => {
|
||||
match_span_opt(span, &v["call"]["span"])?;
|
||||
match_expr(fcn, &v["call"]["fcn"])?;
|
||||
match_vec(span /*dummy*/, params, &v["call"]["params"])
|
||||
}
|
||||
Expr::RefDot { span, refr, field } => {
|
||||
match_span_opt(span, &v["refdot"]["span"])?;
|
||||
match_expr(refr, &v["refdot"]["refr"])?;
|
||||
match_span(field, &v["refdot"]["field"])
|
||||
}
|
||||
Expr::RefBrack { span, refr, index } => {
|
||||
match_span_opt(span, &v["refbrack"]["span"])?;
|
||||
match_expr(refr, &v["refbrack"]["refr"])?;
|
||||
match_expr(index, &v["refbrack"]["index"])
|
||||
}
|
||||
Expr::UnaryExpr { span, expr } => {
|
||||
match_span_opt(span, &v["span"])?;
|
||||
my_assert_eq!(
|
||||
&Value::String("-".to_owned()),
|
||||
&v["op"],
|
||||
"{}",
|
||||
span.source.message(
|
||||
span.line,
|
||||
span.col,
|
||||
"mismatch-error",
|
||||
"could not match `-` operator",
|
||||
),
|
||||
);
|
||||
match_expr(expr, &v["expr"])
|
||||
}
|
||||
Expr::BinExpr { span, op, lhs, rhs } => {
|
||||
match_span_opt(span, &v["binexpr"]["span"])?;
|
||||
match_bin_op(span, op, &v["binexpr"]["op"])?;
|
||||
match_expr(lhs, &v["binexpr"]["lhs"])?;
|
||||
match_expr(rhs, &v["binexpr"]["rhs"])
|
||||
}
|
||||
Expr::ArithExpr { span, op, lhs, rhs } => {
|
||||
match_span_opt(span, &v["arithexpr"]["span"])?;
|
||||
match_arith_op(span, op, &v["arithexpr"]["op"])?;
|
||||
match_expr(lhs, &v["arithexpr"]["lhs"])?;
|
||||
match_expr(rhs, &v["arithexpr"]["rhs"])
|
||||
}
|
||||
Expr::BoolExpr { span, op, lhs, rhs } => {
|
||||
match_span_opt(span, &v["boolexpr"]["span"])?;
|
||||
match_bool_op(span, op, &v["boolexpr"]["op"])?;
|
||||
match_expr(lhs, &v["boolexpr"]["lhs"])?;
|
||||
match_expr(rhs, &v["boolexpr"]["rhs"])
|
||||
}
|
||||
Expr::AssignExpr { span, op, lhs, rhs } => {
|
||||
match_span_opt(span, &v["assignexpr"]["span"])?;
|
||||
match_assign_op(span, op, &v["assignexpr"]["op"])?;
|
||||
match_expr(lhs, &v["assignexpr"]["lhs"])?;
|
||||
match_expr(rhs, &v["assignexpr"]["rhs"])
|
||||
}
|
||||
Expr::Membership {
|
||||
span,
|
||||
key,
|
||||
value,
|
||||
collection,
|
||||
} => {
|
||||
match_span_opt(span, &v["inexpr"]["span"])?;
|
||||
match_expr(key, &v["inexpr"]["key"])?;
|
||||
match_expr_opt(span, value, &v["inexpr"]["value"])?;
|
||||
match_expr(collection, &v["inexpr"]["collection"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn match_expr(expr: &Expr, v: &Value) -> Result<()> {
|
||||
match match_expr_impl(expr, v) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => bail!(
|
||||
"{e}\nexpr = {expr:#?}\nv={}\n-----------------------\n",
|
||||
serde_json::to_string_pretty(v)?
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_with_mod(m: &WithModifier, v: &Value) -> Result<()> {
|
||||
if skip_value(v) {
|
||||
return Ok(());
|
||||
}
|
||||
match_span_opt(&m.span, &v["span"])?;
|
||||
match_expr(&m.refr, &v["refr"])?;
|
||||
match_expr(&m.r#as, &v["as"])
|
||||
}
|
||||
|
||||
fn match_literal_stmt(ls: &LiteralStmt, v: &Value) -> Result<()> {
|
||||
if skip_value(v) {
|
||||
return Ok(());
|
||||
}
|
||||
match_span_opt(&ls.span, &v["span"])?;
|
||||
match_literal(&ls.literal, &v["literal"])?;
|
||||
|
||||
let with_mods = &v["with-mods"];
|
||||
if skip_value(with_mods) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match with_mods.as_array() {
|
||||
Ok(a) => {
|
||||
my_assert_eq!(
|
||||
ls.with_mods.len(),
|
||||
a.len(),
|
||||
"{}",
|
||||
ls.span.source.message(
|
||||
ls.span.line,
|
||||
ls.span.col,
|
||||
"mismatch-error",
|
||||
"with-modifier count mismatch"
|
||||
)
|
||||
);
|
||||
for (idx, with_mod) in a.iter().enumerate() {
|
||||
match_with_mod(&ls.with_mods[idx], with_mod)?;
|
||||
}
|
||||
}
|
||||
_ if ls.with_mods.is_empty() => (),
|
||||
_ => {
|
||||
bail!(
|
||||
"{}",
|
||||
ls.span.source.message(
|
||||
ls.span.line,
|
||||
ls.span.col,
|
||||
"mismatch-error",
|
||||
"failed to match with-modifiers"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn match_query(q: &Query, v: &Value) -> Result<()> {
|
||||
match_span_opt(&q.span, &v["span"])?;
|
||||
let stmts = &v["stmts"].as_array();
|
||||
let stmts = match &stmts {
|
||||
Ok(s) => s,
|
||||
_ => {
|
||||
bail!(
|
||||
"{}",
|
||||
q.span.source.message(
|
||||
q.span.line,
|
||||
q.span.col,
|
||||
"mismatch-error",
|
||||
"empty statements list in query specified"
|
||||
)
|
||||
)
|
||||
}
|
||||
};
|
||||
my_assert_eq!(
|
||||
q.stmts.len(),
|
||||
stmts.len(),
|
||||
"{}",
|
||||
q.span.source.message(
|
||||
q.span.line,
|
||||
q.span.col,
|
||||
"mismatch-error",
|
||||
"mismatch in statement count"
|
||||
)
|
||||
);
|
||||
for (idx, stmt) in stmts.iter().enumerate() {
|
||||
match_literal_stmt(&q.stmts[idx], stmt)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn match_expr_opt(s: &Span, e: &Option<Expr>, v: &Value) -> Result<()> {
|
||||
match (e, v) {
|
||||
(Some(e), v) => match_expr(e, v),
|
||||
(None, Value::Undefined) => Ok(()),
|
||||
_ => {
|
||||
bail!(
|
||||
"{}",
|
||||
s.source.message(
|
||||
s.line,
|
||||
s.col,
|
||||
"mismatch-error",
|
||||
format!(
|
||||
"failed to match {:#?} and {}",
|
||||
e,
|
||||
serde_json::to_string_pretty(&v)?
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn match_bin_op(s: &Span, op: &BinOp, v: &Value) -> Result<()> {
|
||||
match (op, v) {
|
||||
(BinOp::And, Value::String(s)) if s == "&" => Ok(()),
|
||||
(BinOp::Or, Value::String(s)) if s == "|" => Ok(()),
|
||||
_ => bail!(
|
||||
"{}",
|
||||
s.source.message(
|
||||
s.line,
|
||||
s.col,
|
||||
"mismatch-error",
|
||||
format!("left = {:?}\nright = {:?}\n", op, v).as_str()
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_arith_op(s: &Span, op: &ArithOp, v: &Value) -> Result<()> {
|
||||
match (op, v) {
|
||||
(ArithOp::Add, Value::String(s)) if s == "+" => Ok(()),
|
||||
(ArithOp::Sub, Value::String(s)) if s == "-" => Ok(()),
|
||||
(ArithOp::Mul, Value::String(s)) if s == "*" => Ok(()),
|
||||
(ArithOp::Div, Value::String(s)) if s == "/" => Ok(()),
|
||||
_ => bail!(
|
||||
"{}",
|
||||
s.source.message(
|
||||
s.line,
|
||||
s.col,
|
||||
"mismatch-error",
|
||||
format!("left = {:?}\nright = {:?}\n", op, v).as_str()
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_bool_op(s: &Span, op: &BoolOp, v: &Value) -> Result<()> {
|
||||
match (op, v) {
|
||||
(BoolOp::Lt, Value::String(s)) if s == "<" => Ok(()),
|
||||
(BoolOp::Le, Value::String(s)) if s == "<=" => Ok(()),
|
||||
(BoolOp::Eq, Value::String(s)) if s == "==" => Ok(()),
|
||||
(BoolOp::Ge, Value::String(s)) if s == ">=" => Ok(()),
|
||||
(BoolOp::Gt, Value::String(s)) if s == ">" => Ok(()),
|
||||
_ => bail!(
|
||||
"{}",
|
||||
s.source.message(
|
||||
s.line,
|
||||
s.col,
|
||||
"mismatch-error",
|
||||
format!("left = {:?}\nright = {:?}\n", op, v).as_str()
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_assign_op(s: &Span, op: &AssignOp, v: &Value) -> Result<()> {
|
||||
match (op, v) {
|
||||
(AssignOp::Eq, Value::String(s)) if s == "=" => Ok(()),
|
||||
(AssignOp::ColEq, Value::String(s)) if s == ":=" => Ok(()),
|
||||
_ => bail!(
|
||||
"{}",
|
||||
s.source.message(
|
||||
s.line,
|
||||
s.col,
|
||||
"mismatch-error",
|
||||
format!("left = {:?}\nright = {:?}\n", op, v).as_str()
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_rule_assign(a: &RuleAssign, v: &Value) -> Result<()> {
|
||||
match_span_opt(&a.span, &v["span"])?;
|
||||
match_assign_op(&a.span, &a.op, &v["op"])?;
|
||||
match_expr(&a.value, &v["value"])
|
||||
}
|
||||
|
||||
fn match_rule_assign_opt(a: &Option<RuleAssign>, v: &Value) -> Result<()> {
|
||||
match a {
|
||||
Some(a) => match_rule_assign(a, v),
|
||||
None => {
|
||||
my_assert_eq!(*v, Value::Undefined, "mismatch in null assign");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn match_rule_head(h: &RuleHead, v: &Value) -> Result<()> {
|
||||
if skip_value(v) {
|
||||
return Ok(());
|
||||
}
|
||||
match h {
|
||||
RuleHead::Compr { span, refr, assign } => {
|
||||
match_span_opt(span, &v["compr"]["span"])?;
|
||||
match_expr(refr, &v["compr"]["refr"])?;
|
||||
match_rule_assign_opt(assign, &v["compr"]["assign"])
|
||||
}
|
||||
RuleHead::Set { span, refr, key } => {
|
||||
match_span_opt(span, &v["set"]["span"])?;
|
||||
match_expr(refr, &v["set"]["refr"])?;
|
||||
match_expr_opt(span, key, &v["set"]["key"])
|
||||
}
|
||||
RuleHead::Func {
|
||||
span,
|
||||
refr,
|
||||
args,
|
||||
assign,
|
||||
} => {
|
||||
match_span_opt(span, &v["func"]["span"])?;
|
||||
match_expr(refr, &v["func"]["refr"])?;
|
||||
match_vec(span /*dummy*/, args, &v["func"]["args"])?;
|
||||
match_rule_assign_opt(assign, &v["func"]["assign"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn match_literal(l: &Literal, v: &Value) -> Result<()> {
|
||||
match l {
|
||||
Literal::SomeVars { span, vars } => {
|
||||
let v = &v["some-vars"];
|
||||
match_span_opt(span, &v["span"])?;
|
||||
let values = &v["vars"].as_array()?;
|
||||
my_assert_eq!(
|
||||
vars.len(),
|
||||
values.len(),
|
||||
"some-vars mismatch {:#?} {}",
|
||||
vars,
|
||||
serde_json::to_string_pretty(&values)?
|
||||
);
|
||||
for idx in 0..vars.len() {
|
||||
match_span(&vars[idx], &values[idx])?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Literal::SomeIn {
|
||||
span,
|
||||
key,
|
||||
value,
|
||||
collection,
|
||||
} => {
|
||||
let v = &v["some-decl"];
|
||||
match_span_opt(span, &v["span"])?;
|
||||
match_expr(key, &v["key"])?;
|
||||
match_expr_opt(span, value, &v["value"])?;
|
||||
match_expr(collection, &v["collection"])
|
||||
}
|
||||
Literal::Expr { expr, .. } => match_expr(expr, &v["expr"]),
|
||||
Literal::NotExpr { expr, span } => {
|
||||
let v = &v["notexpr"];
|
||||
match &v["op"] {
|
||||
Value::String(s) if s == "not" => (),
|
||||
_ => {
|
||||
bail!(
|
||||
"{}",
|
||||
span.source.message(
|
||||
span.line,
|
||||
span.col,
|
||||
"mismatch-error",
|
||||
"`op: -` not found in value`"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
match_expr(expr, v)
|
||||
}
|
||||
Literal::Every {
|
||||
span,
|
||||
key,
|
||||
value,
|
||||
domain,
|
||||
query,
|
||||
} => {
|
||||
match_span_opt(span, &v["every"]["span"])?;
|
||||
match_span(key, &v["every"]["key"])?;
|
||||
match value {
|
||||
Some(s) => match_span(s, &v["every"]["value"])?,
|
||||
None => {
|
||||
my_assert_eq!(
|
||||
&Value::Undefined,
|
||||
&v["value"],
|
||||
"{}",
|
||||
span.source.message(
|
||||
span.line,
|
||||
span.col,
|
||||
"mismatch-error",
|
||||
"could not match `value``"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
match_expr(domain, &v["every"]["domain"])?;
|
||||
match_query(query, &v["every"]["query"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn match_rule_body(b: &RuleBody, v: &Value) -> Result<()> {
|
||||
if skip_value(v) {
|
||||
return Ok(());
|
||||
}
|
||||
match_span_opt(&b.span, &v["span"])?;
|
||||
match_rule_assign_opt(&b.assign, &v["assign"])?;
|
||||
match_query(&b.query, &v["query"])
|
||||
}
|
||||
|
||||
fn match_rule_bodies(span: &Span, bodies: &Vec<RuleBody>, v: &Value) -> Result<()> {
|
||||
if skip_value(v) {
|
||||
return Ok(());
|
||||
}
|
||||
let v = &v.as_array();
|
||||
let v = match &v {
|
||||
Ok(v) => v,
|
||||
_ => {
|
||||
bail!(
|
||||
"incorrect yaml. bodies is not an array. Corresponding rego: {}",
|
||||
span.source.message(span.line, span.col, "invalid-yaml", "")
|
||||
);
|
||||
}
|
||||
};
|
||||
my_assert_eq!(
|
||||
bodies.len(),
|
||||
v.len(),
|
||||
"{}",
|
||||
span.source.message(
|
||||
span.line,
|
||||
span.col,
|
||||
"mismatch-error",
|
||||
"mismatch in body count",
|
||||
),
|
||||
);
|
||||
|
||||
for idx in 0..bodies.len() {
|
||||
match_rule_body(&bodies[idx], &v[idx])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn match_rule(r: &Rule, v: &Value) -> Result<()> {
|
||||
match r {
|
||||
Rule::Spec { span, head, bodies } => {
|
||||
let obj = &v["spec"];
|
||||
match_span_opt(span, &obj["span"])?;
|
||||
match_rule_head(head, &obj["head"])?;
|
||||
match_rule_bodies(span, bodies, &obj["bodies"])
|
||||
}
|
||||
Rule::Default {
|
||||
span,
|
||||
refr,
|
||||
op,
|
||||
value,
|
||||
} => {
|
||||
let obj = &v["default"];
|
||||
match_span_opt(span, &obj["span"])?;
|
||||
match_expr(refr, &obj["refr"])?;
|
||||
match_assign_op(span, op, &obj["op"])?;
|
||||
match_expr(value, &obj["value"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn match_package(p: &Package, v: &Value) -> Result<()> {
|
||||
if skip_value(v) {
|
||||
return Ok(());
|
||||
}
|
||||
match_span_opt(&p.span, &v["span"])?;
|
||||
match_expr(&p.refr, &v["refr"])
|
||||
}
|
||||
|
||||
fn match_import(i: &Import, v: &Value) -> Result<()> {
|
||||
if skip_value(v) {
|
||||
return Ok(());
|
||||
}
|
||||
match_span_opt(&i.span, &v["span"])?;
|
||||
match_expr(&i.refr, &v["refr"])?;
|
||||
match (&i.r#as, &v["as"]) {
|
||||
(Some(a), v) => match_span(a, v),
|
||||
(None, Value::Undefined) => Ok(()),
|
||||
_ => Err(i
|
||||
.span
|
||||
.source
|
||||
.error(i.span.line, i.span.col, "import does not have `as` binding")),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct TestCase {
|
||||
rego: String,
|
||||
note: String,
|
||||
package: Option<Value>,
|
||||
imports: Option<Vec<Value>>,
|
||||
policy: Option<Vec<Value>>,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct YamlTest {
|
||||
cases: Vec<TestCase>,
|
||||
}
|
||||
|
||||
fn yaml_test_impl(file: &str) -> Result<()> {
|
||||
println!("\nrunning {}", file);
|
||||
|
||||
let yaml_str = std::fs::read_to_string(file)?;
|
||||
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
||||
|
||||
for case in &test.cases {
|
||||
print!("\ncase {} ", case.note);
|
||||
let source = Source {
|
||||
file: "case.rego",
|
||||
contents: case.rego.as_str(),
|
||||
lines: case.rego.split('\n').collect(),
|
||||
};
|
||||
let mut parser = Parser::new(&source)?;
|
||||
match parser.parse() {
|
||||
Ok(module) => {
|
||||
if let Some(e) = &case.error {
|
||||
bail!("error `{}` not raised by parser.", e);
|
||||
}
|
||||
if let Some(p) = &case.package {
|
||||
match_package(&module.package, p)?;
|
||||
}
|
||||
|
||||
if let Some(imports) = &case.imports {
|
||||
my_assert_eq!(
|
||||
module.imports.len(),
|
||||
imports.len(),
|
||||
"mismatch in number of imports"
|
||||
);
|
||||
|
||||
for (idx, import) in imports.iter().enumerate().take(module.imports.len()) {
|
||||
match_import(&module.imports[idx], import)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(policy) = &case.policy {
|
||||
my_assert_eq!(
|
||||
module.policy.len(),
|
||||
policy.len(),
|
||||
"mismatch in policy length"
|
||||
);
|
||||
for (idx, policy) in policy.iter().enumerate().take(module.policy.len()) {
|
||||
if skip_value(policy) {
|
||||
continue;
|
||||
}
|
||||
match_rule(&module.policy[idx], policy)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
println!("{} cases passed.", test.cases.len());
|
||||
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]
|
||||
#[ignore = "intended for use by scripts/yaml-test-parse"]
|
||||
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;
|
||||
match yaml_test(path) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
bail!("test failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("{} parser yaml tests passed.", total);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn parser_yaml_tests() -> Result<()> {
|
||||
run_yaml_tests_in("tests/parser")
|
||||
}
|
||||
*/
|
||||
|
||||
#[test_resources("tests/parser/**/*.yaml")]
|
||||
fn run(path: &str) {
|
||||
yaml_test(path).unwrap()
|
||||
}
|
||||
7
tests/tests.rs
Normal file
7
tests/tests.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Rego-Rs Authors.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
|
||||
mod interpreter;
|
||||
mod lexer;
|
||||
mod parser;
|
||||
mod value;
|
||||
18
tests/tmp-files-to-be-added-as-yaml-tests/arith_0.rego
Normal file
18
tests/tmp-files-to-be-added-as-yaml-tests/arith_0.rego
Normal file
@@ -0,0 +1,18 @@
|
||||
package test
|
||||
|
||||
add {
|
||||
1 + 2 == 3
|
||||
}
|
||||
|
||||
sub {
|
||||
5 - 1 == 4
|
||||
}
|
||||
|
||||
mul {
|
||||
3 * 4 == 12
|
||||
}
|
||||
|
||||
# Lock down float operation.
|
||||
div {
|
||||
21 / 5 == 4.2
|
||||
}
|
||||
20
tests/tmp-files-to-be-added-as-yaml-tests/basic.rego
Normal file
20
tests/tmp-files-to-be-added-as-yaml-tests/basic.rego
Normal file
@@ -0,0 +1,20 @@
|
||||
package basic
|
||||
|
||||
import future.keywords.if
|
||||
import future.keywords.in
|
||||
|
||||
default hello := false
|
||||
|
||||
hello if input.message == "world"
|
||||
|
||||
|
||||
default foo := false
|
||||
|
||||
foo {
|
||||
# some i, {j:5} in {1,2,3} & {5, 6}
|
||||
true
|
||||
}
|
||||
|
||||
bar {
|
||||
1 in {1, 2}
|
||||
}
|
||||
4
tests/tmp-files-to-be-added-as-yaml-tests/input_0.json
Normal file
4
tests/tmp-files-to-be-added-as-yaml-tests/input_0.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"age" : 20,
|
||||
"date" : "12/1/2022"
|
||||
}
|
||||
37
tests/tmp-files-to-be-added-as-yaml-tests/parse_0.rego
Normal file
37
tests/tmp-files-to-be-added-as-yaml-tests/parse_0.rego
Normal file
@@ -0,0 +1,37 @@
|
||||
package acc.rego
|
||||
|
||||
import future.keywords.in
|
||||
|
||||
[ 1, null, 5, "Hello", [ [ 1, 2, 3, set( ), { set(
|
||||
), 5, `ab
|
||||
`}] ],
|
||||
set().a.b.c[a.b],
|
||||
1 >= 2 <= 1 + (- 8 * 44),
|
||||
6, (7, 8 in [ 5, 6] & 7),
|
||||
{
|
||||
a : 56,
|
||||
b : 0,
|
||||
5 : 6
|
||||
},
|
||||
a.foo(5+6, 8, {9}),
|
||||
[ (x + 5) |
|
||||
foo(x)
|
||||
true
|
||||
not foo(x)
|
||||
{ (x + 5) |
|
||||
foo(x)
|
||||
true
|
||||
not foo(x)
|
||||
}
|
||||
|
||||
],
|
||||
{
|
||||
a : { 5, 6, 7 },
|
||||
b : 6,
|
||||
c : { p: q |
|
||||
p + 5 with data.p as 86
|
||||
q * 6
|
||||
5 in {5, 6}
|
||||
}
|
||||
}
|
||||
]
|
||||
3
tests/tmp-files-to-be-added-as-yaml-tests/query.rego
Normal file
3
tests/tmp-files-to-be-added-as-yaml-tests/query.rego
Normal file
@@ -0,0 +1,3 @@
|
||||
x := 5,
|
||||
y := x + 5 * 3; x & y
|
||||
]
|
||||
5
tests/tmp-files-to-be-added-as-yaml-tests/rego_0.rego
Normal file
5
tests/tmp-files-to-be-added-as-yaml-tests/rego_0.rego
Normal file
@@ -0,0 +1,5 @@
|
||||
package test
|
||||
|
||||
accept {
|
||||
input.date == "12/1/2022"
|
||||
}
|
||||
25
tests/tmp-files-to-be-added-as-yaml-tests/rule_assign_0.rego
Normal file
25
tests/tmp-files-to-be-added-as-yaml-tests/rule_assign_0.rego
Normal file
@@ -0,0 +1,25 @@
|
||||
package test
|
||||
|
||||
no_assign {
|
||||
input.date == "12/1/2022"
|
||||
}
|
||||
|
||||
assign_null = null {
|
||||
input.date == "12/1/2022"
|
||||
}
|
||||
|
||||
assign_bool = false {
|
||||
input.date == "12/1/2022"
|
||||
}
|
||||
|
||||
assign_int = 101 {
|
||||
input.date == "12/1/2022"
|
||||
}
|
||||
|
||||
assign_float = 3.14 {
|
||||
input.date == "12/1/2022"
|
||||
}
|
||||
|
||||
assign_string = "test_string" {
|
||||
input.date == "12/1/2022"
|
||||
}
|
||||
12
tests/tmp-files-to-be-added-as-yaml-tests/tmp.rego
Normal file
12
tests/tmp-files-to-be-added-as-yaml-tests/tmp.rego
Normal file
@@ -0,0 +1,12 @@
|
||||
package test
|
||||
|
||||
# This is a comment
|
||||
|
||||
x := 5
|
||||
oct {
|
||||
z := -1
|
||||
true
|
||||
}
|
||||
y := 1234
|
||||
z := "abc\n\u0000
|
||||
"
|
||||
@@ -0,0 +1,6 @@
|
||||
package test
|
||||
|
||||
local = x {
|
||||
some x
|
||||
x = 10
|
||||
}
|
||||
176
tests/value/mod.rs
Normal file
176
tests/value/mod.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
// Copyright (c) Rego-Rs Authors.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
#![cfg(test)]
|
||||
|
||||
use anyhow::Result;
|
||||
use rego_rs::*;
|
||||
|
||||
#[test]
|
||||
fn non_string_key() -> Result<()> {
|
||||
let mut obj = Value::new_object();
|
||||
|
||||
obj.as_object_mut()?.insert(Value::Null, Value::Null);
|
||||
obj.as_object_mut()?.insert(Value::Bool(false), Value::Null);
|
||||
obj.as_object_mut()?
|
||||
.insert(Value::from_f64(std::f64::consts::PI), Value::Null);
|
||||
obj.as_object_mut()?.insert(
|
||||
Value::from_array(vec![
|
||||
Value::Bool(true),
|
||||
Value::Null,
|
||||
Value::from_f64(std::f64::consts::PI),
|
||||
]),
|
||||
Value::Null,
|
||||
);
|
||||
|
||||
let mut set = Value::new_set();
|
||||
set.as_set_mut()?.insert(Value::Bool(true));
|
||||
set.as_set_mut()?.insert(Value::Bool(false));
|
||||
set.as_set_mut()?.insert(Value::Bool(true));
|
||||
set.as_set_mut()?
|
||||
.insert(Value::from_f64(std::f64::consts::PI));
|
||||
obj.as_object_mut()?.insert(set, Value::Null);
|
||||
|
||||
obj.as_object_mut()?.insert(Value::Undefined, Value::Null);
|
||||
|
||||
let key_obj = obj.clone();
|
||||
obj.as_object_mut()?.insert(key_obj, Value::Null);
|
||||
|
||||
let json = serde_json::to_string_pretty(&obj)?;
|
||||
println!("{}", json);
|
||||
|
||||
let expected = r#"{
|
||||
"null": null,
|
||||
"false": null,
|
||||
"3.141592653589793": null,
|
||||
"[true,null,3.141592653589793]": null,
|
||||
"{\"null\":null,\"false\":null,\"3.141592653589793\":null,\"[true,null,3.141592653589793]\":null,\"[false,true,3.141592653589793]\":null,\"\\\"<undefined>\\\"\":null}": null,
|
||||
"[false,true,3.141592653589793]": null,
|
||||
"\"<undefined>\"": null
|
||||
}"#;
|
||||
|
||||
assert_eq!(json, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_number() -> Result<()> {
|
||||
// Check that integer values are serialized without fractional part
|
||||
assert_eq!(serde_json::to_string_pretty(&Value::from_f64(1.0))?, "1");
|
||||
assert_eq!(serde_json::to_string_pretty(&Value::from_f64(-1.0))?, "-1");
|
||||
|
||||
// Ensure that fractional parts are also serialized.
|
||||
assert_eq!(serde_json::to_string_pretty(&Value::from_f64(1.1))?, "1.1");
|
||||
assert_eq!(
|
||||
serde_json::to_string_pretty(&Value::from_f64(-1.1))?,
|
||||
"-1.1"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_number() {
|
||||
use ordered_float::OrderedFloat;
|
||||
let n = Number(OrderedFloat(123456f64));
|
||||
assert_eq!(format!("{}", &n), "123456");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_string() -> Result<()> {
|
||||
assert_eq!(
|
||||
Value::String("Hello, World\n".to_owned()).to_json_str()?,
|
||||
"\"Hello, World\\n\""
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constructors() -> Result<()> {
|
||||
assert_eq!(Value::new_object(), Value::from_json_str("{}")?);
|
||||
assert!(Value::new_set().as_set()?.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_as_index() -> Result<()> {
|
||||
let idx = Value::from_f64(2.0);
|
||||
|
||||
let mut item = Value::new_array();
|
||||
item.as_array_mut()?.push(Value::from_f64(3.0));
|
||||
item.as_array_mut()?.push(Value::from_f64(4.0));
|
||||
item.as_array_mut()?.push(Value::from_f64(5.0));
|
||||
|
||||
// Check case of item present.
|
||||
assert_eq!(&Value::from_json_str("[1, 2, [3, 4, 5]]")?[&idx], &item);
|
||||
|
||||
// Check case of item not present.
|
||||
let idx = Value::from_f64(5.0);
|
||||
assert_eq!(
|
||||
&Value::from_json_str("[1, 2, [3, 4, 5]]")?[&idx],
|
||||
&Value::Undefined
|
||||
);
|
||||
|
||||
// Check case of non indexable item.
|
||||
assert_eq!(&Value::Undefined[&idx], &Value::Undefined);
|
||||
assert_eq!(&Value::Null[&idx], &Value::Undefined);
|
||||
assert_eq!(&Value::Bool(true)[&idx], &Value::Undefined);
|
||||
assert_eq!(&Value::String("Hello".to_owned())[&idx], &Value::Undefined);
|
||||
assert_eq!(&Value::new_set()[&idx], &Value::Undefined);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_as_index() -> Result<()> {
|
||||
let obj = Value::from_json_str(r#"{ "a" : 5, "b" : 6 }"#)?;
|
||||
assert_eq!(&obj["a"], &Value::from_f64(5.0));
|
||||
assert_eq!(&obj[&"b".to_owned()], &Value::from_f64(6.0));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usize_as_index() -> Result<()> {
|
||||
assert_eq!(
|
||||
&Value::from_json_str("[1, 2, 3]")?[0],
|
||||
&Value::from_f64(1.0)
|
||||
);
|
||||
assert_eq!(&Value::from_json_str("[1, 2, 3]")?[5], &Value::Undefined);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api() -> Result<()> {
|
||||
assert!(&Value::from_json_str("{}")?.as_object()?.is_empty());
|
||||
let mut v = Value::new_object();
|
||||
v.as_object_mut()?
|
||||
.insert(Value::String("a".to_owned()), Value::from_f64(3.145));
|
||||
assert_eq!(v["a"], Value::from_f64(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);
|
||||
|
||||
// Check invalid api calls.
|
||||
assert!(matches!(Value::Undefined.as_object(), Err(_)));
|
||||
assert!(matches!(Value::Undefined.as_object_mut(), Err(_)));
|
||||
|
||||
assert!(matches!(Value::Null.as_set(), Err(_)));
|
||||
assert!(matches!(Value::Null.as_set_mut(), Err(_)));
|
||||
|
||||
assert!(matches!(Value::String("anc".to_owned()).as_array(), Err(_)));
|
||||
assert!(matches!(
|
||||
Value::String("anc".to_owned()).as_array_mut(),
|
||||
Err(_)
|
||||
));
|
||||
|
||||
assert!(matches!(Value::new_object().as_number(), Err(_)));
|
||||
assert!(matches!(Value::new_object().as_number_mut(), Err(_)));
|
||||
|
||||
assert!(matches!(Value::from_f64(5.6).as_bool(), Err(_)));
|
||||
assert!(matches!(Value::from_f64(5.6).as_bool_mut(), Err(_)));
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user