mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat: or keyword (#315)
Add `or` operator to Rego languages. Available via `rego-extensions` Cargo feature. If the evaluated lhs value is not false, null or undefined it is returned. Otherwise rhs is evaluated and returned. or operator has least precedence, and is left-associative. closes #314
This commit is contained in:
committed by
GitHub
parent
8498274356
commit
7565ec3ecf
33
.github/workflows/pr-extensions.yml
vendored
Normal file
33
.github/workflows/pr-extensions.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: tests/release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build only std
|
||||||
|
run: cargo build -r --example regorus --no-default-features --features "std,rego-extensions"
|
||||||
|
- name: Doc Tests
|
||||||
|
run: cargo test -r --doc --features rego-extensions
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test -r --features rego-extensions
|
||||||
|
- name: Run example
|
||||||
|
run: cargo run --example regorus --features rego-extensions -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
|
||||||
|
- name: Run tests (ACI)
|
||||||
|
run: cargo test -r --test aci --features rego-extensions
|
||||||
|
- name: Run tests (KATA)
|
||||||
|
run: cargo test -r --test kata --features rego-extensions
|
||||||
|
- name: Run tests (OPA Conformance)
|
||||||
|
run: >-
|
||||||
|
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision,rego-extensions -- $(tr '\n' ' ' < tests/opa.passing)
|
||||||
@@ -67,7 +67,9 @@ full-opa = [
|
|||||||
"time",
|
"time",
|
||||||
"uuid",
|
"uuid",
|
||||||
"urlquery",
|
"urlquery",
|
||||||
"yaml"
|
"yaml",
|
||||||
|
|
||||||
|
#"rego-extensions"
|
||||||
]
|
]
|
||||||
|
|
||||||
# Features that can be used in no_std environments.
|
# Features that can be used in no_std environments.
|
||||||
@@ -89,6 +91,9 @@ opa-no-std = [
|
|||||||
"lazy_static/spin_no_std"
|
"lazy_static/spin_no_std"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Rego language extensions
|
||||||
|
rego-extensions = []
|
||||||
|
|
||||||
# This feature enables some testing utils for OPA tests.
|
# This feature enables some testing utils for OPA tests.
|
||||||
opa-testutil = []
|
opa-testutil = []
|
||||||
rand = ["dep:rand"]
|
rand = ["dep:rand"]
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ in-expr ::= in-expr 'in' bool-expr
|
|||||||
bool-expr ::= bool-expr bool-op or-expr
|
bool-expr ::= bool-expr bool-op or-expr
|
||||||
| or-expr
|
| or-expr
|
||||||
bool-op ::= '<' | '<=' | '==' | '>=' | '>' | '!='
|
bool-op ::= '<' | '<=' | '==' | '>=' | '>' | '!='
|
||||||
or-expr ::= or-expr '|' and-expr
|
set-union-expr ::= set-union-expr '|' set-intersection-expr
|
||||||
| and-expr
|
| set-intersection-expr
|
||||||
and-expr ::= and-expr '&' arith-expr
|
set-intersection-expr ::= set-intersection-expr '&' arith-expr
|
||||||
| arith-expr
|
| arith-expr
|
||||||
arith-expr ::= arith-expr ('+' | '-') mul-div-expr
|
arith-expr ::= arith-expr ('+' | '-') mul-div-expr
|
||||||
| mul-div-expr
|
| mul-div-expr
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ if [ -f Cargo.toml ]; then
|
|||||||
cargo test -r --test aci
|
cargo test -r --test aci
|
||||||
cargo test -r --test kata
|
cargo test -r --test kata
|
||||||
|
|
||||||
|
# Ensure that all tests pass with extensions
|
||||||
|
cargo test -r --features rego-extensions
|
||||||
|
cargo test -r --test aci rego-extensions
|
||||||
|
cargo test -r --test kata rego-extensions
|
||||||
|
|
||||||
# Ensure that OPA conformance tests don't regress.
|
# Ensure that OPA conformance tests don't regress.
|
||||||
cargo test -r --features opa-testutil,serde_json/arbitrary_precision --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
cargo test -r --features opa-testutil,serde_json/arbitrary_precision,rego-extensions --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||||
fi
|
fi
|
||||||
|
|||||||
13
src/ast.rs
13
src/ast.rs
@@ -10,8 +10,8 @@ use core::{cmp, fmt, ops::Deref};
|
|||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ast", derive(serde::Serialize))]
|
||||||
pub enum BinOp {
|
pub enum BinOp {
|
||||||
And,
|
Intersection,
|
||||||
Or,
|
Union,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@@ -209,6 +209,13 @@ pub enum Expr {
|
|||||||
value: Ref<Expr>,
|
value: Ref<Expr>,
|
||||||
collection: Ref<Expr>,
|
collection: Ref<Expr>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[cfg(feature = "rego-extensions")]
|
||||||
|
OrExpr {
|
||||||
|
span: Span,
|
||||||
|
lhs: Ref<Expr>,
|
||||||
|
rhs: Ref<Expr>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
@@ -232,6 +239,8 @@ impl Expr {
|
|||||||
| ArithExpr { span, .. }
|
| ArithExpr { span, .. }
|
||||||
| AssignExpr { span, .. }
|
| AssignExpr { span, .. }
|
||||||
| Membership { span, .. } => span,
|
| Membership { span, .. } => span,
|
||||||
|
#[cfg(feature = "rego-extensions")]
|
||||||
|
OrExpr { span, .. } => span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -484,6 +484,12 @@ impl Interpreter {
|
|||||||
self.hoist_loops_impl(rhs, loops);
|
self.hoist_loops_impl(rhs, loops);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rego-extensions")]
|
||||||
|
OrExpr { lhs, rhs, .. } => {
|
||||||
|
self.hoist_loops_impl(lhs, loops);
|
||||||
|
self.hoist_loops_impl(rhs, loops);
|
||||||
|
}
|
||||||
|
|
||||||
Membership {
|
Membership {
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
@@ -554,8 +560,8 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
BinOp::Or => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
|
BinOp::Union => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
|
||||||
BinOp::And => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
|
BinOp::Intersection => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2831,6 +2837,15 @@ impl Interpreter {
|
|||||||
..
|
..
|
||||||
} => self.eval_membership(key, value, collection),
|
} => self.eval_membership(key, value, collection),
|
||||||
|
|
||||||
|
#[cfg(feature = "rego-extensions")]
|
||||||
|
Expr::OrExpr { lhs, rhs, .. } => {
|
||||||
|
let lhs = self.eval_expr(lhs)?;
|
||||||
|
match lhs {
|
||||||
|
Value::Bool(false) | Value::Null | Value::Undefined => self.eval_expr(rhs),
|
||||||
|
_ => Ok(lhs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Creation expression
|
// Creation expression
|
||||||
Expr::Array { items, .. } => self.eval_array(items),
|
Expr::Array { items, .. } => self.eval_array(items),
|
||||||
Expr::Object { fields, .. } => self.eval_object(fields),
|
Expr::Object { fields, .. } => self.eval_object(fields),
|
||||||
@@ -3126,6 +3141,8 @@ impl Interpreter {
|
|||||||
ArithExpr { span, .. } => ("arithexpr", span),
|
ArithExpr { span, .. } => ("arithexpr", span),
|
||||||
AssignExpr { span, .. } => ("assignexpr", span),
|
AssignExpr { span, .. } => ("assignexpr", span),
|
||||||
Membership { span, .. } => ("membership", span),
|
Membership { span, .. } => ("membership", span),
|
||||||
|
#[cfg(feature = "rego-extensions")]
|
||||||
|
OrExpr { span, .. } => ("orexpr", span),
|
||||||
};
|
};
|
||||||
|
|
||||||
Err(span.error(format!("invalid `{kind}` in default value").as_str()))
|
Err(span.error(format!("invalid `{kind}` in default value").as_str()))
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ impl<'source> Parser<'source> {
|
|||||||
|
|
||||||
fn parse_parens_expr(&mut self) -> Result<Expr> {
|
fn parse_parens_expr(&mut self) -> Result<Expr> {
|
||||||
self.next_token()?;
|
self.next_token()?;
|
||||||
let expr = self.parse_membership_expr()?;
|
let expr = self.parse_expr()?;
|
||||||
self.expect(")", "while parsing parenthesized expression")?;
|
self.expect(")", "while parsing parenthesized expression")?;
|
||||||
//TODO: if needed introduce a parens-expr node or adjust expr's span.
|
//TODO: if needed introduce a parens-expr node or adjust expr's span.
|
||||||
Ok(expr)
|
Ok(expr)
|
||||||
@@ -700,7 +700,7 @@ impl<'source> Parser<'source> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_and_expr(&mut self) -> Result<Expr> {
|
fn parse_set_intersection_expr(&mut self) -> Result<Expr> {
|
||||||
let start = self.tok.1.start;
|
let start = self.tok.1.start;
|
||||||
let mut expr = self.parse_arith_expr()?;
|
let mut expr = self.parse_arith_expr()?;
|
||||||
|
|
||||||
@@ -712,7 +712,7 @@ impl<'source> Parser<'source> {
|
|||||||
span.end = self.end;
|
span.end = self.end;
|
||||||
expr = Expr::BinExpr {
|
expr = Expr::BinExpr {
|
||||||
span,
|
span,
|
||||||
op: BinOp::And,
|
op: BinOp::Intersection,
|
||||||
lhs: Ref::new(expr),
|
lhs: Ref::new(expr),
|
||||||
rhs: Ref::new(right),
|
rhs: Ref::new(right),
|
||||||
};
|
};
|
||||||
@@ -720,19 +720,19 @@ impl<'source> Parser<'source> {
|
|||||||
Ok(expr)
|
Ok(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_or_expr(&mut self) -> Result<Expr> {
|
fn parse_set_union_expr(&mut self) -> Result<Expr> {
|
||||||
let start = self.tok.1.start;
|
let start = self.tok.1.start;
|
||||||
let mut expr = self.parse_and_expr()?;
|
let mut expr = self.parse_set_intersection_expr()?;
|
||||||
|
|
||||||
while self.token_text() == "|" {
|
while self.token_text() == "|" {
|
||||||
let mut span = self.tok.1.clone();
|
let mut span = self.tok.1.clone();
|
||||||
span.start = start;
|
span.start = start;
|
||||||
self.next_token()?;
|
self.next_token()?;
|
||||||
let right = self.parse_and_expr()?;
|
let right = self.parse_set_intersection_expr()?;
|
||||||
span.end = self.end;
|
span.end = self.end;
|
||||||
expr = Expr::BinExpr {
|
expr = Expr::BinExpr {
|
||||||
span,
|
span,
|
||||||
op: BinOp::Or,
|
op: BinOp::Union,
|
||||||
lhs: Ref::new(expr),
|
lhs: Ref::new(expr),
|
||||||
rhs: Ref::new(right),
|
rhs: Ref::new(right),
|
||||||
};
|
};
|
||||||
@@ -742,7 +742,7 @@ impl<'source> Parser<'source> {
|
|||||||
|
|
||||||
fn parse_bool_expr(&mut self) -> Result<Expr> {
|
fn parse_bool_expr(&mut self) -> Result<Expr> {
|
||||||
let start = self.tok.1.start;
|
let start = self.tok.1.start;
|
||||||
let mut expr = self.parse_or_expr()?;
|
let mut expr = self.parse_set_union_expr()?;
|
||||||
loop {
|
loop {
|
||||||
let mut span = self.tok.1.clone();
|
let mut span = self.tok.1.clone();
|
||||||
span.start = start;
|
span.start = start;
|
||||||
@@ -756,7 +756,7 @@ impl<'source> Parser<'source> {
|
|||||||
_ => break,
|
_ => break,
|
||||||
};
|
};
|
||||||
self.next_token()?;
|
self.next_token()?;
|
||||||
let right = self.parse_or_expr()?;
|
let right = self.parse_set_union_expr()?;
|
||||||
span.end = self.end;
|
span.end = self.end;
|
||||||
expr = Expr::BoolExpr {
|
expr = Expr::BoolExpr {
|
||||||
span,
|
span,
|
||||||
@@ -811,6 +811,32 @@ impl<'source> Parser<'source> {
|
|||||||
Ok(expr)
|
Ok(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_expr(&mut self) -> Result<Expr> {
|
||||||
|
#[cfg(feature = "rego-extensions")]
|
||||||
|
return self.parse_or_expr();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "rego-extensions"))]
|
||||||
|
return self.parse_membership_expr();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rego-extensions")]
|
||||||
|
pub fn parse_or_expr(&mut self) -> Result<Expr> {
|
||||||
|
let start = self.tok.1.start;
|
||||||
|
let mut expr = self.parse_membership_expr()?;
|
||||||
|
while self.token_text() == "or" {
|
||||||
|
let mut span = self.tok.1.clone();
|
||||||
|
span.start = start;
|
||||||
|
self.next_token()?;
|
||||||
|
let rhs = self.parse_membership_expr()?;
|
||||||
|
expr = Expr::OrExpr {
|
||||||
|
span,
|
||||||
|
lhs: Ref::new(expr),
|
||||||
|
rhs: Ref::new(rhs),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(expr)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_membership_expr(&mut self) -> Result<Expr> {
|
pub fn parse_membership_expr(&mut self) -> Result<Expr> {
|
||||||
let start = self.tok.1.start;
|
let start = self.tok.1.start;
|
||||||
let mut expr = self.parse_bool_expr()?;
|
let mut expr = self.parse_bool_expr()?;
|
||||||
@@ -851,12 +877,12 @@ impl<'source> Parser<'source> {
|
|||||||
":=" => AssignOp::ColEq,
|
":=" => AssignOp::ColEq,
|
||||||
_ => {
|
_ => {
|
||||||
*self = state;
|
*self = state;
|
||||||
return self.parse_membership_expr();
|
return self.parse_expr();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.next_token()?;
|
self.next_token()?;
|
||||||
let right = self.parse_membership_expr()?;
|
let right = self.parse_expr()?;
|
||||||
span.end = self.end;
|
span.end = self.end;
|
||||||
Ok(Expr::AssignExpr {
|
Ok(Expr::AssignExpr {
|
||||||
span,
|
span,
|
||||||
@@ -1048,7 +1074,13 @@ impl<'source> Parser<'source> {
|
|||||||
// Treat { 1 | 1 } as a comprehension instead of a
|
// Treat { 1 | 1 } as a comprehension instead of a
|
||||||
// set of 1 element.
|
// set of 1 element.
|
||||||
if let Literal::Expr { expr: e, .. } = &stmt.literal {
|
if let Literal::Expr { expr: e, .. } = &stmt.literal {
|
||||||
if matches!(e.as_ref(), Expr::BinExpr { op: BinOp::Or, .. }) {
|
if matches!(
|
||||||
|
e.as_ref(),
|
||||||
|
Expr::BinExpr {
|
||||||
|
op: BinOp::Union,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) {
|
||||||
*self = state;
|
*self = state;
|
||||||
bail!("try parse as comprehension");
|
bail!("try parse as comprehension");
|
||||||
}
|
}
|
||||||
@@ -1116,7 +1148,7 @@ impl<'source> Parser<'source> {
|
|||||||
_ => return Ok(None),
|
_ => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let expr = Ref::new(self.parse_membership_expr()?);
|
let expr = Ref::new(self.parse_expr()?);
|
||||||
span.end = self.end;
|
span.end = self.end;
|
||||||
Ok(Some(RuleAssign {
|
Ok(Some(RuleAssign {
|
||||||
span,
|
span,
|
||||||
@@ -1264,7 +1296,7 @@ impl<'source> Parser<'source> {
|
|||||||
}
|
}
|
||||||
"[" => {
|
"[" => {
|
||||||
self.next_token()?;
|
self.next_token()?;
|
||||||
let index = self.parse_membership_expr()?;
|
let index = self.parse_expr()?;
|
||||||
span.end = self.end;
|
span.end = self.end;
|
||||||
self.expect("]", "while parsing bracketed reference")?;
|
self.expect("]", "while parsing bracketed reference")?;
|
||||||
term = Expr::RefBrack {
|
term = Expr::RefBrack {
|
||||||
@@ -1312,7 +1344,7 @@ impl<'source> Parser<'source> {
|
|||||||
}
|
}
|
||||||
"contains" => {
|
"contains" => {
|
||||||
self.next_token()?;
|
self.next_token()?;
|
||||||
let key = Ref::new(self.parse_membership_expr()?);
|
let key = Ref::new(self.parse_expr()?);
|
||||||
span.end = self.end;
|
span.end = self.end;
|
||||||
Ok(RuleHead::Set {
|
Ok(RuleHead::Set {
|
||||||
span,
|
span,
|
||||||
|
|||||||
@@ -258,6 +258,12 @@ pub fn traverse(expr: &Ref<Expr>, f: &mut dyn FnMut(&Ref<Expr>) -> Result<bool>)
|
|||||||
traverse(rhs, f)?;
|
traverse(rhs, f)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rego-extensions")]
|
||||||
|
OrExpr { lhs, rhs, .. } => {
|
||||||
|
traverse(lhs, f)?;
|
||||||
|
traverse(rhs, f)?;
|
||||||
|
}
|
||||||
|
|
||||||
Membership {
|
Membership {
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
|
|||||||
@@ -385,6 +385,11 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn yaml_test(file: &str) -> Result<()> {
|
fn yaml_test(file: &str) -> Result<()> {
|
||||||
|
#[cfg(not(feature = "rego-extensions"))]
|
||||||
|
if file.contains("rego-extensions") {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
match yaml_test_impl(file) {
|
match yaml_test_impl(file) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
52
tests/interpreter/cases/rego-extensions/or/tests.yaml
Normal file
52
tests/interpreter/cases/rego-extensions/or/tests.yaml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Copyright (c) Microsoft Corporation.
|
||||||
|
# Licensed under the MIT License.
|
||||||
|
cases:
|
||||||
|
- note: basic
|
||||||
|
modules:
|
||||||
|
- |
|
||||||
|
package test
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
x := data.foo or 2 # undefined lhs
|
||||||
|
y := false or 3 # false rhs
|
||||||
|
z := null or 4
|
||||||
|
a := data.foo or false or null or 5
|
||||||
|
b := startswith("a", "b") or startswith("a", "a")
|
||||||
|
c := 5 in [1,2] or 6 in [6]
|
||||||
|
d := x if {
|
||||||
|
x := false or [1, 2][_]
|
||||||
|
x > 1
|
||||||
|
}
|
||||||
|
e if 1 > 2 or false
|
||||||
|
query: data.test
|
||||||
|
want_result:
|
||||||
|
x: 2
|
||||||
|
y: 3
|
||||||
|
z: 4
|
||||||
|
a: 5
|
||||||
|
b: true
|
||||||
|
c: true
|
||||||
|
d: 2
|
||||||
|
- note: Azure Policy
|
||||||
|
modules:
|
||||||
|
- |
|
||||||
|
package policy
|
||||||
|
|
||||||
|
effect := parameters.effect if {
|
||||||
|
resource.type == "Microsoft.Storage/storageaccounts"
|
||||||
|
resource.properties.networkAcls.defaultAction == "Deny"
|
||||||
|
or count(resource.properties.networkAcls.ipRules) >= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
resource := input.resource
|
||||||
|
parameters := input.parameters
|
||||||
|
input:
|
||||||
|
resource:
|
||||||
|
type: "Microsoft.Storage/storageaccounts"
|
||||||
|
properties:
|
||||||
|
networksAcls:
|
||||||
|
ipRules: ["rule1", "rule2"]
|
||||||
|
parameters:
|
||||||
|
effect: "Deny"
|
||||||
|
query: data.policy.effect
|
||||||
|
want_result: "Deny"
|
||||||
@@ -198,6 +198,13 @@ fn match_expr_impl(e: &Expr, v: &Value) -> Result<()> {
|
|||||||
match_expr(value, &v["inexpr"]["value"])?;
|
match_expr(value, &v["inexpr"]["value"])?;
|
||||||
match_expr(collection, &v["inexpr"]["collection"])
|
match_expr(collection, &v["inexpr"]["collection"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rego-extensions")]
|
||||||
|
Expr::OrExpr { span, lhs, rhs } => {
|
||||||
|
match_span_opt(span, &v["orexpr"]["span"])?;
|
||||||
|
match_expr(lhs, &v["orexpr"]["lhs"])?;
|
||||||
|
match_expr(rhs, &v["orexpr"]["rhs"])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,8 +331,8 @@ fn match_expr_opt(s: &Span, e: &Option<Ref<Expr>>, v: &Value) -> Result<()> {
|
|||||||
|
|
||||||
fn match_bin_op(s: &Span, op: &BinOp, v: &Value) -> Result<()> {
|
fn match_bin_op(s: &Span, op: &BinOp, v: &Value) -> Result<()> {
|
||||||
match (op, v) {
|
match (op, v) {
|
||||||
(BinOp::And, Value::String(s)) if s.as_ref() == "&" => Ok(()),
|
(BinOp::Intersection, Value::String(s)) if s.as_ref() == "&" => Ok(()),
|
||||||
(BinOp::Or, Value::String(s)) if s.as_ref() == "|" => Ok(()),
|
(BinOp::Union, Value::String(s)) if s.as_ref() == "|" => Ok(()),
|
||||||
_ => bail!(
|
_ => bail!(
|
||||||
"{}",
|
"{}",
|
||||||
s.source.message(
|
s.source.message(
|
||||||
|
|||||||
Reference in New Issue
Block a user