mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
no_std support (#232)
- Disable default features in dependencies - Use anyhow::Error::msg to map errors. Note: anyhow will itself be removed later. - lazy_static/spin_no_std used in no_std environments - ensure_no_std binary is built to target thumbv7m-none-eabi to ensure that there are no std dependencies. thumbv7m-none-eabi target has no std support. - The opa-no-std feature enables only those Regorus features that work with no_std. - Enable tests with no_std - Update sizes of regorus binary in README.md - Ensure that regorus example can be built with only std - Ensure that regorus example can be built with no_std Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
01fc234a33
commit
e86b590f91
8
.github/workflows/pr.yml
vendored
8
.github/workflows/pr.yml
vendored
@@ -18,6 +18,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Add musl target
|
- name: Add musl target
|
||||||
run: rustup target add x86_64-unknown-linux-musl
|
run: rustup target add x86_64-unknown-linux-musl
|
||||||
|
- name: Add no_std target
|
||||||
|
run: rustup target add thumbv7m-none-eabi
|
||||||
- name: Install musl-gcc
|
- name: Install musl-gcc
|
||||||
run: sudo apt update && sudo apt install -y musl-tools
|
run: sudo apt update && sudo apt install -y musl-tools
|
||||||
- name: Format Check
|
- name: Format Check
|
||||||
@@ -26,6 +28,12 @@ jobs:
|
|||||||
run: cargo build -r --all-features --verbose
|
run: cargo build -r --all-features --verbose
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build -r --verbose
|
run: cargo build -r --verbose
|
||||||
|
- name: Build no_std
|
||||||
|
run: cd tests/ensure_no_std && cargo build -r --target thumbv7m-none-eabi
|
||||||
|
- name: Test no_std
|
||||||
|
run: cargo test -r --no-default-features
|
||||||
|
- name: Build only std
|
||||||
|
run: cargo build -r --example regorus --no-default-features --features "std"
|
||||||
- name: Doc Tests
|
- name: Doc Tests
|
||||||
run: cargo test -r --doc
|
run: cargo test -r --doc
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|||||||
69
Cargo.toml
69
Cargo.toml
@@ -6,6 +6,7 @@ members = [
|
|||||||
"bindings/wasm",
|
"bindings/wasm",
|
||||||
"bindings/java",
|
"bindings/java",
|
||||||
"bindings/ruby/ext/regorusrb",
|
"bindings/ruby/ext/regorusrb",
|
||||||
|
"tests/ensure_no_std",
|
||||||
]
|
]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
@@ -15,7 +16,7 @@ version = "0.1.5"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license-file = "LICENSE"
|
license-file = "LICENSE"
|
||||||
repository = "https://github.com/microsoft/regorus"
|
repository = "https://github.com/microsoft/regorus"
|
||||||
keywords = ["interpreter", "opa", "policy-as-code", "rego"]
|
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -33,14 +34,15 @@ crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha1",
|
|||||||
deprecated = []
|
deprecated = []
|
||||||
hex = ["dep:data-encoding"]
|
hex = ["dep:data-encoding"]
|
||||||
http = []
|
http = []
|
||||||
jwt = ["dep:jsonwebtoken", "dep:data-encoding"]
|
|
||||||
glob = ["dep:wax"]
|
glob = ["dep:wax"]
|
||||||
graph = []
|
graph = []
|
||||||
jsonschema = ["dep:jsonschema"]
|
jsonschema = ["dep:jsonschema"]
|
||||||
|
jwt = ["dep:jsonwebtoken", "dep:data-encoding", "dep:itertools"]
|
||||||
|
no_std = ["lazy_static/spin_no_std"]
|
||||||
opa-runtime = []
|
opa-runtime = []
|
||||||
regex = ["dep:regex"]
|
regex = ["dep:regex"]
|
||||||
semver = ["dep:semver"]
|
semver = ["dep:semver"]
|
||||||
std = ["serde_json/std"]
|
std = ["rand/std", "rand/std_rng", "serde_json/std"]
|
||||||
time = ["dep:chrono", "dep:chrono-tz"]
|
time = ["dep:chrono", "dep:chrono-tz"]
|
||||||
uuid = ["dep:uuid"]
|
uuid = ["dep:uuid"]
|
||||||
urlquery = ["dep:url"]
|
urlquery = ["dep:url"]
|
||||||
@@ -67,41 +69,62 @@ full-opa = [
|
|||||||
"yaml"
|
"yaml"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Features that can be used in no_std environments.
|
||||||
|
# Note that: the spin_no_std feature in lazy_static must be specified.
|
||||||
|
opa-no-std = [
|
||||||
|
"arc",
|
||||||
|
"base64",
|
||||||
|
"base64url",
|
||||||
|
"coverage",
|
||||||
|
"crypto",
|
||||||
|
"deprecated",
|
||||||
|
"graph",
|
||||||
|
"hex",
|
||||||
|
"no_std",
|
||||||
|
"opa-runtime",
|
||||||
|
"regex",
|
||||||
|
"semver",
|
||||||
|
# Configure lazy_static to use spinlocks.
|
||||||
|
"lazy_static/spin_no_std"
|
||||||
|
]
|
||||||
|
|
||||||
# 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"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.45", default-features=false }
|
anyhow = { version = "1.0.45", default-features = false }
|
||||||
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc"] }
|
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc"] }
|
||||||
serde_json = { version = "1.0.89", default-features=false, features = ["alloc"] }
|
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
|
||||||
serde_yaml = {version = "0.9.16", optional = true }
|
lazy_static = { version = "1.4.0", default-features = false }
|
||||||
lazy_static = "1.4.0"
|
|
||||||
rand = "0.8.5"
|
|
||||||
num = "0.4.1"
|
|
||||||
|
|
||||||
# Crypto
|
# Crypto
|
||||||
constant_time_eq = {version = "0.3.0", optional = true}
|
constant_time_eq = {version = "0.3.0", optional = true, default-features = false }
|
||||||
hmac = {version = "0.12.1", optional = true}
|
hmac = {version = "0.12.1", optional = true, default-features = false}
|
||||||
sha2 = {version= "0.10.8", optional = true}
|
sha2 = {version= "0.10.8", optional = true, default-features = false }
|
||||||
hex = {version = "0.4.3", optional = true}
|
hex = {version = "0.4.3", optional = true, default-features = false, features = ["alloc"] }
|
||||||
sha1 = {version = "0.10.6", optional = true}
|
sha1 = {version = "0.10.6", optional = true, default-features = false }
|
||||||
md-5 = {version = "0.10.6", optional = true}
|
md-5 = {version = "0.10.6", optional = true, default-features = false }
|
||||||
|
|
||||||
data-encoding = { version = "2.4.0", optional = true }
|
data-encoding = { version = "2.4.0", optional = true, default-features=false, features = ["alloc"] }
|
||||||
scientific = { version = "0.5.2" }
|
scientific = { version = "0.5.2" }
|
||||||
|
|
||||||
regex = {version = "1.10.2", optional = true}
|
regex = {version = "1.10.2", optional = true, default-features = false }
|
||||||
semver = {version = "1.0.20", optional = true}
|
semver = {version = "1.0.20", optional = true, default-features = false }
|
||||||
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
|
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
|
||||||
url = { version = "2.5.0", optional = true }
|
url = { version = "2.5.0", optional = true }
|
||||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }
|
uuid = { version = "1.6.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
|
||||||
jsonschema = { version = "0.17.1", default-features = false, optional = true }
|
jsonschema = { version = "0.17.1", default-features = false, optional = true }
|
||||||
chrono = { version = "0.4.31", optional = true }
|
chrono = { version = "0.4.31", optional = true }
|
||||||
chrono-tz = { version = "0.8.5", optional = true }
|
chrono-tz = { version = "0.8.5", optional = true }
|
||||||
jsonwebtoken = { version = "9.2.0", optional = true }
|
jsonwebtoken = { version = "9.2.0", optional = true }
|
||||||
itertools = "0.12.1"
|
itertools = { version = "0.12.1", default-features = false, optional = true }
|
||||||
|
|
||||||
|
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
|
||||||
|
rand = { version = "0.8.5", default-features = false, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
anyhow = "1.0.45"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
clap = { version = "4.4.7", features = ["derive"] }
|
clap = { version = "4.4.7", features = ["derive"] }
|
||||||
prettydiff = { version = "0.6.4", default-features = false }
|
prettydiff = { version = "0.6.4", default-features = false }
|
||||||
@@ -131,6 +154,12 @@ name="kata"
|
|||||||
harness=false
|
harness=false
|
||||||
test=false
|
test=false
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name="regorus"
|
||||||
|
harness=false
|
||||||
|
test=false
|
||||||
|
doctest=false
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
# To build locally:
|
# To build locally:
|
||||||
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps
|
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps
|
||||||
|
|||||||
@@ -88,14 +88,14 @@ features. By default all features are enabled.
|
|||||||
The default build of regorus example program is 6.4M:
|
The default build of regorus example program is 6.4M:
|
||||||
```bash
|
```bash
|
||||||
$ cargo build -r --example regorus; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
|
$ cargo build -r --example regorus; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
|
||||||
-rwxr-xr-x 1 anand staff 6.4M Jan 19 11:23 target/release/examples/regorus*
|
-rwxr-xr-x 1 anand staff 6.3M May 11 22:03 target/release/examples/regorus*
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
When all features except for `yaml` are disabled, the binary size drops down to 2.9M.
|
When all default features are disabled, the binary size drops down to 1.9M.
|
||||||
```bash
|
```bash
|
||||||
$ cargo build -r --example regorus --features "yaml" --no-default-features; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
|
$ cargo build -r --example regorus --no-default-features; strip target/release/examples/regorus; ls -lh target/release/examples/regorus
|
||||||
-rwxr-xr-x 1 anand staff 2.9M Jan 19 11:26 target/release/examples/regorus*
|
-rwxr-xr-x 1 anand staff 1.9M May 11 22:04 target/release/examples/regorus*
|
||||||
```
|
```
|
||||||
|
|
||||||
Regorus passes the [OPA v0.64.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
Regorus passes the [OPA v0.64.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
|
||||||
|
|||||||
@@ -1,7 +1,37 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn read_file(path: &String) -> Result<String> {
|
||||||
|
std::fs::read_to_string(path).map_err(|_| anyhow!("could not read {path}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn read_value_from_yaml_file(path: &String) -> Result<regorus::Value> {
|
||||||
|
#[cfg(feature = "yaml")]
|
||||||
|
return regorus::Value::from_yaml_file(path);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "yaml"))]
|
||||||
|
bail!("regorus has not been built with yaml support");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_value_from_json_file(path: &String) -> Result<regorus::Value> {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
return regorus::Value::from_json_file(path);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
regorus::Value::from_json_str(&read_file(path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_policy_from_file(engine: &mut regorus::Engine, path: String) -> Result<()> {
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
return engine.add_policy_from_file(path);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
engine.add_policy(path.clone(), read_file(&path)?)
|
||||||
|
}
|
||||||
|
|
||||||
fn rego_eval(
|
fn rego_eval(
|
||||||
bundles: &[String],
|
bundles: &[String],
|
||||||
@@ -35,7 +65,7 @@ fn rego_eval(
|
|||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.add_policy_from_file(entry.path())?;
|
add_policy_from_file(&mut engine, entry.path().display().to_string())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,15 +73,15 @@ fn rego_eval(
|
|||||||
for file in files.iter() {
|
for file in files.iter() {
|
||||||
if file.ends_with(".rego") {
|
if file.ends_with(".rego") {
|
||||||
// Read policy file.
|
// Read policy file.
|
||||||
engine.add_policy_from_file(file)?;
|
add_policy_from_file(&mut engine, file.clone())?;
|
||||||
} else {
|
} else {
|
||||||
// Read data file.
|
// Read data file.
|
||||||
let data = if file.ends_with(".json") {
|
let data = if file.ends_with(".json") {
|
||||||
regorus::Value::from_json_file(file)?
|
read_value_from_json_file(file)?
|
||||||
} else if file.ends_with(".yaml") {
|
} else if file.ends_with(".yaml") {
|
||||||
regorus::Value::from_yaml_file(file)?
|
read_value_from_yaml_file(file)?
|
||||||
} else {
|
} else {
|
||||||
bail!("Unsupported data file `{file}`. Must be rego, json or yaml.")
|
bail!("Unsupported data file `{file}`. Must be rego, json or yaml.");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merge given data.
|
// Merge given data.
|
||||||
@@ -61,9 +91,9 @@ fn rego_eval(
|
|||||||
|
|
||||||
if let Some(file) = input {
|
if let Some(file) = input {
|
||||||
let input = if file.ends_with(".json") {
|
let input = if file.ends_with(".json") {
|
||||||
regorus::Value::from_json_file(&file)?
|
read_value_from_json_file(&file)?
|
||||||
} else if file.ends_with(".yaml") {
|
} else if file.ends_with(".yaml") {
|
||||||
regorus::Value::from_yaml_file(&file)?
|
read_value_from_yaml_file(&file)?
|
||||||
} else {
|
} else {
|
||||||
bail!("Unsupported input file `{file}`. Must be json or yaml.")
|
bail!("Unsupported input file `{file}`. Must be json or yaml.")
|
||||||
};
|
};
|
||||||
@@ -95,8 +125,12 @@ fn rego_lex(file: String, verbose: bool) -> Result<()> {
|
|||||||
use regorus::unstable::*;
|
use regorus::unstable::*;
|
||||||
|
|
||||||
// Create source.
|
// Create source.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
let source = Source::from_file(file)?;
|
let source = Source::from_file(file)?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
let source = Source::from_contents(file.clone(), read_file(&file)?)?;
|
||||||
|
|
||||||
// Create lexer.
|
// Create lexer.
|
||||||
let mut lexer = Lexer::new(&source);
|
let mut lexer = Lexer::new(&source);
|
||||||
|
|
||||||
@@ -122,8 +156,12 @@ fn rego_parse(file: String) -> Result<()> {
|
|||||||
use regorus::unstable::*;
|
use regorus::unstable::*;
|
||||||
|
|
||||||
// Create source.
|
// Create source.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
let source = Source::from_file(file)?;
|
let source = Source::from_file(file)?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
let source = Source::from_contents(file.clone(), read_file(&file)?)?;
|
||||||
|
|
||||||
// Create a parser and parse the source.
|
// Create a parser and parse the source.
|
||||||
let mut parser = Parser::new(&source)?;
|
let mut parser = Parser::new(&source)?;
|
||||||
let ast = parser.parse()?;
|
let ast = parser.parse()?;
|
||||||
|
|||||||
@@ -5,17 +5,27 @@
|
|||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
|
|
||||||
if [ -f Cargo.toml ]; then
|
if [ -f Cargo.toml ]; then
|
||||||
# Run precommit checks
|
# Run precommit checks.
|
||||||
dir=$(dirname "${BASH_SOURCE[0]}")
|
dir=$(dirname "${BASH_SOURCE[0]}")
|
||||||
"$dir/pre-commit"
|
"$dir/pre-commit"
|
||||||
|
|
||||||
# Ensure that the public API works
|
# Ensure that the public API works.
|
||||||
cargo test -r --doc
|
cargo test -r --doc
|
||||||
|
|
||||||
# Ensure that we can build with all features
|
# Ensure that no_std build succeeds.
|
||||||
|
# Build for a target that has no std available.
|
||||||
|
if command -v rustup > /dev/null; then
|
||||||
|
rustup target add thumbv7m-none-eabi
|
||||||
|
(cd tests/ensure_no_std; cargo build -r --target thumbv7m-none-eabi)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure that we can build with only std.
|
||||||
|
cargo build -r --example regorus --no-default-features --features std
|
||||||
|
|
||||||
|
# Ensure that we can build with all features.
|
||||||
cargo build -r --all-features
|
cargo build -r --all-features
|
||||||
|
|
||||||
# Ensure that all tests pass
|
# Ensure that all tests pass.
|
||||||
cargo test -r
|
cargo test -r
|
||||||
cargo test -r --test aci
|
cargo test -r --test aci
|
||||||
cargo test -r --test kata
|
cargo test -r --test kata
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
|
||||||
// Licensed under the MIT License.
|
|
||||||
|
|
||||||
use crate::ast::{Expr, Ref};
|
|
||||||
use crate::builtins;
|
|
||||||
use crate::lexer::Span;
|
|
||||||
use crate::value::Value;
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
|
|
||||||
// TODO: Should we avoid this limit?
|
|
||||||
const MAX_ARGS: u8 = core::u8::MAX;
|
|
||||||
|
|
||||||
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
|
|
||||||
m.insert("print", (print, MAX_ARGS));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_to_string(
|
|
||||||
span: &Span,
|
|
||||||
_params: &[Ref<Expr>],
|
|
||||||
args: &[Value],
|
|
||||||
_strict: bool,
|
|
||||||
) -> Result<String> {
|
|
||||||
if args.len() > MAX_ARGS as usize {
|
|
||||||
bail!(span.error("print supports up to 100 arguments"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut msg = String::default();
|
|
||||||
for a in args {
|
|
||||||
match a {
|
|
||||||
Value::Undefined => msg += " <undefined>",
|
|
||||||
Value::String(s) => msg += &format!(" {s}"),
|
|
||||||
_ => msg += &format!(" {a}"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Symbol analyzer must ensure that vars used by print are defined before
|
|
||||||
// the print statement. Scheduler must ensure the above constraint.
|
|
||||||
// Additionally interpreter must allow undefined inputs to print.
|
|
||||||
fn print(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
|
||||||
let msg = print_to_string(span, params, args, strict)?;
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
if !msg.is_empty() {
|
|
||||||
std::eprintln!("{}", &msg[1..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Value::Bool(true))
|
|
||||||
}
|
|
||||||
@@ -63,7 +63,13 @@ fn base64_decode(
|
|||||||
ensure_args_count(span, name, params, args, 1)?;
|
ensure_args_count(span, name, params, args, 1)?;
|
||||||
|
|
||||||
let encoded_str = ensure_string(name, ¶ms[0], &args[0])?;
|
let encoded_str = ensure_string(name, ¶ms[0], &args[0])?;
|
||||||
let decoded_bytes = data_encoding::BASE64.decode(encoded_str.as_bytes())?;
|
let decoded_bytes = data_encoding::BASE64
|
||||||
|
.decode(encoded_str.as_bytes())
|
||||||
|
.map_err(|e| {
|
||||||
|
params[0]
|
||||||
|
.span()
|
||||||
|
.error(&format!("decode failed\nCaused by\n{e}"))
|
||||||
|
})?;
|
||||||
Ok(Value::String(
|
Ok(Value::String(
|
||||||
String::from_utf8_lossy(&decoded_bytes).into(),
|
String::from_utf8_lossy(&decoded_bytes).into(),
|
||||||
))
|
))
|
||||||
@@ -173,7 +179,13 @@ fn hex_decode(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
|
|||||||
ensure_args_count(span, name, params, args, 1)?;
|
ensure_args_count(span, name, params, args, 1)?;
|
||||||
|
|
||||||
let encoded_str = ensure_string(name, ¶ms[0], &args[0])?;
|
let encoded_str = ensure_string(name, ¶ms[0], &args[0])?;
|
||||||
let decoded_bytes = data_encoding::HEXLOWER_PERMISSIVE.decode(encoded_str.as_bytes())?;
|
let decoded_bytes = data_encoding::HEXLOWER_PERMISSIVE
|
||||||
|
.decode(encoded_str.as_bytes())
|
||||||
|
.map_err(|e| {
|
||||||
|
params[0]
|
||||||
|
.span()
|
||||||
|
.error(&format!("decode failure\nCaused by\n{e}"))
|
||||||
|
})?;
|
||||||
Ok(Value::String(
|
Ok(Value::String(
|
||||||
String::from_utf8_lossy(&decoded_bytes).into(),
|
String::from_utf8_lossy(&decoded_bytes).into(),
|
||||||
))
|
))
|
||||||
@@ -361,11 +373,9 @@ fn json_is_valid(
|
|||||||
fn json_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
fn json_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||||
let name = "json.marshal";
|
let name = "json.marshal";
|
||||||
ensure_args_count(span, name, params, args, 1)?;
|
ensure_args_count(span, name, params, args, 1)?;
|
||||||
Ok(Value::String(
|
Ok(Value::from(serde_json::to_string(&args[0]).map_err(
|
||||||
serde_json::to_string(&args[0])
|
|e| span.error(&format!("could not serialize to json\nCaused by\n{e}")),
|
||||||
.with_context(|| span.error("could not serialize to json"))?
|
)?))
|
||||||
.into(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_marshal_with_options(
|
fn json_marshal_with_options(
|
||||||
@@ -406,15 +416,13 @@ fn json_marshal_with_options(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !pretty || options.is_empty() {
|
if !pretty || options.is_empty() {
|
||||||
return Ok(Value::String(
|
return Ok(Value::from(serde_json::to_string(&args[0]).map_err(
|
||||||
serde_json::to_string(&args[0])
|
|e| span.error(&format!("could not serialize to json\nCaused by\n{e}")),
|
||||||
.with_context(|| span.error("could not serialize to json"))?
|
)?));
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let lines: Vec<String> = serde_json::to_string_pretty(&args[0])
|
let lines: Vec<String> = serde_json::to_string_pretty(&args[0])
|
||||||
.with_context(|| span.error("could not serialize to json"))?
|
.map_err(|e| span.error(&format!("could not serialize to json\nCaused by\n{e}")))?
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(|line| {
|
.map(|line| {
|
||||||
let mut line = line.to_string();
|
let mut line = line.to_string();
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ mod conversions;
|
|||||||
|
|
||||||
#[cfg(feature = "crypto")]
|
#[cfg(feature = "crypto")]
|
||||||
mod crypto;
|
mod crypto;
|
||||||
mod debugging;
|
|
||||||
#[cfg(feature = "deprecated")]
|
#[cfg(feature = "deprecated")]
|
||||||
pub mod deprecated;
|
pub mod deprecated;
|
||||||
mod encoding;
|
mod encoding;
|
||||||
@@ -54,8 +53,6 @@ use lazy_static::lazy_static;
|
|||||||
|
|
||||||
pub type BuiltinFcn = (fn(&Span, &[Ref<Expr>], &[Value], bool) -> Result<Value>, u8);
|
pub type BuiltinFcn = (fn(&Span, &[Ref<Expr>], &[Value], bool) -> Result<Value>, u8);
|
||||||
|
|
||||||
pub use debugging::print_to_string;
|
|
||||||
|
|
||||||
#[cfg(feature = "deprecated")]
|
#[cfg(feature = "deprecated")]
|
||||||
pub use deprecated::DEPRECATED;
|
pub use deprecated::DEPRECATED;
|
||||||
|
|
||||||
@@ -104,7 +101,6 @@ lazy_static! {
|
|||||||
//rego::register(&mut m);
|
//rego::register(&mut m);
|
||||||
#[cfg(feature = "opa-runtime")]
|
#[cfg(feature = "opa-runtime")]
|
||||||
opa::register(&mut m);
|
opa::register(&mut m);
|
||||||
debugging::register(&mut m);
|
|
||||||
tracing::register(&mut m);
|
tracing::register(&mut m);
|
||||||
units::register(&mut m);
|
units::register(&mut m);
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,15 @@
|
|||||||
|
|
||||||
use crate::ast::{ArithOp, Expr, Ref};
|
use crate::ast::{ArithOp, Expr, Ref};
|
||||||
use crate::builtins;
|
use crate::builtins;
|
||||||
use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string};
|
use crate::builtins::utils::{ensure_args_count, ensure_numeric};
|
||||||
use crate::lexer::Span;
|
use crate::lexer::Span;
|
||||||
use crate::number::Number;
|
use crate::number::Number;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
|
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
|
||||||
@@ -18,6 +20,7 @@ pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn
|
|||||||
m.insert("floor", (floor, 1));
|
m.insert("floor", (floor, 1));
|
||||||
m.insert("numbers.range", (range, 2));
|
m.insert("numbers.range", (range, 2));
|
||||||
m.insert("numbers.range_step", (range_step, 3));
|
m.insert("numbers.range_step", (range_step, 3));
|
||||||
|
#[cfg(feature = "std")]
|
||||||
m.insert("rand.intn", (intn, 2));
|
m.insert("rand.intn", (intn, 2));
|
||||||
m.insert("round", (round, 1));
|
m.insert("round", (round, 1));
|
||||||
}
|
}
|
||||||
@@ -155,10 +158,11 @@ fn round(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
fn intn(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
fn intn(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||||
let fcn = "rand.intn";
|
let fcn = "rand.intn";
|
||||||
ensure_args_count(span, fcn, params, args, 2)?;
|
ensure_args_count(span, fcn, params, args, 2)?;
|
||||||
let _ = ensure_string(fcn, ¶ms[0], &args[0])?;
|
let _ = crate::builtins::utils::ensure_string(fcn, ¶ms[0], &args[0])?;
|
||||||
let n = ensure_numeric(fcn, ¶ms[0], &args[1])?;
|
let n = ensure_numeric(fcn, ¶ms[0], &args[1])?;
|
||||||
|
|
||||||
Ok(match n.as_u64() {
|
Ok(match n.as_u64() {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ fn opa_runtime(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool)
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Emitting environment variables could lead to confidential data being leaked.
|
// Emitting environment variables could lead to confidential data being leaked.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
if false {
|
if false {
|
||||||
obj.insert(
|
obj.insert(
|
||||||
Value::String("env".into()),
|
Value::String("env".into()),
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ fn compare(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
|
|||||||
|
|
||||||
let v1 = ensure_string(name, ¶ms[0], &args[0])?;
|
let v1 = ensure_string(name, ¶ms[0], &args[0])?;
|
||||||
let v2 = ensure_string(name, ¶ms[1], &args[1])?;
|
let v2 = ensure_string(name, ¶ms[1], &args[1])?;
|
||||||
let version1 = Version::parse(&v1)?;
|
let version1 = Version::parse(&v1).map_err(|_| params[0].span().error("invalid semver"))?;
|
||||||
let version2 = Version::parse(&v2)?;
|
let version2 = Version::parse(&v2).map_err(|_| params[0].span().error("invalid semver"))?;
|
||||||
let result = match version1.cmp_precedence(&version2) {
|
let result = match version1.cmp_precedence(&version2) {
|
||||||
Ordering::Less => -1,
|
Ordering::Less => -1,
|
||||||
Ordering::Equal => 0,
|
Ordering::Equal => 0,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::builtins::time;
|
|||||||
use crate::builtins::utils::{ensure_args_count, ensure_string};
|
use crate::builtins::utils::{ensure_args_count, ensure_string};
|
||||||
use crate::lexer::Span;
|
use crate::lexer::Span;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
@@ -21,7 +22,8 @@ fn sleep(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
|
|||||||
ensure_args_count(span, name, params, args, 1)?;
|
ensure_args_count(span, name, params, args, 1)?;
|
||||||
|
|
||||||
let val = ensure_string(name, ¶ms[0], &args[0])?;
|
let val = ensure_string(name, ¶ms[0], &args[0])?;
|
||||||
let dur = time::compat::parse_duration(val.as_ref())?;
|
let dur = time::compat::parse_duration(val.as_ref())
|
||||||
|
.map_err(|e| params[0].span().error(&format!("{e}")))?;
|
||||||
|
|
||||||
thread::sleep(dur.to_std()?);
|
thread::sleep(dur.to_std()?);
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ fn parse_duration_ns(
|
|||||||
ensure_args_count(span, name, params, args, 1)?;
|
ensure_args_count(span, name, params, args, 1)?;
|
||||||
|
|
||||||
let value = ensure_string(name, ¶ms[0], &args[0])?;
|
let value = ensure_string(name, ¶ms[0], &args[0])?;
|
||||||
let dur = compat::parse_duration(value.as_ref())?;
|
let dur = compat::parse_duration(value.as_ref()).map_err(anyhow::Error::msg)?;
|
||||||
safe_timestamp_nanos(span, strict, dur.num_nanoseconds())
|
safe_timestamp_nanos(span, strict, dur.num_nanoseconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::iter;
|
use core::iter;
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
use chrono::{
|
use chrono::{
|
||||||
@@ -72,8 +71,6 @@ impl fmt::Display for ParseDurationError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for ParseDurationError {}
|
|
||||||
|
|
||||||
// Parses a duration string in the form of `10h12m45s`.
|
// Parses a duration string in the form of `10h12m45s`.
|
||||||
//
|
//
|
||||||
// Adapted from Go's `time.ParseDuration`:
|
// Adapted from Go's `time.ParseDuration`:
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ pub struct Interpreter {
|
|||||||
builtins_cache: BTreeMap<(&'static str, Vec<Value>), Value>,
|
builtins_cache: BTreeMap<(&'static str, Vec<Value>), Value>,
|
||||||
no_rules_lookup: bool,
|
no_rules_lookup: bool,
|
||||||
traces: Option<Vec<Rc<str>>>,
|
traces: Option<Vec<Rc<str>>>,
|
||||||
|
#[cfg(feature = "deprecated")]
|
||||||
allow_deprecated: bool,
|
allow_deprecated: bool,
|
||||||
strict_builtin_errors: bool,
|
strict_builtin_errors: bool,
|
||||||
imports: BTreeMap<String, Ref<Expr>>,
|
imports: BTreeMap<String, Ref<Expr>>,
|
||||||
@@ -186,6 +187,7 @@ impl Interpreter {
|
|||||||
builtins_cache: BTreeMap::new(),
|
builtins_cache: BTreeMap::new(),
|
||||||
no_rules_lookup: false,
|
no_rules_lookup: false,
|
||||||
traces: None,
|
traces: None,
|
||||||
|
#[cfg(feature = "deprecated")]
|
||||||
allow_deprecated: true,
|
allow_deprecated: true,
|
||||||
strict_builtin_errors: true,
|
strict_builtin_errors: true,
|
||||||
imports: BTreeMap::default(),
|
imports: BTreeMap::default(),
|
||||||
@@ -1208,7 +1210,7 @@ impl Interpreter {
|
|||||||
let mut target = path.join(".");
|
let mut target = path.join(".");
|
||||||
|
|
||||||
let mut target_is_function = self.lookup_function_by_name(&target).is_some()
|
let mut target_is_function = self.lookup_function_by_name(&target).is_some()
|
||||||
|| matches!(self.lookup_builtin(wm.refr.span(), &target), Ok(Some(_)));
|
|| self.is_builtin(wm.refr.span(), &target);
|
||||||
|
|
||||||
if !target_is_function
|
if !target_is_function
|
||||||
&& !target.starts_with("data.")
|
&& !target.starts_with("data.")
|
||||||
@@ -1217,7 +1219,7 @@ impl Interpreter {
|
|||||||
{
|
{
|
||||||
// target must be a function.
|
// target must be a function.
|
||||||
if self.lookup_function_by_name(&target).is_none()
|
if self.lookup_function_by_name(&target).is_none()
|
||||||
&& !matches!(self.lookup_builtin(wm.refr.span(), &target), Ok(Some(_)))
|
&& !self.is_builtin(wm.refr.span(), &target)
|
||||||
{
|
{
|
||||||
// Prefix target with current module path.
|
// Prefix target with current module path.
|
||||||
target = self.current_module_path.clone() + "." + ⌖
|
target = self.current_module_path.clone() + "." + ⌖
|
||||||
@@ -1244,10 +1246,7 @@ impl Interpreter {
|
|||||||
// Lookup without current module path prefixed.
|
// Lookup without current module path prefixed.
|
||||||
function_path = get_path_string(&wm.r#as, None)?;
|
function_path = get_path_string(&wm.r#as, None)?;
|
||||||
if self.lookup_function_by_name(&function_path).is_none()
|
if self.lookup_function_by_name(&function_path).is_none()
|
||||||
&& !matches!(
|
&& !self.is_builtin(wm.r#as.span(), &function_path)
|
||||||
self.lookup_builtin(wm.r#as.span(), &function_path),
|
|
||||||
Ok(Some(_))
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
// bail!(wm.r#as.span().error("could not evaluate expression"));
|
// bail!(wm.r#as.span().error("could not evaluate expression"));
|
||||||
skip_exec = true;
|
skip_exec = true;
|
||||||
@@ -1743,9 +1742,9 @@ impl Interpreter {
|
|||||||
span.col,
|
span.col,
|
||||||
format!(
|
format!(
|
||||||
"value for key `{}` generated multiple times: `{}` and `{}`",
|
"value for key `{}` generated multiple times: `{}` and `{}`",
|
||||||
serde_json::to_string_pretty(&key)?,
|
serde_json::to_string_pretty(&key).map_err(anyhow::Error::msg)?,
|
||||||
serde_json::to_string_pretty(&pv)?,
|
serde_json::to_string_pretty(&pv).map_err(anyhow::Error::msg)?,
|
||||||
serde_json::to_string_pretty(&value)?,
|
serde_json::to_string_pretty(&value).map_err(anyhow::Error::msg)?,
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
));
|
));
|
||||||
@@ -2121,27 +2120,11 @@ impl Interpreter {
|
|||||||
name: &str,
|
name: &str,
|
||||||
builtin: builtins::BuiltinFcn,
|
builtin: builtins::BuiltinFcn,
|
||||||
params: &[ExprRef],
|
params: &[ExprRef],
|
||||||
|
args: Vec<Value>,
|
||||||
) -> Result<Value> {
|
) -> Result<Value> {
|
||||||
let mut args = vec![];
|
// If any argument is undefined, then the call is undefined.
|
||||||
let is_print = name == "print"; // TODO: with modifier
|
if args.iter().any(|a| a == &Value::Undefined) {
|
||||||
let allow_undefined = is_print;
|
return Ok(Value::Undefined);
|
||||||
for p in params {
|
|
||||||
match self.eval_expr(p)? {
|
|
||||||
// If any argument is undefined, then the call is undefined.
|
|
||||||
Value::Undefined if !allow_undefined => return Ok(Value::Undefined),
|
|
||||||
p => args.push(p),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_print && self.gather_prints {
|
|
||||||
// Do not print to stderr. Instead, gather.
|
|
||||||
let msg =
|
|
||||||
builtins::print_to_string(span, params, &args[..], self.strict_builtin_errors)?;
|
|
||||||
|
|
||||||
// Prefix location information.
|
|
||||||
self.prints
|
|
||||||
.push(format!("{}:{}: {msg}", span.source.file(), span.line));
|
|
||||||
return Ok(Value::Bool(true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let cache = builtins::must_cache(name);
|
let cache = builtins::must_cache(name);
|
||||||
@@ -2173,6 +2156,7 @@ impl Interpreter {
|
|||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
fn lookup_builtin(&self, span: &Span, path: &str) -> Result<Option<&BuiltinFcn>> {
|
fn lookup_builtin(&self, span: &Span, path: &str) -> Result<Option<&BuiltinFcn>> {
|
||||||
if let Some(builtin) = builtins::BUILTINS.get(path) {
|
if let Some(builtin) = builtins::BUILTINS.get(path) {
|
||||||
return Ok(Some(builtin));
|
return Ok(Some(builtin));
|
||||||
@@ -2187,12 +2171,92 @@ impl Interpreter {
|
|||||||
return Ok(Some(builtin));
|
return Ok(Some(builtin));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark as used when deprecated feature is not enabled.
|
|
||||||
core::convert::identity((span, self.allow_deprecated));
|
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_builtin(&self, span: &Span, path: &str) -> bool {
|
||||||
|
path == "print" || matches!(self.lookup_builtin(span, path), Ok(Some(_)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_printable(v: &Value, s: &mut String) {
|
||||||
|
match v {
|
||||||
|
Value::Array(array) => {
|
||||||
|
s.push('[');
|
||||||
|
for (idx, e) in array.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
s.push_str(", ");
|
||||||
|
}
|
||||||
|
Self::to_printable(e, s);
|
||||||
|
}
|
||||||
|
s.push(']');
|
||||||
|
}
|
||||||
|
Value::Set(set) => {
|
||||||
|
s.push('{');
|
||||||
|
for (idx, e) in set.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
s.push_str(", ");
|
||||||
|
}
|
||||||
|
Self::to_printable(e, s);
|
||||||
|
}
|
||||||
|
s.push('}');
|
||||||
|
}
|
||||||
|
Value::Object(map) => {
|
||||||
|
s.push('{');
|
||||||
|
for (idx, (k, v)) in map.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
s.push_str(", ");
|
||||||
|
}
|
||||||
|
Self::to_printable(k, s);
|
||||||
|
s.push_str(": ");
|
||||||
|
Self::to_printable(v, s);
|
||||||
|
}
|
||||||
|
s.push('}');
|
||||||
|
}
|
||||||
|
v => s.push_str(&format!("{v}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_print(&mut self, span: &Span, params: &[ExprRef], args: Vec<Value>) -> Result<Value> {
|
||||||
|
const MAX_ARGS: u8 = 100;
|
||||||
|
if args.len() > MAX_ARGS as usize {
|
||||||
|
bail!(span.error(&format!("print supports upto {MAX_ARGS} arguments")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not compiling for std target, return early if gathering is not
|
||||||
|
// requested.
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
if !self.gather_prints {
|
||||||
|
return Ok(Value::Bool(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut msg = String::default();
|
||||||
|
for (i, p) in params.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
msg.push(' ');
|
||||||
|
}
|
||||||
|
match self.eval_expr(p)? {
|
||||||
|
Value::Undefined => msg.push_str("<undefined>"),
|
||||||
|
// Do not print quotes for string values.
|
||||||
|
Value::String(s) => msg.push_str(&format!("{s}")),
|
||||||
|
a => Self::to_printable(&a, &mut msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.gather_prints {
|
||||||
|
// Prefix location information.
|
||||||
|
self.prints
|
||||||
|
.push(format!("{}:{}: {msg}", span.source.file(), span.line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print to stderr only if not gathering.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
if !self.gather_prints {
|
||||||
|
std::eprintln!("{msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Bool(true))
|
||||||
|
}
|
||||||
|
|
||||||
fn eval_call_impl(
|
fn eval_call_impl(
|
||||||
&mut self,
|
&mut self,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
@@ -2261,10 +2325,18 @@ impl Interpreter {
|
|||||||
else if let Some(ext) = self.extensions.get_mut(&fcn_path) {
|
else if let Some(ext) = self.extensions.get_mut(&fcn_path) {
|
||||||
extension = Some(ext);
|
extension = Some(ext);
|
||||||
(&empty, None)
|
(&empty, None)
|
||||||
|
} else if fcn_path == "print" {
|
||||||
|
return self.eval_print(span, params, param_values);
|
||||||
}
|
}
|
||||||
// Look up builtin function.
|
// Look up builtin function.
|
||||||
else if let Some(builtin) = self.lookup_builtin(span, &fcn_path)? {
|
else if let Some(builtin) = self.lookup_builtin(span, &fcn_path)? {
|
||||||
let r = self.eval_builtin_call(span, &fcn_path.clone(), *builtin, params);
|
let r = self.eval_builtin_call(
|
||||||
|
span,
|
||||||
|
&fcn_path.clone(),
|
||||||
|
*builtin,
|
||||||
|
params,
|
||||||
|
param_values,
|
||||||
|
);
|
||||||
if let Some(with_functions) = with_functions_saved {
|
if let Some(with_functions) = with_functions_saved {
|
||||||
self.with_functions = with_functions;
|
self.with_functions = with_functions;
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/lib.rs
19
src/lib.rs
@@ -404,30 +404,29 @@ pub mod coverage {
|
|||||||
/// <img src="https://github.com/microsoft/regorus/blob/main/docs/coverage.png?raw=true">
|
/// <img src="https://github.com/microsoft/regorus/blob/main/docs/coverage.png?raw=true">
|
||||||
|
|
||||||
pub fn to_colored_string(&self) -> anyhow::Result<String> {
|
pub fn to_colored_string(&self) -> anyhow::Result<String> {
|
||||||
use std::io::Write;
|
let mut s = String::default();
|
||||||
let mut s = Vec::new();
|
s.push_str("COVERAGE REPORT:\n");
|
||||||
writeln!(&mut s, "COVERAGE REPORT:")?;
|
|
||||||
for file in self.files.iter() {
|
for file in self.files.iter() {
|
||||||
if file.not_covered.is_empty() {
|
if file.not_covered.is_empty() {
|
||||||
writeln!(&mut s, "{} has full coverage", file.path)?;
|
s.push_str(&format!("{} has full coverage\n", file.path));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(&mut s, "{}:", file.path)?;
|
s.push_str(&format!("{}:", file.path));
|
||||||
for (line, code) in file.code.split('\n').enumerate() {
|
for (line, code) in file.code.split('\n').enumerate() {
|
||||||
let line = line as u32 + 1;
|
let line = line as u32 + 1;
|
||||||
if file.not_covered.contains(&line) {
|
if file.not_covered.contains(&line) {
|
||||||
writeln!(&mut s, "\x1b[31m {line:4} {code}\x1b[0m")?;
|
s.push_str(&format!("\x1b[31m {line:4} {code}\x1b[0m\n"));
|
||||||
} else if file.covered.contains(&line) {
|
} else if file.covered.contains(&line) {
|
||||||
writeln!(&mut s, "\x1b[32m {line:4} {code}\x1b[0m")?;
|
s.push_str(&format!("\x1b[32m {line:4} {code}\x1b[0m\n"));
|
||||||
} else {
|
} else {
|
||||||
writeln!(&mut s, " {line:4} {code}")?;
|
s.push_str(&format!(" {line:4} {code}\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(&mut s)?;
|
s.push('\n');
|
||||||
Ok(core::str::from_utf8(&s)?.to_string())
|
Ok(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,16 +70,19 @@ impl<'source> Parser<'source> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn warn_future_keyword(&self) {
|
pub fn warn_future_keyword(&self) {
|
||||||
let kw = self.token_text();
|
|
||||||
let msg = format!(
|
|
||||||
"`{kw}` will be treated as identifier due to missing `import future.keywords.{kw}`"
|
|
||||||
);
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
std::println!(
|
{
|
||||||
"{}",
|
let kw = self.token_text();
|
||||||
self.source
|
let msg = format!(
|
||||||
.message(self.tok.1.line, self.tok.1.col, "warning", &msg)
|
"`{kw}` will be treated as identifier due to missing `import future.keywords.{kw}`"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
std::println!(
|
||||||
|
"{}",
|
||||||
|
self.source
|
||||||
|
.message(self.tok.1.line, self.tok.1.col, "warning", &msg)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> {
|
pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> {
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ pub fn schedule<Str: Clone + cmp::Ord + fmt::Debug>(
|
|||||||
empty: &Str,
|
empty: &Str,
|
||||||
) -> Result<SortResult> {
|
) -> Result<SortResult> {
|
||||||
let num_statements = infos.len();
|
let num_statements = infos.len();
|
||||||
let orig_infos: Vec<&StmtInfo<Str>> = infos.iter().collect();
|
|
||||||
|
|
||||||
// Mapping from each var to the list of statements that define it.
|
// Mapping from each var to the list of statements that define it.
|
||||||
let mut defining_stmts: BTreeMap<Str, Vec<usize>> = BTreeMap::new();
|
let mut defining_stmts: BTreeMap<Str, Vec<usize>> = BTreeMap::new();
|
||||||
@@ -198,7 +197,7 @@ pub fn schedule<Str: Clone + cmp::Ord + fmt::Debug>(
|
|||||||
|
|
||||||
if order.len() != num_statements {
|
if order.len() != num_statements {
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
std::eprintln!("could not schedule all statements {order:?} {orig_infos:?}");
|
std::eprintln!("could not schedule all statements {order:?}");
|
||||||
return Ok(SortResult::Order(
|
return Ok(SortResult::Order(
|
||||||
(0..num_statements).map(|i| i as u16).collect(),
|
(0..num_statements).map(|i| i as u16).collect(),
|
||||||
));
|
));
|
||||||
@@ -633,8 +632,8 @@ impl Analyzer {
|
|||||||
) -> Result<(Vec<SourceStr>, Vec<Ref<Expr>>)> {
|
) -> Result<(Vec<SourceStr>, Vec<Ref<Expr>>)> {
|
||||||
let mut used_vars = vec![];
|
let mut used_vars = vec![];
|
||||||
let mut comprs = vec![];
|
let mut comprs = vec![];
|
||||||
|
#[cfg(feature = "deprecated")]
|
||||||
let full_expr = expr;
|
let full_expr = expr;
|
||||||
core::convert::identity(&full_expr);
|
|
||||||
traverse(expr, &mut |e| match e.as_ref() {
|
traverse(expr, &mut |e| match e.as_ref() {
|
||||||
Var(v) if !matches!(v.0.text(), "_" | "input" | "data") => {
|
Var(v) if !matches!(v.0.text(), "_" | "input" | "data") => {
|
||||||
let name = v.0.source_str();
|
let name = v.0.source_str();
|
||||||
|
|||||||
@@ -277,6 +277,35 @@ fn yaml_test_impl(file: &str) -> Result<()> {
|
|||||||
let yaml_str = std::fs::read_to_string(file)?;
|
let yaml_str = std::fs::read_to_string(file)?;
|
||||||
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
{
|
||||||
|
// Skip tests that depend on bultins that need std feature.
|
||||||
|
let skip = [
|
||||||
|
"intn.yaml",
|
||||||
|
"is_valid.yaml",
|
||||||
|
"add_date.yaml",
|
||||||
|
"date.yaml",
|
||||||
|
"clock.yaml",
|
||||||
|
"compare.yaml",
|
||||||
|
"diff.yaml",
|
||||||
|
"format.yaml",
|
||||||
|
"now_ns.yaml",
|
||||||
|
"parse_duration_ns.yaml",
|
||||||
|
"parse_ns.yaml",
|
||||||
|
"parse_rfc3339_ns.yaml",
|
||||||
|
"weekday.yaml",
|
||||||
|
"generate.yaml",
|
||||||
|
"parse.yaml",
|
||||||
|
"tests.yaml",
|
||||||
|
];
|
||||||
|
for s in skip {
|
||||||
|
if file.contains(s) {
|
||||||
|
std::println!("skipped {file} in no_std mode.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::println!("running {file}");
|
std::println!("running {file}");
|
||||||
|
|
||||||
for case in test.cases {
|
for case in test.cases {
|
||||||
|
|||||||
@@ -406,6 +406,7 @@ impl Value {
|
|||||||
|
|
||||||
/// Deserialize a value from a file containing YAML.
|
/// Deserialize a value from a file containing YAML.
|
||||||
/// Note: Deserialization from YAML does not support arbitrary precision numbers.
|
/// Note: Deserialization from YAML does not support arbitrary precision numbers.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
#[cfg(feature = "yaml")]
|
#[cfg(feature = "yaml")]
|
||||||
pub fn from_yaml_file(path: &String) -> Result<Value> {
|
pub fn from_yaml_file(path: &String) -> Result<Value> {
|
||||||
match std::fs::read_to_string(path) {
|
match std::fs::read_to_string(path) {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
#![allow(unused)]
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use regorus::*;
|
use regorus::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "arc")]
|
||||||
// Ensure that types can be s
|
// Ensure that types can be s
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref VALUE: Value = Value::Null;
|
static ref VALUE: Value = Value::Null;
|
||||||
@@ -14,6 +16,7 @@ lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "arc")]
|
||||||
fn shared_engine() -> anyhow::Result<()> {
|
fn shared_engine() -> anyhow::Result<()> {
|
||||||
let e_guard = ENGINE.lock();
|
let e_guard = ENGINE.lock();
|
||||||
let mut engine = e_guard.expect("failed to lock engine");
|
let mut engine = e_guard.expect("failed to lock engine");
|
||||||
|
|||||||
10
tests/ensure_no_std/Cargo.toml
Normal file
10
tests/ensure_no_std/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "ensure_no_std"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { version = "1.0.83", default-features = false }
|
||||||
|
regorus = { path = "../..", default-features = false, features = ["opa-no-std"] }
|
||||||
16
tests/ensure_no_std/src/main.rs
Normal file
16
tests/ensure_no_std/src/main.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn _start() -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
@@ -225,6 +225,7 @@ fn invalid_line() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "std")]
|
||||||
fn file_more_than_64_kb_size() -> Result<()> {
|
fn file_more_than_64_kb_size() -> Result<()> {
|
||||||
let source = Source::from_file("tests/kata/data/large.rego")?;
|
let source = Source::from_file("tests/kata/data/large.rego")?;
|
||||||
let mut lexer = Lexer::new(&source);
|
let mut lexer = Lexer::new(&source);
|
||||||
|
|||||||
@@ -8,6 +8,3 @@ mod engine;
|
|||||||
mod lexer;
|
mod lexer;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
#[cfg(feature = "arc")]
|
|
||||||
mod arc;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user