From 76b50d36dceccf45af6d0822c0c8c01a02c41254 Mon Sep 17 00:00:00 2001 From: Magnus Kulke Date: Wed, 24 Apr 2024 13:10:08 +0200 Subject: [PATCH] errors: replace anyhow errors with thiserror fixes #161 This change replaces stringly-typed anyhow errors with thiserror-constructed explicit error enums. The following strategy has been used in the conversion: - Every module maintains its own error enum - Exception is builtins/*.rs in which all errors are consolidated in one enum to avoid duplication - Source errors are being wrapped and propagated in the error string - Crate-specific businesslogic errors have been convernted into their own enum - A simple `bail!` macro has been added to reduce changes - Extension fn's require a `Box` error type. it would be nice if the Extension fn's could have a result with trait bound `std::error::Error` but that requires some unergonomic type gymnastics on the trait + impls - A local type alias for `Result` has been added to modules to reduce changes in the signatures where reasonable. Signed-off-by: Magnus Kulke --- .gitignore | 2 +- Cargo.toml | 3 +- bindings/ffi/RegorusFFI.g.cs | 1 - src/builtins/aggregates.rs | 4 +- src/builtins/arrays.rs | 24 +++- src/builtins/bitwise.rs | 3 +- src/builtins/comparison.rs | 3 +- src/builtins/conversions.rs | 4 +- src/builtins/crypto.rs | 25 ++-- src/builtins/debugging.rs | 4 +- src/builtins/deprecated.rs | 5 +- src/builtins/encoding.rs | 21 ++- src/builtins/glob.rs | 6 +- src/builtins/graph.rs | 8 +- src/builtins/http.rs | 9 +- src/builtins/jwt.rs | 4 +- src/builtins/mod.rs | 74 +++++++++- src/builtins/numbers.rs | 5 +- src/builtins/objects.rs | 6 +- src/builtins/opa.rs | 3 +- src/builtins/regex.rs | 39 ++++-- src/builtins/semver.rs | 5 +- src/builtins/sets.rs | 4 +- src/builtins/strings.rs | 18 +-- src/builtins/test.rs | 3 +- src/builtins/time.rs | 87 +++++++++--- src/builtins/time/compat.rs | 1 + src/builtins/time/diff.rs | 6 +- src/builtins/tracing.rs | 3 +- src/builtins/types.rs | 3 +- src/builtins/units.rs | 6 +- src/builtins/utils.rs | 59 +++++--- src/builtins/uuid.rs | 4 +- src/engine.rs | 66 ++++++--- src/interpreter.rs | 257 +++++++++++++++++++++++------------ src/lexer.rs | 45 +++--- src/lib.rs | 25 +++- src/number.rs | 55 ++++---- src/parser.rs | 222 ++++++++++++++++-------------- src/scheduler.rs | 44 ++++-- src/utils.rs | 42 ++++-- src/value.rs | 176 ++++++++++++++---------- tests/aci/main.rs | 3 +- tests/engine/mod.rs | 3 +- tests/opa.rs | 3 +- tests/parser/mod.rs | 5 +- 46 files changed, 925 insertions(+), 473 deletions(-) diff --git a/.gitignore b/.gitignore index 33384ff6..a0600148 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ Cargo.lock .vscode/ # worktrees -worktrees/ \ No newline at end of file +worktrees/ diff --git a/Cargo.toml b/Cargo.toml index 351d79c6..5fa5c9fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,6 @@ full-opa = [ opa-testutil = [] [dependencies] -anyhow = { version = "1.0.45", default-features=false } serde = {version = "1.0.150", features = ["derive", "rc"] } serde_json = "1.0.89" serde_yaml = {version = "0.9.16", optional = true } @@ -98,8 +97,10 @@ chrono = { version = "0.4.31", optional = true } chrono-tz = { version = "0.8.5", optional = true } jsonwebtoken = { version = "9.2.0", optional = true } itertools = "0.12.1" +thiserror = "1.0.59" [dev-dependencies] +anyhow = "1.0" cfg-if = "1.0.0" clap = { version = "4.4.7", features = ["derive"] } prettydiff = { version = "0.6.4", default-features = false } diff --git a/bindings/ffi/RegorusFFI.g.cs b/bindings/ffi/RegorusFFI.g.cs index 3b3b12f1..84b9ac1d 100644 --- a/bindings/ffi/RegorusFFI.g.cs +++ b/bindings/ffi/RegorusFFI.g.cs @@ -85,4 +85,3 @@ internal enum RegorusStatus : uint } - \ No newline at end of file diff --git a/src/builtins/aggregates.rs b/src/builtins/aggregates.rs index c6101722..90aa6eea 100644 --- a/src/builtins/aggregates.rs +++ b/src/builtins/aggregates.rs @@ -2,15 +2,17 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_numeric}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::number::Number; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("count", (count, 1)); diff --git a/src/builtins/arrays.rs b/src/builtins/arrays.rs index bd80d7b2..a9c70280 100644 --- a/src/builtins/arrays.rs +++ b/src/builtins/arrays.rs @@ -4,21 +4,25 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_array, ensure_numeric}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::Rc; use crate::Value; use std::collections::HashMap; -use anyhow::Result; - pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("array.concat", (concat, 2)); m.insert("array.reverse", (reverse, 1)); m.insert("array.slice", (slice, 3)); } -fn concat(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn concat( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "array.concat"; ensure_args_count(span, name, params, args, 2)?; let mut v1 = ensure_array(name, ¶ms[0], args[0].clone())?; @@ -28,7 +32,12 @@ fn concat(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> R Ok(Value::Array(v1)) } -fn reverse(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn reverse( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "array.reverse"; ensure_args_count(span, name, params, args, 1)?; @@ -37,7 +46,12 @@ fn reverse(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Ok(Value::Array(v1)) } -fn slice(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn slice( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "array.slice"; ensure_args_count(span, name, params, args, 3)?; diff --git a/src/builtins/bitwise.rs b/src/builtins/bitwise.rs index deac3041..e97c84c9 100644 --- a/src/builtins/bitwise.rs +++ b/src/builtins/bitwise.rs @@ -4,13 +4,14 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_numeric}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::Result; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("bits.and", (and, 2)); diff --git a/src/builtins/comparison.rs b/src/builtins/comparison.rs index 574f57ed..8a325800 100644 --- a/src/builtins/comparison.rs +++ b/src/builtins/comparison.rs @@ -2,9 +2,10 @@ // Licensed under the MIT License. use crate::ast::BoolOp; +use crate::builtins::BuiltinError; use crate::value::Value; -use anyhow::Result; +type Result = std::result::Result; /// compare two values /// diff --git a/src/builtins/conversions.rs b/src/builtins/conversions.rs index afb35fd8..f1348cb5 100644 --- a/src/builtins/conversions.rs +++ b/src/builtins/conversions.rs @@ -2,14 +2,16 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::ensure_args_count; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("to_number", (to_number, 1)); diff --git a/src/builtins/crypto.rs b/src/builtins/crypto.rs index a0aa0719..a64130c3 100644 --- a/src/builtins/crypto.rs +++ b/src/builtins/crypto.rs @@ -2,20 +2,23 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; use constant_time_eq::constant_time_eq; use hmac::{Hmac, Mac}; use md5::{Digest, Md5}; use sha1::Sha1; use sha2::{Sha256, Sha512}; +type Result = std::result::Result; + pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("crypto.hmac.equal", (hmac_equal_fixed_time, 2)); m.insert("crypto.hmac.md5", (hmac_md5, 2)); @@ -53,8 +56,9 @@ fn hmac_md5(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> let x = ensure_string(name, ¶ms[0], &args[0])?; let key = ensure_string(name, ¶ms[1], &args[1])?; - let mut hmac = Hmac::::new_from_slice(key.as_bytes()) - .or_else(|_| bail!(span.error("failed to create hmac instance")))?; + let Ok(mut hmac) = Hmac::::new_from_slice(key.as_bytes()) else { + bail!(span.error("failed to create md5 hmac instance")); + }; hmac.update(x.as_bytes()); let result = hmac.finalize(); @@ -69,8 +73,9 @@ fn hmac_sha1(span: &Span, params: &[Ref], args: &[Value], _strict: bool) - let x = ensure_string(name, ¶ms[0], &args[0])?; let key = ensure_string(name, ¶ms[1], &args[1])?; - let mut hmac = Hmac::::new_from_slice(key.as_bytes()) - .or_else(|_| bail!(span.error("failed to create hmac instance")))?; + let Ok(mut hmac) = Hmac::::new_from_slice(key.as_bytes()) else { + bail!(span.error("failed to sha1 create hmac instance")); + }; hmac.update(x.as_bytes()); let result = hmac.finalize(); @@ -85,8 +90,9 @@ fn hmac_sha256(span: &Span, params: &[Ref], args: &[Value], _strict: bool) let x = ensure_string(name, ¶ms[0], &args[0])?; let key = ensure_string(name, ¶ms[1], &args[1])?; - let mut hmac = Hmac::::new_from_slice(key.as_bytes()) - .or_else(|_| bail!(span.error("failed to create hmac instance")))?; + let Ok(mut hmac) = Hmac::::new_from_slice(key.as_bytes()) else { + bail!(span.error("failed to create sha256 hmac instance")); + }; hmac.update(x.as_bytes()); let result = hmac.finalize(); @@ -101,8 +107,9 @@ fn hmac_sha512(span: &Span, params: &[Ref], args: &[Value], _strict: bool) let x = ensure_string(name, ¶ms[0], &args[0])?; let key = ensure_string(name, ¶ms[1], &args[1])?; - let mut hmac = Hmac::::new_from_slice(key.as_bytes()) - .or_else(|_| bail!(span.error("failed to create hmac instance")))?; + let Ok(mut hmac) = Hmac::::new_from_slice(key.as_bytes()) else { + bail!(span.error("failed to create sha512 hmac instance")); + }; hmac.update(x.as_bytes()); let result = hmac.finalize(); diff --git a/src/builtins/debugging.rs b/src/builtins/debugging.rs index 45fa46c3..4de22d84 100644 --- a/src/builtins/debugging.rs +++ b/src/builtins/debugging.rs @@ -2,13 +2,15 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; +type Result = std::result::Result; // TODO: Should we avoid this limit? const MAX_ARGS: u8 = std::u8::MAX; diff --git a/src/builtins/deprecated.rs b/src/builtins/deprecated.rs index f3c71346..b4ec89ad 100644 --- a/src/builtins/deprecated.rs +++ b/src/builtins/deprecated.rs @@ -2,19 +2,22 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins::utils::{ensure_args_count, ensure_set}; +use crate::builtins::BuiltinError; use crate::builtins::BuiltinFcn; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; use lazy_static::lazy_static; #[cfg(feature = "regex")] use crate::builtins::regex::regex_match; +type Result = std::result::Result; + #[rustfmt::skip] lazy_static! { pub static ref DEPRECATED: HashMap<&'static str, BuiltinFcn> = { diff --git a/src/builtins/encoding.rs b/src/builtins/encoding.rs index 16a51d66..d615a98e 100644 --- a/src/builtins/encoding.rs +++ b/src/builtins/encoding.rs @@ -2,18 +2,21 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; #[allow(unused)] use crate::builtins::utils::{ ensure_args_count, ensure_object, ensure_string, ensure_string_collection, }; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -#[allow(unused)] -use anyhow::{anyhow, bail, Context, Result}; +pub(crate) use data_encoding::DecodeError; + +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { #[cfg(feature = "base64")] @@ -119,7 +122,7 @@ fn base64url_decode( { data_encoding::BASE64URL_NOPAD .decode(encoded_str.as_bytes()) - .map_err(|_| anyhow!(params[0].span().error("not a valid url")))? + .map_err(|_| params[0].span().error("not a valid url"))? } #[cfg(not(feature = "base64url"))] { @@ -327,7 +330,7 @@ fn yaml_marshal(span: &Span, params: &[Ref], args: &[Value], _strict: bool ensure_args_count(span, name, params, args, 1)?; Ok(Value::String( serde_yaml::to_string(&args[0]) - .with_context(|| span.error("could not serialize to yaml"))? + .map_err(|_| BuiltinError::SerializeFailed(span.error("could not serialize to yaml")))? .into(), )) } @@ -342,7 +345,9 @@ fn yaml_unmarshal( let name = "yaml.unmarshal"; ensure_args_count(span, name, params, args, 1)?; let yaml_str = ensure_string(name, ¶ms[0], &args[0])?; - Value::from_yaml_str(&yaml_str).with_context(|| span.error("could not deserialize yaml.")) + let value = Value::from_yaml_str(&yaml_str) + .map_err(|_| BuiltinError::DeserializeFailed(span.error("could not deserialize yaml")))?; + Ok(value) } fn json_is_valid( @@ -363,7 +368,7 @@ fn json_marshal(span: &Span, params: &[Ref], args: &[Value], _strict: bool ensure_args_count(span, name, params, args, 1)?; Ok(Value::String( serde_json::to_string(&args[0]) - .with_context(|| span.error("could not serialize to json"))? + .map_err(|_| BuiltinError::SerializeFailed(span.error("could not serialize to json")))? .into(), )) } @@ -377,5 +382,7 @@ fn json_unmarshal( let name = "json.unmarshal"; ensure_args_count(span, name, params, args, 1)?; let json_str = ensure_string(name, ¶ms[0], &args[0])?; - Value::from_json_str(&json_str).with_context(|| span.error("could not deserialize json.")) + let value = Value::from_json_str(&json_str) + .map_err(|_| BuiltinError::DeserializeFailed(span.error("could not deserialize json")))?; + Ok(value) } diff --git a/src/builtins/glob.rs b/src/builtins/glob.rs index b3993b10..27582629 100644 --- a/src/builtins/glob.rs +++ b/src/builtins/glob.rs @@ -2,14 +2,16 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_string, ensure_string_collection}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; +type Result = std::result::Result; //use glob::{Pattern, MatchOptions}; use wax::{Glob, Pattern}; @@ -28,7 +30,7 @@ fn suppress_unix_style_delimiter(s: &str) -> Result { fn make_delimiters_unix_style(s: &str, delimiters: &[char]) -> Result { if s.contains(PLACE_HOLDER) { - bail!("string contains internal glob placeholder"); + return Err(BuiltinError::StringContainsGlobPattern); } let has_unix_style = delimiters.contains(&'/'); diff --git a/src/builtins/graph.rs b/src/builtins/graph.rs index 0335512a..1536ff8c 100644 --- a/src/builtins/graph.rs +++ b/src/builtins/graph.rs @@ -2,14 +2,16 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_object}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::{BTreeMap, BTreeSet, HashMap}; -use anyhow::{bail, Result}; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("graph.reachable", (reachable, 2)); @@ -94,7 +96,9 @@ fn visit( set.len() } Some(&Value::Null) => 0, - _ => bail!(format!("neighbors for node `{node}` must be array/set.")), + _ => { + return Err(BuiltinError::WrongNeighbours(node.clone())); + } }; if n == 0 { diff --git a/src/builtins/http.rs b/src/builtins/http.rs index c7549ac8..54de5030 100644 --- a/src/builtins/http.rs +++ b/src/builtins/http.rs @@ -10,13 +10,18 @@ use crate::value::Value; use std::collections::HashMap; -use anyhow::Result; +use super::BuiltinError; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("http.send", (send, 1)); } -fn send(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn send( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "http.send"; ensure_args_count(span, name, params, args, 1)?; Ok(Value::Undefined) diff --git a/src/builtins/jwt.rs b/src/builtins/jwt.rs index a37443db..c1f51f71 100644 --- a/src/builtins/jwt.rs +++ b/src/builtins/jwt.rs @@ -2,8 +2,10 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; @@ -11,7 +13,7 @@ use crate::value::Value; use itertools::Itertools; use std::collections::HashMap; -use anyhow::{bail, Result}; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("io.jwt.decode", (jwt_decode, 1)); diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 3f87d9c3..f1a886f5 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -44,15 +44,81 @@ mod uuid; mod test; use crate::ast::{Expr, Ref}; -use crate::lexer::Span; -use crate::value::Value; +use crate::builtins::encoding::DecodeError; +#[cfg(feature = "semver")] +use crate::builtins::semver::SemverError; +#[cfg(feature = "time")] +use crate::builtins::time::compat::ParseDurationError; +#[cfg(feature = "time")] +use crate::builtins::time::compat::ParseError as TimeParseError; +use crate::builtins::utils::UtilsError; +use crate::lexer::{LexerError, Span}; +use crate::number::NumberError; +use crate::value::{Value, ValueError}; use std::collections::HashMap; -use anyhow::Result; +use thiserror::Error; + use lazy_static::lazy_static; -pub type BuiltinFcn = (fn(&Span, &[Ref], &[Value], bool) -> Result, u8); +#[derive(Error, Debug)] +pub enum BuiltinError { + #[error(transparent)] + UtilsError(#[from] UtilsError), + #[error(transparent)] + LexerError(#[from] LexerError), + #[error(transparent)] + NumberError(#[from] NumberError), + #[error(transparent)] + ValueError(#[from] ValueError), + #[cfg(feature = "time")] + #[error(transparent)] + ParseDurationError(#[from] ParseDurationError), + #[cfg(feature = "time")] + #[error(transparent)] + TimeParseError(#[from] TimeParseError), + #[cfg(feature = "time")] + #[error("unknown timezone: {0}")] + UnknownTimezone(String), + #[error(transparent)] + DecodeError(#[from] DecodeError), + #[error("serialize failed: {0}")] + SerializeFailed(#[source] LexerError), + #[error("deserialize failed: {0}")] + DeserializeFailed(#[source] LexerError), + #[cfg(feature = "glob")] + #[error("string contains internal glob placeholder")] + StringContainsGlobPattern, + #[cfg(feature = "crypto")] + #[error("failed to create hmac: {0}")] + HmacError(#[source] LexerError), + #[cfg(feature = "graph")] + #[error("neighbours for node {0} must be array or set")] + WrongNeighbours(Value), + #[cfg(feature = "jsonschema")] + #[error("json schema validation failed: {0}")] + JsonSchemaValidationFailed(String), + #[error("json parsing failed")] + JsonParsingFailed(#[from] serde_json::Error), + #[cfg(feature = "regex")] + #[error("regex error: {0}")] + RegexError(#[source] LexerError), + #[cfg(feature = "semver")] + #[error(transparent)] + SemverError(#[from] SemverError), + #[cfg(feature = "time")] + #[error("could not convert `ns1` to datetime")] + DateTimeConversionError, + #[cfg(feature = "opa-runtime")] + #[error(transparent)] + OutOfRangeError(#[from] chrono::OutOfRangeError), +} + +pub type BuiltinFcn = ( + fn(&Span, &[Ref], &[Value], bool) -> Result, + u8, +); pub use debugging::print_to_string; diff --git a/src/builtins/numbers.rs b/src/builtins/numbers.rs index f810a6e1..ac65e5a8 100644 --- a/src/builtins/numbers.rs +++ b/src/builtins/numbers.rs @@ -2,17 +2,20 @@ // Licensed under the MIT License. use crate::ast::{ArithOp, Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::number::Number; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; use rand::{thread_rng, Rng}; +type Result = std::result::Result; + pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("abs", (abs, 1)); m.insert("ceil", (ceil, 1)); diff --git a/src/builtins/objects.rs b/src/builtins/objects.rs index 995a4ae6..dbc86e16 100644 --- a/src/builtins/objects.rs +++ b/src/builtins/objects.rs @@ -2,8 +2,10 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_array, ensure_object}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::Rc; use crate::Value; @@ -11,7 +13,7 @@ use crate::Value; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::iter::Iterator; -use anyhow::{bail, Result}; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("json.filter", (json_filter, 2)); @@ -399,7 +401,7 @@ fn compile_json_schema(param: &Ref, arg: &Value) -> Result return Ok(schema), - Err(e) => bail!(e.to_string()), + Err(e) => return Err(BuiltinError::JsonSchemaValidationFailed(e.to_string())), } } bail!(param.span().error("not a valid json schema")) diff --git a/src/builtins/opa.rs b/src/builtins/opa.rs index 363db4bc..16722f09 100644 --- a/src/builtins/opa.rs +++ b/src/builtins/opa.rs @@ -4,13 +4,14 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::utils::ensure_args_count; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::{BTreeMap, HashMap}; -use anyhow::Result; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("opa.runtime", (opa_runtime, 0)); diff --git a/src/builtins/regex.rs b/src/builtins/regex.rs index 6b80b14e..67a6a97d 100644 --- a/src/builtins/regex.rs +++ b/src/builtins/regex.rs @@ -2,15 +2,17 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; use regex::Regex; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert( @@ -39,8 +41,11 @@ fn find_all_string_submatch_n( let value = ensure_string(name, ¶ms[1], &args[1])?; let n = ensure_numeric(name, ¶ms[2], &args[2])?; - let pattern = - Regex::new(&pattern).or_else(|_| bail!(params[0].span().error("invalid regex")))?; + let Ok(pattern) = Regex::new(&pattern) else { + return Err(BuiltinError::RegexError( + params[0].span().error("invalid regex"), + )); + }; if !n.is_integer() { bail!(params[2].span().error("n must be an integer")); @@ -81,8 +86,11 @@ fn find_n(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> R let value = ensure_string(name, ¶ms[1], &args[1])?; let n = ensure_numeric(name, ¶ms[2], &args[2])?; - let pattern = - Regex::new(&pattern).or_else(|_| bail!(params[0].span().error("invalid regex")))?; + let Ok(pattern) = Regex::new(&pattern) else { + return Err(BuiltinError::RegexError( + params[0].span().error("invalid regex"), + )); + }; if !n.is_integer() { bail!(params[2].span().error("n must be an integer")); @@ -121,8 +129,11 @@ pub fn regex_match( let pattern = ensure_string(name, ¶ms[0], &args[0])?; let value = ensure_string(name, ¶ms[1], &args[1])?; - let pattern = - Regex::new(&pattern).or_else(|_| bail!(params[0].span().error("invalid regex")))?; + let Ok(pattern) = Regex::new(&pattern) else { + return Err(BuiltinError::RegexError( + params[0].span().error("invalid regex"), + )); + }; Ok(Value::Bool(pattern.is_match(&value))) } @@ -156,8 +167,11 @@ fn regex_split(span: &Span, params: &[Ref], args: &[Value], _strict: bool) let pattern = ensure_string(name, ¶ms[0], &args[0])?; let value = ensure_string(name, ¶ms[1], &args[1])?; - let pattern = - Regex::new(&pattern).or_else(|_| bail!(params[0].span().error("invalid regex")))?; + let Ok(pattern) = Regex::new(&pattern) else { + return Err(BuiltinError::RegexError( + params[0].span().error("invalid regex"), + )); + }; Ok(Value::from_array( pattern .split(&value) @@ -196,8 +210,11 @@ fn regex_template_match( } // Fetch pattern, excluding delimiters. - let pattern = Regex::new(&template[start + delimiter_start.len()..end]) - .or_else(|_| bail!(params[0].span().error("invalid regex")))?; + let Ok(pattern) = Regex::new(&template[start + delimiter_start.len()..end]) else { + return Err(BuiltinError::RegexError( + params[0].span().error("invalid regex"), + )); + }; // Skip preceding literal in value. value = &value[start..]; diff --git a/src/builtins/semver.rs b/src/builtins/semver.rs index c64ddee3..1c594395 100644 --- a/src/builtins/semver.rs +++ b/src/builtins/semver.rs @@ -4,6 +4,7 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; @@ -12,7 +13,9 @@ use semver::Version; use std::cmp::Ordering; use std::collections::HashMap; -use anyhow::Result; +type Result = std::result::Result; + +pub(crate) use semver::Error as SemverError; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("semver.compare", (compare, 2)); diff --git a/src/builtins/sets.rs b/src/builtins/sets.rs index e4c0f783..8a2a5195 100644 --- a/src/builtins/sets.rs +++ b/src/builtins/sets.rs @@ -2,14 +2,16 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_set}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::{BTreeSet, HashMap}; -use anyhow::{bail, Result}; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("intersection", (intersection_of_set_of_sets, 1)); diff --git a/src/builtins/strings.rs b/src/builtins/strings.rs index 286adf18..173be818 100644 --- a/src/builtins/strings.rs +++ b/src/builtins/strings.rs @@ -2,19 +2,19 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ ensure_args_count, ensure_array, ensure_numeric, ensure_object, ensure_string, ensure_string_collection, }; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::number::Number; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; - pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("concat", (concat, 2)); m.insert("contains", (contains, 2)); @@ -41,6 +41,8 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("upper", (upper, 1)); } +type Result = std::result::Result; + fn concat(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "concat"; ensure_args_count(span, name, params, args, 2)?; @@ -315,7 +317,7 @@ fn sprintf(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> bail!(args_span.error( format!("invalid value {} for format verb c.", f.format_decimal()) .as_str() - )) + )); } } } @@ -372,7 +374,7 @@ fn sprintf(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> let (sign, v) = get_sign_value(f); let v = match v.as_f64() { Some(v) => v, - _ => bail!("cannot print large float using g format specified"), + _ => bail!(span.error("cannot print large float using g format specified")), }; s += sign; let bits = v.to_bits(); @@ -427,7 +429,7 @@ fn any_prefix_match( Value::Array(_) | Value::Set(_) => { match ensure_string_collection(name, ¶ms[0], &args[0]) { Ok(c) => c, - Err(e) if strict => return Err(e), + Err(e) if strict => bail!(e), _ => return Ok(Value::Undefined), } } @@ -445,7 +447,7 @@ fn any_prefix_match( Value::Array(_) | Value::Set(_) => { match ensure_string_collection(name, ¶ms[1], &args[1]) { Ok(c) => c, - Err(e) if strict => return Err(e), + Err(e) if strict => bail!(e), _ => return Ok(Value::Undefined), } } @@ -477,7 +479,7 @@ fn any_suffix_match( Value::Array(_) | Value::Set(_) => { match ensure_string_collection(name, ¶ms[0], &args[0]) { Ok(c) => c, - Err(e) if strict => return Err(e), + Err(e) if strict => bail!(e), _ => return Ok(Value::Undefined), } } @@ -495,7 +497,7 @@ fn any_suffix_match( Value::Array(_) | Value::Set(_) => { match ensure_string_collection(name, ¶ms[1], &args[1]) { Ok(c) => c, - Err(e) if strict => return Err(e), + Err(e) if strict => bail!(e), _ => return Ok(Value::Undefined), } } diff --git a/src/builtins/test.rs b/src/builtins/test.rs index d3a473d5..df32d570 100644 --- a/src/builtins/test.rs +++ b/src/builtins/test.rs @@ -5,13 +5,14 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::time; use crate::builtins::utils::{ensure_args_count, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; use std::thread; -use anyhow::{Ok, Result}; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("test.sleep", (sleep, 1)); diff --git a/src/builtins/time.rs b/src/builtins/time.rs index 1087ed85..5bb94c94 100644 --- a/src/builtins/time.rs +++ b/src/builtins/time.rs @@ -2,15 +2,15 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Result}; - use chrono::{ DateTime, Datelike, Days, FixedOffset, Local, Months, SecondsFormat, TimeZone, Timelike, Utc, Weekday, @@ -33,7 +33,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("time.weekday", (weekday, 1)); } -fn add_date(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { +fn add_date( + span: &Span, + params: &[Ref], + args: &[Value], + strict: bool, +) -> Result { let name = "time.add_date"; ensure_args_count(span, name, params, args, 4)?; @@ -69,7 +74,12 @@ fn add_date(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> }) } -fn clock(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn clock( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "time.clock"; ensure_args_count(span, name, params, args, 1)?; @@ -83,7 +93,12 @@ fn clock(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Re .into()) } -fn date(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn date( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "time.date"; ensure_args_count(span, name, params, args, 1)?; @@ -97,7 +112,12 @@ fn date(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Res .into()) } -fn diff(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn diff( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "time.diff"; ensure_args_count(span, name, params, args, 2)?; @@ -117,7 +137,12 @@ fn diff(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Res .into()) } -fn format(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn format( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "time.format"; ensure_args_count(span, name, params, args, 1)?; @@ -131,7 +156,12 @@ fn format(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> R Ok(Value::String(result.into())) } -fn now_ns(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { +fn now_ns( + span: &Span, + params: &[Ref], + args: &[Value], + strict: bool, +) -> Result { let name = "time.now_ns"; ensure_args_count(span, name, params, args, 0)?; @@ -143,7 +173,7 @@ fn parse_duration_ns( params: &[Ref], args: &[Value], strict: bool, -) -> Result { +) -> Result { let name = "time.parse_duration_ns"; ensure_args_count(span, name, params, args, 1)?; @@ -152,7 +182,12 @@ fn parse_duration_ns( safe_timestamp_nanos(span, strict, dur.num_nanoseconds()) } -fn parse_ns(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { +fn parse_ns( + span: &Span, + params: &[Ref], + args: &[Value], + strict: bool, +) -> Result { let name = "time.parse_ns"; ensure_args_count(span, name, params, args, 2)?; @@ -168,7 +203,7 @@ fn parse_rfc3339_ns( params: &[Ref], args: &[Value], strict: bool, -) -> Result { +) -> Result { let name = "time.parse_rfc3339_ns"; ensure_args_count(span, name, params, args, 1)?; @@ -178,7 +213,12 @@ fn parse_rfc3339_ns( safe_timestamp_nanos(span, strict, datetime.timestamp_nanos_opt()) } -fn weekday(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn weekday( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "time.weekday"; ensure_args_count(span, name, params, args, 1)?; @@ -197,18 +237,23 @@ fn weekday(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Ok(Value::String(weekday.into())) } -fn ensure_i32(name: &str, arg: &Expr, v: &Value) -> Result { - ensure_numeric(name, arg, v)? +fn ensure_i32(name: &str, arg: &Expr, v: &Value) -> Result { + let i = ensure_numeric(name, arg, v)? .as_i64() .and_then(|n| n.try_into().ok()) - .ok_or_else(|| arg.span().error("could not convert to int32")) + .ok_or_else(|| arg.span().error("could not convert to int32"))?; + Ok(i) } -fn safe_timestamp_nanos(span: &Span, strict: bool, nanos: Option) -> Result { +fn safe_timestamp_nanos( + span: &Span, + strict: bool, + nanos: Option, +) -> Result { match nanos { Some(ns) => Ok(Value::Number(ns.into())), None if strict => { - bail!(span.error("time outside of valid range")) + bail!(span.error("time outside of valid range")); } None => Ok(Value::Undefined), } @@ -218,7 +263,7 @@ fn parse_epoch( fcn: &str, arg: &Expr, val: &Value, -) -> Result<(DateTime, Option)> { +) -> Result<(DateTime, Option), BuiltinError> { match val { Value::Number(num) => { let ns = num.as_i64().ok_or_else(|| { @@ -250,7 +295,7 @@ fn parse_epoch( _ => { let tz: Tz = match tz.parse() { Ok(tz) => tz, - Err(e) => bail!(e), + Err(e) => bail!(BuiltinError::UnknownTimezone(e)), }; tz.timestamp_nanos(ns).fixed_offset() } @@ -261,7 +306,7 @@ fn parse_epoch( Some(other) => { bail!(arg.span().error(&format!( "`{fcn}` expects 3rd element of `ns` to be a `string`. Got `{other}` instead" - ))) + ))); } None => None, }; @@ -276,7 +321,7 @@ fn parse_epoch( bail!(arg.span().error(&format!( "`{fcn}` expects `ns` to be a `number` or `array[number, string]`. Got `{val}` instead" - ))) + ))); } fn layout_with_predefined_formats(format: &str) -> &str { diff --git a/src/builtins/time/compat.rs b/src/builtins/time/compat.rs index 280216e7..1bcd917f 100644 --- a/src/builtins/time/compat.rs +++ b/src/builtins/time/compat.rs @@ -35,6 +35,7 @@ use std::error::Error; use std::fmt; use std::iter; +pub(crate) use chrono::ParseError; use chrono::TimeZone; use chrono::{ format::{self, Fixed, Parsed}, diff --git a/src/builtins/time/diff.rs b/src/builtins/time/diff.rs index 8846e51b..335b6bde 100644 --- a/src/builtins/time/diff.rs +++ b/src/builtins/time/diff.rs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT and Apache 2.0 License. -use anyhow::{anyhow, Result}; +use crate::builtins::BuiltinError; use chrono::{DateTime, Datelike, FixedOffset, NaiveDate, Timelike}; +type Result = std::result::Result; + // Adapted from the official Go implementation: // https://github.com/open-policy-agent/opa/blob/eb17a716b97720a27c6569395ba7c4b7409aae87/topdown/time.go#L179-L243 pub fn diff_between_datetimes( @@ -50,7 +52,7 @@ pub fn diff_between_datetimes( } if day < 0 { let days_in_month = days_in_month(datetime1.year(), datetime1.month()) - .ok_or(anyhow!("Could not convert `ns1` to datetime"))?; + .ok_or(BuiltinError::DateTimeConversionError)?; day += days_in_month as i32; month -= 1; } diff --git a/src/builtins/tracing.rs b/src/builtins/tracing.rs index 2870107b..ee1fd01f 100644 --- a/src/builtins/tracing.rs +++ b/src/builtins/tracing.rs @@ -4,12 +4,13 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::Result; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("trace", (trace, 1)); diff --git a/src/builtins/types.rs b/src/builtins/types.rs index bc11b0e0..0f77b502 100644 --- a/src/builtins/types.rs +++ b/src/builtins/types.rs @@ -4,12 +4,13 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::utils::ensure_args_count; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::HashMap; -use anyhow::Result; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("is_array", (is_array, 1)); diff --git a/src/builtins/units.rs b/src/builtins/units.rs index d3ea4b60..ff1225e6 100644 --- a/src/builtins/units.rs +++ b/src/builtins/units.rs @@ -2,15 +2,17 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::number::Number; use crate::value::Value; use std::collections::HashMap; -use anyhow::{bail, Context, Result}; +type Result = std::result::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("units.parse", (parse, 1)); @@ -94,7 +96,7 @@ fn parse(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Re } else { serde_json::from_str(number_part) } - .with_context(|| span.error("could not parse number"))?; + .map_err(|_| BuiltinError::DeserializeFailed(span.error("could not parse number")))?; let mut n = match v { Value::Number(n) => n.clone(), diff --git a/src/builtins/utils.rs b/src/builtins/utils.rs index 4996e410..3dca47e7 100644 --- a/src/builtins/utils.rs +++ b/src/builtins/utils.rs @@ -2,14 +2,25 @@ // Licensed under the MIT License. use crate::ast::{Expr, Ref}; +use crate::bail; +use crate::lexer::LexerError; use crate::lexer::Span; use crate::number::Number; +use crate::utils::UtilsError as CoreUtilsError; use crate::Rc; use crate::Value; use std::collections::{BTreeMap, BTreeSet}; -use anyhow::{bail, Result}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum UtilsError { + #[error(transparent)] + LexerError(#[from] LexerError), + #[error(transparent)] + CoreUtilsError(#[from] CoreUtilsError), +} pub fn ensure_args_count( span: &Span, @@ -17,39 +28,41 @@ pub fn ensure_args_count( params: &[Ref], args: &[Value], expected: usize, -) -> Result<()> { +) -> Result<(), UtilsError> { if args.len() != expected { let span = match args.len() > expected { false => span, true => params[args.len() - 1].span(), }; if expected == 1 { - bail!(span.error(format!("`{fcn}` expects 1 argument").as_str())) + bail!(span.error(format!("`{fcn}` expects 1 argument").as_str())); } else { - bail!(span.error(format!("`{fcn}` expects {expected} arguments").as_str())) + bail!(span.error(format!("`{fcn}` expects {expected} arguments").as_str())); } } Ok(()) } -pub fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result { +pub fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result { Ok(match &v { Value::Number(n) => n.clone(), _ => { let span = arg.span(); bail!( span.error(format!("`{fcn}` expects numeric argument. Got `{v}` instead").as_str()) - ) + ); } }) } -pub fn ensure_string(fcn: &str, arg: &Expr, v: &Value) -> Result> { +pub fn ensure_string(fcn: &str, arg: &Expr, v: &Value) -> Result, UtilsError> { Ok(match &v { Value::String(s) => s.clone(), _ => { let span = arg.span(); - bail!(span.error(format!("`{fcn}` expects string argument. Got `{v}` instead").as_str())) + bail!( + span.error(format!("`{fcn}` expects string argument. Got `{v}` instead").as_str()) + ); } }) } @@ -59,7 +72,7 @@ pub fn ensure_string_element<'a>( arg: &Expr, v: &'a Value, idx: usize, -) -> Result<&'a str> { +) -> Result<&'a str, UtilsError> { Ok(match &v { Value::String(s) => s.as_ref(), _ => { @@ -67,12 +80,16 @@ pub fn ensure_string_element<'a>( bail!(span.error( format!("`{fcn}` expects string collection. Element {idx} is not a string.") .as_str() - )) + )); } }) } -pub fn ensure_string_collection<'a>(fcn: &str, arg: &Expr, v: &'a Value) -> Result> { +pub fn ensure_string_collection<'a>( + fcn: &str, + arg: &Expr, + v: &'a Value, +) -> Result, UtilsError> { let mut collection = vec![]; match &v { Value::Array(a) => { @@ -87,38 +104,44 @@ pub fn ensure_string_collection<'a>(fcn: &str, arg: &Expr, v: &'a Value) -> Resu } _ => { let span = arg.span(); - bail!(span.error(format!("`{fcn}` expects array/set of strings.").as_str())) + bail!(span.error(format!("`{fcn}` expects array/set of strings.").as_str())); } } Ok(collection) } -pub fn ensure_array(fcn: &str, arg: &Expr, v: Value) -> Result>> { +pub fn ensure_array(fcn: &str, arg: &Expr, v: Value) -> Result>, UtilsError> { Ok(match v { Value::Array(a) => a, _ => { let span = arg.span(); - bail!(span.error(format!("`{fcn}` expects array argument. Got `{v}` instead").as_str())) + bail!(span.error(format!("`{fcn}` expects array argument. Got `{v}` instead").as_str())); } }) } -pub fn ensure_set(fcn: &str, arg: &Expr, v: Value) -> Result>> { +pub fn ensure_set(fcn: &str, arg: &Expr, v: Value) -> Result>, UtilsError> { Ok(match v { Value::Set(s) => s, _ => { let span = arg.span(); - bail!(span.error(format!("`{fcn}` expects set argument. Got `{v}` instead").as_str())) + bail!(span.error(format!("`{fcn}` expects set argument. Got `{v}` instead").as_str())); } }) } -pub fn ensure_object(fcn: &str, arg: &Expr, v: Value) -> Result>> { +pub fn ensure_object( + fcn: &str, + arg: &Expr, + v: Value, +) -> Result>, UtilsError> { Ok(match v { Value::Object(o) => o, _ => { let span = arg.span(); - bail!(span.error(format!("`{fcn}` expects object argument. Got `{v}` instead").as_str())) + bail!( + span.error(format!("`{fcn}` expects object argument. Got `{v}` instead").as_str()) + ); } }) } diff --git a/src/builtins/uuid.rs b/src/builtins/uuid.rs index a8668114..1ec7337a 100644 --- a/src/builtins/uuid.rs +++ b/src/builtins/uuid.rs @@ -4,14 +4,16 @@ use crate::ast::{Expr, Ref}; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_string}; +use crate::builtins::BuiltinError; use crate::lexer::Span; use crate::value::Value; use std::collections::{BTreeMap, HashMap}; -use anyhow::Result; use uuid::{Timestamp, Uuid}; +type Result = std::result::Result; + pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("uuid.parse", (parse, 1)); m.insert("uuid.rfc4122", (rfc4122, 1)); diff --git a/src/engine.rs b/src/engine.rs index 4eaef8fc..99c045a2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -6,14 +6,38 @@ use crate::interpreter::*; use crate::lexer::*; use crate::parser::*; use crate::scheduler::*; -use crate::utils::gather_functions; +use crate::utils::{gather_functions, UtilsError}; use crate::value::*; use crate::{Extension, QueryResults}; +use thiserror::Error; + use std::convert::AsRef; use std::path::Path; -use anyhow::{bail, Result}; +type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum EngineError { + #[error("interpreter error: {0}")] + InterpreterError(#[from] InterpreterError), + #[error("lexer error: {0}")] + LexerError(#[from] LexerError), + #[error("parser error: {0}")] + ParserError(#[from] ParserError), + #[error("scheduler error: {0}")] + SchedulerError(#[from] SchedulerError), + #[error("utils error: {0}")] + UtilsError(#[from] UtilsError), + #[error("value error: {0}")] + ValueError(#[from] ValueError), + #[error("data must be object")] + DataMustBeObject, + #[error("query did not produce any values")] + QueryDidNotProduceAnyValues, + #[error("query produced more than one value")] + QueryProducedMoreThanOneValue, +} /// The Rego evaluation engine. /// @@ -181,10 +205,11 @@ impl Engine { /// ``` pub fn add_data(&mut self, data: Value) -> Result<()> { if data.as_object().is_err() { - bail!("data must be object"); + return Err(EngineError::DataMustBeObject); } self.prepared = false; - self.interpreter.get_data_mut().merge(data) + self.interpreter.get_data_mut().merge(data)?; + Ok(()) } pub fn add_data_json(&mut self, data_json: &str) -> Result<()> { @@ -251,7 +276,8 @@ impl Engine { pub fn eval_rule(&mut self, path: String) -> Result { self.prepare_for_eval(false)?; self.interpreter.clean_internal_evaluation_state(); - self.interpreter.eval_rule_in_path(path) + let value = self.interpreter.eval_rule_in_path(path)?; + Ok(value) } /// Evaluate a Rego query. @@ -309,12 +335,13 @@ impl Engine { self.eval_modules(enable_tracing)?; } let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?; - self.interpreter.eval_user_query( + let results = self.interpreter.eval_user_query( &query_module, &query_node, &query_schedule, enable_tracing, - ) + )?; + Ok(results) } /// Evaluate a Rego query that produces a boolean value. @@ -346,11 +373,12 @@ impl Engine { pub fn eval_bool_query(&mut self, query: String, enable_tracing: bool) -> Result { let results = self.eval_query(query, enable_tracing)?; match results.result.len() { - 0 => bail!("query did not produce any values"), + 0 => Err(EngineError::QueryDidNotProduceAnyValues), 1 if results.result[0].expressions.len() == 1 => { - results.result[0].expressions[0].value.as_bool().copied() + let b = results.result[0].expressions[0].value.as_bool().copied()?; + Ok(b) } - _ => bail!("query produced more than one value"), + _ => Err(EngineError::QueryProducedMoreThanOneValue), } } @@ -423,12 +451,13 @@ impl Engine { let mut parser = Parser::new(&query_source)?; let query_node = parser.parse_user_query()?; let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?; - self.interpreter.eval_user_query( + let results = self.interpreter.eval_user_query( &query_module, &query_node, &query_schedule, enable_tracing, - ) + )?; + Ok(results) } #[doc(hidden)] @@ -532,7 +561,7 @@ impl Engine { /// /// ```rust /// # use regorus::*; - /// # use anyhow::{bail, Result}; + /// # use anyhow::{anyhow, Result}; /// # fn main() -> Result<()> { /// let mut engine = Engine::new(); /// @@ -563,7 +592,7 @@ impl Engine { /// } /// // Extensions can raise errors. Regorus will add location information to /// // the error. - /// _ => bail!("do_magic expects i64 value") + /// _ => Err(anyhow!("do_magic expects i64 value").into()) /// } /// }))?; /// @@ -609,7 +638,8 @@ impl Engine { nargs: u8, extension: Box, ) -> Result<()> { - self.interpreter.add_extension(path, nargs, extension) + self.interpreter.add_extension(path, nargs, extension)?; + Ok(()) } #[cfg(feature = "coverage")] @@ -653,7 +683,8 @@ impl Engine { /// /// See also [`crate::coverage::Report::to_colored_string`]. pub fn get_coverage_report(&self) -> Result { - self.interpreter.get_coverage_report() + let report = self.interpreter.get_coverage_report()?; + Ok(report) } #[cfg(feature = "coverage")] @@ -709,6 +740,7 @@ impl Engine { /// # } /// ``` pub fn take_prints(&mut self) -> Result> { - self.interpreter.take_prints() + let strings = self.interpreter.take_prints()?; + Ok(strings) } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 00e1be4c..e3b94b93 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -2,19 +2,72 @@ // Licensed under the MIT License. use crate::ast::*; -use crate::builtins::{self, BuiltinFcn}; +use crate::bail; +use crate::builtins::{self, BuiltinError, BuiltinFcn}; use crate::lexer::*; -use crate::parser::Parser; +use crate::parser::{Parser, ParserError}; use crate::scheduler::*; use crate::utils::*; use crate::value::*; use crate::Rc; use crate::{Expression, Extension, Location, QueryResult, QueryResults}; -use anyhow::{anyhow, bail, Result}; use std::collections::btree_map::Entry as BTreeMapEntry; use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet}; use std::ops::Bound::*; +use thiserror::Error; + +type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum InterpreterError { + #[error("unexpected")] + Unexpected, + #[error("not an object")] + NotAnObject, + #[error("builtin error: {0}")] + BuiltinError(#[from] BuiltinError), + #[error("lexer error: {0}")] + LexerError(#[from] LexerError), + #[error("parser error: {0}")] + ParserError(#[from] ParserError), + #[error("scheduler error: {0}")] + SchedulerError(#[from] SchedulerError), + #[error("value error: {0}")] + ValueError(#[from] ValueError), + #[error(transparent)] + UtilsError(#[from] UtilsError), + #[error("invalid rule path")] + InvalidRulePath, + #[error("current module not set")] + CurrentModuleNotSet, + #[error("current rule not active")] + CurrentRuleNotActive, + #[error("no active scope")] + NoActiveScope, + #[error("variable {0} is undefined")] + VariableUndefined(String), + #[error("duplicated definition of local variable {0}")] + DuplicatedDefinition(String), + #[error("previous value is not an object")] + PreviousValueNotAnObject, + #[error("previous value is not a set")] + PreviousValueNotASet, + #[error("invalid context value {0}")] + InvalidContextValue(Value), + #[error("context already popped")] + ContextAlreadyPopped, + #[error("not a function")] + NotAFunction, + #[error("could not find module for rule")] + RuleModuleNotFound, + #[error("no current context")] + NoCurrentContext, + #[error("extension already added")] + ExtensionAlreadyAdded, + #[error("serde_json error")] + SerdeJsonError(#[from] serde_json::Error), +} type Scope = BTreeMap; @@ -265,19 +318,17 @@ impl Interpreter { fn current_module(&self) -> Result> { self.module .clone() - .ok_or_else(|| anyhow!("internal error: current module not set")) + .ok_or(InterpreterError::CurrentModuleNotSet) } fn current_scope(&mut self) -> Result<&Scope> { - self.scopes - .last() - .ok_or_else(|| anyhow!("internal error: no active scope")) + self.scopes.last().ok_or(InterpreterError::NoActiveScope) } fn current_scope_mut(&mut self) -> Result<&mut Scope> { self.scopes .last_mut() - .ok_or_else(|| anyhow!("internal error: no active scope")) + .ok_or(InterpreterError::NoActiveScope) } #[inline(always)] @@ -309,7 +360,7 @@ impl Interpreter { } else if name.text() == "_" { Ok(()) } else { - bail!("variable {} is undefined", name) + return Err(InterpreterError::VariableUndefined(name.text().into())); } } @@ -536,7 +587,8 @@ impl Interpreter { return Ok(Value::Undefined); } - builtins::comparison::compare(op, &lhs, &rhs) + let value = builtins::comparison::compare(op, &lhs, &rhs)?; + Ok(value) } fn eval_bin_expr(&mut self, op: &BinOp, lhs: &ExprRef, rhs: &ExprRef) -> Result { @@ -547,10 +599,11 @@ impl Interpreter { return Ok(Value::Undefined); } - match op { - BinOp::Or => builtins::sets::union(lhs, rhs, lhs_value, rhs_value), - BinOp::And => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value), - } + let set = match op { + BinOp::Or => builtins::sets::union(lhs, rhs, lhs_value, rhs_value)?, + BinOp::And => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value)?, + }; + Ok(set) } fn eval_arith_expr( @@ -567,9 +620,9 @@ impl Interpreter { return Ok(Value::Undefined); } - match (op, &lhs_value, &rhs_value) { + let value = match (op, &lhs_value, &rhs_value) { (ArithOp::Sub, Value::Set(_), _) | (ArithOp::Sub, _, Value::Set(_)) => { - builtins::sets::difference(lhs, rhs, lhs_value, rhs_value) + builtins::sets::difference(lhs, rhs, lhs_value, rhs_value)? } _ => builtins::numbers::arithmetic_operation( span, @@ -579,8 +632,9 @@ impl Interpreter { lhs_value, rhs_value, self.strict_builtin_errors, - ), - } + )?, + }; + Ok(value) } fn eval_assign_expr(&mut self, op: &AssignOp, lhs: &ExprRef, rhs: &ExprRef) -> Result { @@ -848,7 +902,7 @@ impl Interpreter { (Expr::Array { items, .. }, Value::Array(a)) => { if items.len() != a.len() { if raise_error { - return Err(span.error( + bail!(span.error( format!( "array length mismatch. Expected {} got {}.", items.len(), @@ -886,7 +940,7 @@ impl Interpreter { if field_value == &Value::Undefined { if raise_error { - return Err(span.error("Expected value, got undefined.")); + bail!(span.error("Expected value, got undefined.")); } return Ok(false); } @@ -922,7 +976,7 @@ impl Interpreter { let value_t = builtins::types::get_type(value); if expr_t != value_t { - return Err(span.error( + bail!(span.error( format!("Cannot bind pattern of type `{expr_t}` with value of type `{value_t}`. Value is {value}.").as_str())); } } @@ -1046,8 +1100,8 @@ impl Interpreter { v => { let span = collection.span(); bail!(span.error( - format!("`some .. in collection` expects array/set/object. Got `{v}`").as_str() - )) + format!("`some .. in collection` expects array/set/object. Got `{v}`").as_str(), + )); } } @@ -1138,7 +1192,7 @@ impl Interpreter { for var in vars { let name = var.source_str(); if self.current_scope()?.get(&name).is_some() { - bail!("duplicated definition of local variable {}", name); + return Err(InterpreterError::DuplicatedDefinition(format!("{}", name))); } self.add_variable_or(&name)?; } @@ -1560,16 +1614,16 @@ impl Interpreter { if is_set { let set = obj .as_object_mut() - .map_err(|_| anyhow!(span.error("previous value is not an object")))? + .map_err(|_| InterpreterError::PreviousValueNotAnObject)? .entry(p) .or_insert(Value::new_set()) .as_set_mut() - .map_err(|_| anyhow!(span.error("previous value is not a set")))?; + .map_err(|_| InterpreterError::PreviousValueNotASet)?; set.append(value.as_set_mut()?); } else { let obj = obj .as_object_mut() - .map_err(|_| anyhow!(span.error("previous value is not an object")))?; + .map_err(|_| InterpreterError::PreviousValueNotAnObject)?; match obj.entry(p) { BTreeMapEntry::Vacant(v) => { if value != Value::Undefined { @@ -1582,7 +1636,7 @@ impl Interpreter { BTreeMapEntry::Occupied(o) => { if o.get() != &value && value != Value::Undefined { bail!(span - .error("complete rules should not produce multiple outputs")) + .error("complete rules should not produce multiple outputs")); } } } @@ -1591,7 +1645,7 @@ impl Interpreter { } else { obj = obj .as_object_mut() - .map_err(|_| anyhow!(span.error("previous value is not an object")))? + .map_err(|_| InterpreterError::PreviousValueNotAnObject)? .entry(p) .or_insert(Value::new_object()); } @@ -1715,9 +1769,11 @@ impl Interpreter { BTreeMapEntry::Vacant(v) => { v.insert(output); } - BTreeMapEntry::Occupied(o) if o.get() != &output => bail!(rule_ref - .span() - .error("rules must not produce multiple outputs")), + BTreeMapEntry::Occupied(o) if o.get() != &output => { + bail!(rule_ref + .span() + .error("rules must not produce multiple outputs")); + } _ => { // Rule produced same value. } @@ -1737,14 +1793,14 @@ impl Interpreter { match map.get(&key) { Some(pv) if *pv != value => { let span = ke.span(); - return Err(span.source.error( + bail!(span.source.error( span.line, span.col, format!( - "value for key `{}` generated multiple times: `{}` and `{}`", - serde_json::to_string_pretty(&key)?, - serde_json::to_string_pretty(&pv)?, - serde_json::to_string_pretty(&value)?, + "value for key `{}` generated multiple times: `{}` and `{}`", + serde_json::to_string_pretty(&key)?, + serde_json::to_string_pretty(&pv)?, + serde_json::to_string_pretty(&value)?, ) .as_str(), )); @@ -1769,7 +1825,9 @@ impl Interpreter { Value::Set(ref mut s) => { Rc::make_mut(s).insert(output); } - a => bail!("internal error: invalid context value {a}"), + a => { + return Err(InterpreterError::InvalidContextValue(a.clone())); + } } } else if !ctx.is_compr { match &ctx.value { @@ -1832,7 +1890,7 @@ impl Interpreter { } } _ => { - return Err(loop_expr.span().source.error( + bail!(loop_expr.span().source.error( loop_expr.span().line, loop_expr.span().col, "item cannot be indexed", @@ -1846,7 +1904,7 @@ impl Interpreter { fn get_current_context(&self) -> Result<&Context> { match self.contexts.last() { Some(ctx) => Ok(ctx), - _ => bail!("internal error: no active context found"), + _ => Err(InterpreterError::NoCurrentContext), } } @@ -1938,9 +1996,11 @@ impl Interpreter { match schedule.order.get(query) { Some(ord) => ord.iter().map(|i| &query.stmts[*i as usize]).collect(), // TODO - _ => bail!(query - .span - .error("statements not scheduled in query {query:?}")), + _ => { + bail!(query + .span + .error("statements not scheduled in query {query:?}")); + } } } else { query.stmts.iter().collect() @@ -2058,7 +2118,7 @@ impl Interpreter { match self.contexts.pop() { Some(ctx) => Ok(ctx.value), - None => bail!("internal error: context already popped"), + None => Err(InterpreterError::ContextAlreadyPopped), } } @@ -2075,7 +2135,7 @@ impl Interpreter { match self.contexts.pop() { Some(ctx) => Ok(ctx.value), - None => bail!("internal error: context already popped"), + None => Err(InterpreterError::ContextAlreadyPopped), } } @@ -2098,7 +2158,7 @@ impl Interpreter { match self.contexts.pop() { Some(ctx) => Ok(ctx.value), - None => bail!("internal error: context already popped"), + None => Err(InterpreterError::ContextAlreadyPopped), } } @@ -2181,7 +2241,7 @@ impl Interpreter { if let Some(builtin) = builtins::DEPRECATED.get(path) { let allow = self.allow_deprecated && !self.current_module()?.rego_v1; if !allow { - bail!(span.error(format!("{path} is deprecated").as_str())) + bail!(span.error(format!("{path} is deprecated").as_str())); } return Ok(Some(builtin)); } @@ -2206,7 +2266,9 @@ impl Interpreter { let fcn_path = match get_path_string(fcn, None) { Ok(p) => p, - _ => bail!(span.error("invalid function expression")), + _ => { + bail!(span.error("invalid function expression")); + } }; let mut param_values = Vec::with_capacity(params.len()); @@ -2292,14 +2354,16 @@ impl Interpreter { } match r { Ok(v) => return Ok(v), - Err(e) => bail!(span.error(&format!("{e}"))), + Err(e) => { + bail!(span.error(&format!("{}", e))); + } } } let fcns = fcns_rules.clone(); let mut results: Vec = Vec::new(); - let mut errors: Vec = Vec::new(); + let mut errors: Vec = Vec::new(); 'outer: for fcn_rule in fcns { let (args, output_expr, bodies) = match fcn_rule.as_ref() { @@ -2308,11 +2372,13 @@ impl Interpreter { bodies, .. } => (args, assign.as_ref().map(|a| a.value.clone()), bodies), - _ => bail!("internal error not a function"), + _ => { + return Err(InterpreterError::NotAFunction); + } }; if args.len() != params.len() { - return Err(span.source.error( + bail!(span.source.error( span.line, span.col, format!( @@ -2372,11 +2438,11 @@ impl Interpreter { let result = match &value { Value::Set(s) if s.len() == 1 => s.iter().next().unwrap().clone(), Value::Set(s) if !s.is_empty() => { - return Err(span.source.error( + bail!(span.source.error( span.line, span.col, format!("function produced multiple outputs {value:?}").as_str(), - )) + )); } // If the function successfully executed, but did not return any value, then return true. Value::Set(s) if s.is_empty() && output_expr.is_none() => Value::Bool(true), @@ -2399,7 +2465,7 @@ impl Interpreter { } if self.strict_builtin_errors && !errors.is_empty() { - return Err(anyhow!(errors[0].to_string())); + return Err(errors.remove(0)); } if results.is_empty() { @@ -2439,13 +2505,13 @@ impl Interpreter { if errors.is_empty() { return Ok(Value::Undefined); } else { - return Err(anyhow!(errors[0].to_string())); + return Err(errors.remove(0)); } } // all defined values should be the equal to the same value that should be returned if !results.windows(2).all(|w| w[0] == w[1]) { - return Err(span.source.error( + bail!(span.source.error( span.line, span.col, "functions must not produce multiple outputs for same inputs", @@ -2623,7 +2689,7 @@ impl Interpreter { if no_error { return Ok(Value::Undefined); } - return Err(span.error("undefined var")); + bail!(span.error("undefined var")); } // Ensure that rules are evaluated @@ -2774,7 +2840,7 @@ impl Interpreter { Expr::SetCompr { term, query, .. } => self.eval_set_compr(term, query), Expr::UnaryExpr { span, expr: uexpr } => match uexpr.as_ref() { Expr::Number(_) if !uexpr.span().text().starts_with('-') => { - builtins::numbers::arithmetic_operation( + let value = builtins::numbers::arithmetic_operation( span, &ArithOp::Sub, expr, @@ -2782,11 +2848,14 @@ impl Interpreter { Value::from(0), self.eval_expr(uexpr)?, self.strict_builtin_errors, - ) + )?; + Ok(value) + } + _ => { + bail!(expr + .span() + .error("unary - can only be used with numeric literals")); } - _ => bail!(expr - .span() - .error("unary - can only be used with numeric literals")), }, Expr::Call { span, fcn, params } => { self.eval_call(span, expr, fcn, params, None, false) @@ -2844,7 +2913,7 @@ impl Interpreter { return Ok(m.clone()); } } - bail!("internal error: could not find module for rule"); + Err(InterpreterError::RuleModuleNotFound) } fn eval_rule_bodies( @@ -2883,7 +2952,9 @@ impl Interpreter { let ctx = match self.contexts.pop() { Some(ctx) => ctx, - _ => bail!("internal error: rule's context already popped"), + _ => { + return Err(InterpreterError::ContextAlreadyPopped); + } }; let result = match result { @@ -2907,11 +2978,11 @@ impl Interpreter { Value::Array(a) if a.len() == 1 => a[0].clone(), Value::Array(a) if a.is_empty() => Value::Bool(true), Value::Array(_) => { - return Err(span.source.error( + bail!(span.source.error( span.line, span.col, "complete rules should not produce multiple outputs", - )) + )); } Value::Set(_) => ctx.value, _ => unimplemented!("todo fix this: ctx.value = {:?}", ctx.value), @@ -2947,21 +3018,23 @@ impl Interpreter { Value::Object(map) => match Rc::make_mut(map).get_mut(&key) { Some(v) if paths.len() == 1 => Ok(v), Some(v) => Self::make_or_get_value_mut(v, &paths[1..]), - _ => bail!("internal error: unexpected"), + _ => Err(InterpreterError::Unexpected), }, Value::Undefined if paths.len() > 1 => { *obj = Value::new_object(); Self::make_or_get_value_mut(obj, paths) } Value::Undefined => Ok(obj), - _ => bail!("internal error: make: not an object {obj:?}"), + _ => Err(InterpreterError::NotAnObject), } } pub fn merge_rule_value(span: &Span, value: &mut Value, new: Value) -> Result<()> { match value.merge(new) { Ok(()) => Ok(()), - Err(_) => Err(span.error("rules should not produce multiple outputs.")), + Err(_) => { + bail!(span.error("rules should not produce multiple outputs.")); + } } } @@ -2984,7 +3057,9 @@ impl Interpreter { comps.push(v.0.text()); expr = None; } - _ => bail!(e.span().error("invalid ref expression")), + _ => { + bail!(e.span().error("invalid ref expression")); + } } } if let Some(d) = document { @@ -3059,7 +3134,7 @@ impl Interpreter { Membership { span, .. } => ("membership", span), }; - Err(span.error(format!("invalid `{kind}` in default value").as_str())) + bail!(span.error(format!("invalid `{kind}` in default value").as_str())); } pub fn check_default_rules(&self) -> Result<()> { @@ -3101,10 +3176,12 @@ impl Interpreter { Expr::RefBrack { refr, index, .. } => (refr, Some(index.clone())), Expr::RefDot { .. } => (refr, None), Expr::Var(_) => (refr, None), - _ => bail!(refr.span().error(&format!( - "invalid token {:?} with the default keyword", - refr - ))), + _ => { + bail!(refr.span().error(&format!( + "invalid token {:?} with the default keyword", + refr + ))); + } }; Parser::get_path_ref_components_into(refr, &mut path)?; @@ -3280,7 +3357,9 @@ impl Interpreter { } } } - _ => bail!("internal error: unexpected"), + _ => { + return Err(InterpreterError::Unexpected); + } } Ok(()) } @@ -3311,7 +3390,7 @@ impl Interpreter { self.active_rules.pop(); let refr = Self::get_rule_refr(rule); let span = refr.span(); - return Err(span.source.error( + bail!(span.source.error( span.line, span.col, format!("recursion detected when evaluating rule:{msg}").as_str(), @@ -3329,7 +3408,7 @@ impl Interpreter { self.scopes = scopes; match self.active_rules.pop() { Some(ref r) if r == rule => res, - _ => bail!("internal error: current rule not active"), + _ => Err(InterpreterError::CurrentRuleNotActive), } } @@ -3367,7 +3446,9 @@ impl Interpreter { let mut results = match self.contexts.pop() { Some(ctx) => ctx.results, - _ => bail!("internal error: no context"), + _ => { + return Err(InterpreterError::NoCurrentContext); + } }; // Restore schedules. @@ -3574,10 +3655,7 @@ impl Interpreter { }, }; if target.is_empty() { - bail!(import - .refr - .span() - .message("warning", "invalid ref in import")); + bail!(import.refr.span().error("invalid ref in import")); } self.imports .insert(module_path.clone() + "." + target, import.refr.clone()); @@ -3640,10 +3718,9 @@ impl Interpreter { ) -> Result<()> { if let std::collections::hash_map::Entry::Vacant(v) = self.extensions.entry(path) { v.insert((nargs, Rc::new(extension))); - Ok(()) - } else { - bail!("extension already added"); + return Ok(()); } + Err(InterpreterError::ExtensionAlreadyAdded) } #[cfg(feature = "coverage")] @@ -3652,7 +3729,7 @@ impl Interpreter { query: &Ref, covered: &Vec, file: &mut crate::coverage::File, - ) -> Result<()> { + ) -> crate::scheduler::Result<()> { for stmt in &query.stmts { // TODO: with mods match &stmt.literal { @@ -3681,7 +3758,7 @@ impl Interpreter { expr: &Ref, covered: &Vec, file: &mut crate::coverage::File, - ) -> Result<()> { + ) -> crate::scheduler::Result<()> { use Expr::*; traverse(expr, &mut |e| { Ok(match e.as_ref() { @@ -3781,7 +3858,7 @@ impl Interpreter { pub fn eval_rule_in_path(&mut self, path: String) -> Result { if !self.rule_paths.contains(&path) { - bail!("not a valid rule path"); + return Err(InterpreterError::InvalidRulePath); } self.ensure_rule_evaluated(path.clone())?; let parts: Vec<&str> = path.split('.').collect(); diff --git a/src/lexer.rs b/src/lexer.rs index f4ae34c7..d2387c52 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -9,11 +9,11 @@ use std::convert::AsRef; use std::hash::{Hash, Hasher}; use std::path::Path; +use thiserror::Error; + use crate::Rc; use crate::Value; -use anyhow::{anyhow, bail, Result}; - #[derive(Clone)] struct SourceInternal { pub file: String, @@ -26,6 +26,14 @@ pub struct Source { src: Rc, } +#[derive(Error, Debug)] +pub enum LexerError { + #[error("io failed: {0}")] + IoFailed(#[from] std::io::Error), + #[error("{0}")] + DetailedError(String), +} + impl std::cmp::Ord for Source { fn cmp(&self, other: &Source) -> std::cmp::Ordering { Rc::as_ptr(&self.src).cmp(&Rc::as_ptr(&other.src)) @@ -151,11 +159,8 @@ impl Source { } } - pub fn from_file>(path: P) -> Result { - let contents = match std::fs::read_to_string(&path) { - Ok(c) => c, - Err(e) => bail!("Failed to read {}. {e}", path.as_ref().display()), - }; + pub fn from_file>(path: P) -> Result { + let contents = std::fs::read_to_string(&path)?; // TODO: retain path instead of converting to string Ok(Self::new( path.as_ref().to_string_lossy().to_string(), @@ -206,8 +211,8 @@ impl Source { ) } - pub fn error(&self, line: u16, col: u16, msg: &str) -> anyhow::Error { - anyhow!(self.message(line, col, "error", msg)) + pub fn error(&self, line: u16, col: u16, msg: &str) -> LexerError { + LexerError::DetailedError(self.message(line, col, "error", msg)) } } @@ -233,7 +238,7 @@ impl Span { self.source.message(self.line, self.col, kind, msg) } - pub fn error(&self, msg: &str) -> anyhow::Error { + pub fn error(&self, msg: &str) -> LexerError { self.source.error(self.line, self.col, msg) } } @@ -300,7 +305,7 @@ impl<'source> Lexer<'source> { } } - fn read_ident(&mut self) -> Result { + fn read_ident(&mut self) -> Result { let start = self.peek().0; let col = self.col; loop { @@ -332,7 +337,7 @@ impl<'source> Lexer<'source> { } // See https://www.json.org/json-en.html for number's grammar - fn read_number(&mut self) -> Result { + fn read_number(&mut self) -> Result { let (start, chr) = self.peek(); let col = self.col; self.iter.next(); @@ -385,7 +390,7 @@ impl<'source> Lexer<'source> { m => m.to_owned(), }; - bail!( + return Err(LexerError::DetailedError(format!( "{} {}", self.source.error( self.line, @@ -393,7 +398,7 @@ impl<'source> Lexer<'source> { "invalid number. serde_json cannot parse number:" ), msg - ) + ))); } } @@ -409,7 +414,7 @@ impl<'source> Lexer<'source> { )) } - fn read_raw_string(&mut self) -> Result { + fn read_raw_string(&mut self) -> Result { self.iter.next(); self.col += 1; let (start, _) = self.peek(); @@ -446,7 +451,7 @@ impl<'source> Lexer<'source> { )) } - fn read_string(&mut self) -> Result { + fn read_string(&mut self) -> Result { let (line, col) = (self.line, self.col); self.iter.next(); self.col += 1; @@ -507,12 +512,12 @@ impl<'source> Lexer<'source> { Err(e) => { let serde_msg = &e.to_string(); let msg = serde_msg; - bail!( + return Err(LexerError::DetailedError(format!( "{} {}", self.source .error(self.line, col, "serde_json cannot parse string:"), msg - ) + ))); } } @@ -528,7 +533,7 @@ impl<'source> Lexer<'source> { )) } - fn skip_ws(&mut self) -> Result<()> { + fn skip_ws(&mut self) -> Result<(), LexerError> { // Only the 4 json whitespace characters are recognized. // https://www.crockford.com/mckeeman.html. // Additionally, comments are also skipped. @@ -566,7 +571,7 @@ impl<'source> Lexer<'source> { Ok(()) } - pub fn next_token(&mut self) -> Result { + pub fn next_token(&mut self) -> Result { self.skip_ws()?; let (start, chr) = self.peek(); diff --git a/src/lib.rs b/src/lib.rs index faef1488..1530f863 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -303,10 +303,12 @@ pub struct QueryResults { pub result: Vec, } +pub type FnError = Box; + /// A user defined builtin function implementation. /// /// It is not necessary to implement this trait directly. -pub trait Extension: FnMut(Vec) -> anyhow::Result + Send + Sync { +pub trait Extension: FnMut(Vec) -> Result + Send + Sync { /// Fn, FnMut etc are not sized and cannot be cloned in their boxed form. /// clone_box exists to overcome that. fn clone_box<'a>(&self) -> Box @@ -317,7 +319,7 @@ pub trait Extension: FnMut(Vec) -> anyhow::Result + Send + Sync { /// Automatically make matching closures a valid [`Extension`]. impl Extension for F where - F: FnMut(Vec) -> anyhow::Result + Clone + Send + Sync, + F: FnMut(Vec) -> Result + Clone + Send + Sync, { fn clone_box<'a>(&self) -> Box where @@ -343,6 +345,11 @@ impl std::fmt::Debug for dyn Extension { #[cfg(feature = "coverage")] #[cfg_attr(docsrs, doc(cfg(feature = "coverage")))] pub mod coverage { + use std::io; + use std::io::Write; + use std::str::Utf8Error; + use thiserror::Error; + #[derive(Default, serde::Serialize, serde::Deserialize)] /// Coverage information about a rego policy file. pub struct File { @@ -366,6 +373,14 @@ pub mod coverage { pub files: Vec, } + #[derive(Error, Debug)] + pub enum ReportError { + #[error(transparent)] + Io(#[from] io::Error), + #[error(transparent)] + Utf8Error(#[from] Utf8Error), + } + impl Report { /// Produce an ANSI color encoded version of the report. /// @@ -374,8 +389,7 @@ pub mod coverage { /// /// - pub fn to_colored_string(&self) -> anyhow::Result { - use std::io::Write; + pub fn to_colored_string(&self) -> Result { let mut s = Vec::new(); writeln!(&mut s, "COVERAGE REPORT:")?; for file in self.files.iter() { @@ -398,7 +412,8 @@ pub mod coverage { } writeln!(&mut s)?; - Ok(std::str::from_utf8(&s)?.to_string()) + let utf8_str = std::str::from_utf8(&s)?; + Ok(utf8_str.to_string()) } } } diff --git a/src/number.rs b/src/number.rs index 735695dd..20c7a8d4 100644 --- a/src/number.rs +++ b/src/number.rs @@ -3,14 +3,14 @@ use core::fmt::{Debug, Formatter}; use std::cmp::{Ord, Ordering}; +use std::num::ParseFloatError; use std::str::FromStr; -use anyhow::{anyhow, bail, Result}; - use serde::ser::Serializer; use serde::Serialize; use crate::Rc; +use thiserror::Error; pub type BigInt = i128; @@ -132,6 +132,16 @@ impl From for Number { } } +#[derive(Error, Debug)] +pub enum NumberError { + #[error("parsing number failed: {0}")] + ParsingError(#[from] ParseFloatError), + #[error("conversion to big decimal failed")] + ToBigFailed, + #[error("calculation failed: {0}")] + CalculationFailed(String), +} + impl Number { pub fn as_u128(&self) -> Option { match self { @@ -191,25 +201,20 @@ impl Number { }) } - pub fn to_big(&self) -> Result> { - match self.as_big() { - Some(b) => Ok(b), - _ => bail!("Number::to_big failed"), - } + pub fn to_big(&self) -> Result, NumberError> { + self.as_big().ok_or(NumberError::ToBigFailed) } } -#[derive(Debug, PartialEq, Eq)] -pub struct ParseNumberError; - impl FromStr for Number { - type Err = ParseNumberError; + type Err = NumberError; fn from_str(s: &str) -> Result { if let Ok(v) = BigFloat::from_str(s) { return Ok(v.into()); } - Ok(f64::from_str(s).map_err(|_| ParseNumberError)?.into()) + let n = f64::from_str(s)?; + Ok(n.into()) } } @@ -238,54 +243,56 @@ impl PartialOrd for Number { } impl Number { - pub fn add_assign(&mut self, rhs: &Self) -> Result<()> { + pub fn add_assign(&mut self, rhs: &Self) -> Result<(), NumberError> { *self = self.add(rhs)?; Ok(()) } - pub fn add(&self, rhs: &Self) -> Result { + pub fn add(&self, rhs: &Self) -> Result { match (self, rhs) { (Big(a), Big(b)) => Ok(Big(BigDecimal::from(&a.d + &b.d).into())), } } - pub fn sub_assign(&mut self, rhs: &Self) -> Result<()> { + pub fn sub_assign(&mut self, rhs: &Self) -> Result<(), NumberError> { *self = self.sub(rhs)?; Ok(()) } - pub fn sub(&self, rhs: &Self) -> Result { + pub fn sub(&self, rhs: &Self) -> Result { match (self, rhs) { (Big(a), Big(b)) => Ok(Big(BigDecimal::from(&a.d - &b.d).into())), } } - pub fn mul_assign(&mut self, rhs: &Self) -> Result<()> { + pub fn mul_assign(&mut self, rhs: &Self) -> Result<(), NumberError> { *self = self.mul(rhs)?; Ok(()) } - pub fn mul(&self, rhs: &Self) -> Result { + pub fn mul(&self, rhs: &Self) -> Result { match (self, rhs) { (Big(a), Big(b)) => Ok(Big(BigDecimal::from(&a.d * &b.d).into())), } } - pub fn divide(self, rhs: &Self) -> Result { + pub fn divide(self, rhs: &Self) -> Result { match (self, rhs) { (Big(a), Big(b)) => { let c = a.d.div_truncate(&b.d, PRECISION) - .map_err(|e| anyhow!("{e}"))?; + .map_err(|e| NumberError::CalculationFailed(e.to_string()))?; Ok(Big(BigDecimal::from(c).into())) } } } - pub fn modulo(self, rhs: &Self) -> Result { + pub fn modulo(self, rhs: &Self) -> Result { match (self, rhs) { (Big(a), Big(b)) => { - let (_, c) = a.d.div_rem(&b.d).map_err(|e| anyhow!("{e}"))?; + let (_, c) = + a.d.div_rem(&b.d) + .map_err(|e| NumberError::CalculationFailed(e.to_string()))?; Ok(Big(BigDecimal::from(c).into())) } } @@ -400,7 +407,7 @@ impl Number { } } - pub fn two_pow(e: i32) -> Result { + pub fn two_pow(e: i32) -> Result { if e >= 0 { Ok(BigFloat::from(2).powi(e as usize).into()) } else { @@ -408,7 +415,7 @@ impl Number { } } - pub fn ten_pow(e: i32) -> Result { + pub fn ten_pow(e: i32) -> Result { if e >= 0 { Ok(BigFloat::from(10).powi(e as usize).into()) } else { diff --git a/src/parser.rs b/src/parser.rs index b8705be2..70037231 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,6 +2,7 @@ // Licensed under the MIT License. use crate::ast::*; +use crate::bail; use crate::lexer::*; use crate::number::*; use crate::value::*; @@ -9,7 +10,21 @@ use crate::value::*; use std::collections::BTreeMap; use std::str::FromStr; -use anyhow::{anyhow, bail, Result}; +use thiserror::Error; + +type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum ParserError { + #[error("value error: {0}")] + ValueError(#[from] ValueError), + #[error("lexer error: {0}")] + LexerError(#[from] LexerError), + #[error("internal error: not a compr")] + NotACompr, + #[error("{0}")] + DetailedError(String), +} #[derive(Clone)] pub struct Parser<'source> { @@ -60,7 +75,7 @@ impl<'source> Parser<'source> { self.next_token() } else { let msg = format!("expecting `{text}` {context}"); - Err(self.source.error(self.tok.1.line, self.tok.1.col, &msg)) + bail!(self.source.error(self.tok.1.line, self.tok.1.col, &msg)); } } @@ -82,15 +97,17 @@ impl<'source> Parser<'source> { pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> { match &self.future_keywords.get(kw) { - Some(s) if self.rego_v1 => Err(self.source.error( - span.line, - span.col, - format!( - "this import shadows previous import of `{kw}` defined at:{}", - s.message("", "this import is shadowed.") - ) - .as_str(), - )), + Some(s) if self.rego_v1 => { + bail!(self.source.error( + span.line, + span.col, + format!( + "this import shadows previous import of `{kw}` defined at:{}", + s.message("", "this import is shadowed.") + ) + .as_str(), + )); + } _ => { self.future_keywords.insert(kw.to_string(), span.clone()); if kw == "every" && !self.rego_v1 { @@ -124,7 +141,9 @@ impl<'source> Parser<'source> { } } - _ => bail!(refr.span().error("not a valid ref")), + _ => { + bail!(refr.span().error("not a valid ref")); + } } Ok(()) } @@ -147,7 +166,7 @@ impl<'source> Parser<'source> { } _ => { let s = &comps[3]; - return Err(self + bail!(self .source .error(s.line, s.col - 1, "invalid future keyword")); } @@ -155,9 +174,9 @@ impl<'source> Parser<'source> { Ok(true) } else if !comps.is_empty() && comps[0].text() == "future" { let s = &comps[0]; - Err(self + bail!(self .source - .error(s.line, s.col, "invalid import, must be `future.keywords`")) + .error(s.line, s.col, "invalid import, must be `future.keywords`")); } else { Ok(false) } @@ -205,18 +224,22 @@ impl<'source> Parser<'source> { fn parse_ident(&mut self) -> Result { let span = self.tok.1.clone(); match self.tok.0 { - TokenKind::Ident if self.is_keyword(span.text()) => Err(self.source.error( - self.tok.1.line, - self.tok.1.col, - &format!("unexpected keyword `{}`", span.text()), - )), + TokenKind::Ident if self.is_keyword(span.text()) => { + bail!(self.source.error( + self.tok.1.line, + self.tok.1.col, + &format!("unexpected keyword `{}`", span.text()), + )); + } TokenKind::Ident => { self.next_token()?; Ok(span) } - _ => Err(self - .source - .error(self.tok.1.line, self.tok.1.col, "expecting identifier")), + _ => { + bail!(self + .source + .error(self.tok.1.line, self.tok.1.col, "expecting identifier")); + } } } @@ -229,26 +252,30 @@ impl<'source> Parser<'source> { // contains can be the name of a builtin even when a keyword && span.text() != "contains") => { - Err(self.source.error( + bail!(self.source.error( self.tok.1.line, self.tok.1.col, &format!("unexpected keyword `{}`", span.text()), - )) + )); } TokenKind::Ident => { self.next_token()?; Ok(span) } - _ => Err(self - .source - .error(self.tok.1.line, self.tok.1.col, "expecting identifier")), + _ => { + bail!(self + .source + .error(self.tok.1.line, self.tok.1.col, "expecting identifier")); + } } } fn read_number(span: Span) -> Result { match Number::from_str(span.text()) { Ok(v) => Ok(Expr::Number((span, Value::Number(v)))), - Err(_) => bail!(span.error("could not parse number")), + Err(_) => { + bail!(span.error("could not parse number")); + } } } @@ -260,7 +287,9 @@ impl<'source> Parser<'source> { let v = match serde_json::from_str::(format!("\"{}\"", span.text()).as_str()) { Ok(v) => v, - Err(e) => bail!(span.error(format!("invalid string literal. {e}").as_str())), + Err(e) => { + bail!(span.error(format!("invalid string literal. {e}").as_str())); + } }; Expr::String((span, v)) } @@ -279,11 +308,9 @@ impl<'source> Parser<'source> { } }, _ => { - return Err(self.source.error( - self.tok.1.line, - self.tok.1.col, - "expecting expression", - )) + bail!(self + .source + .error(self.tok.1.line, self.tok.1.col, "expecting expression",)); } }; self.next_token()?; @@ -301,7 +328,7 @@ impl<'source> Parser<'source> { _ => { // Not a comprehension. Restore state. *self = state; - bail!("internal error: not a compr"); + return Err(ParserError::NotACompr); } }; @@ -317,7 +344,7 @@ impl<'source> Parser<'source> { // No progress was made in parsing the query. // Restore state and try parsing as set, array or object. *self = state; - bail!("internal error: not a compr"); + Err(ParserError::NotACompr) } Err(err) => Err(err), } @@ -538,14 +565,11 @@ impl<'source> Parser<'source> { // literal. break; } - bail!( - "{}", - self.source.error( - self.tok.1.line, - self.tok.1.col, - format!("invalid whitespace before {}", self.token_text()).as_str() - ) - ); + bail!(self.source.error( + self.tok.1.line, + self.tok.1.col, + format!("invalid whitespace before {}", self.token_text()).as_str(), + )); } "." => { // Read identifier. @@ -555,14 +579,11 @@ impl<'source> Parser<'source> { // Disallow any whitespace between . and identifier. if field.start != sep_pos + 1 { - bail!( - "{}", - self.source.error( - field.line, - field.col - 1, - "invalid whitespace between . and identifier" - ) - ); + bail!(self.source.error( + field.line, + field.col - 1, + "invalid whitespace between . and identifier", + )); } let fieldv = Value::from(field.text()); term = Expr::RefDot { @@ -879,11 +900,11 @@ impl<'source> Parser<'source> { match self.parse_var() { Ok(v) => (Some(ident), v), Err(e) => { - return Err(self.source.error( + bail!(self.source.error( span.line, span.col, format!("Failed to parse `every` statement.\n{e}").as_str(), - )) + )); } } } @@ -932,13 +953,14 @@ impl<'source> Parser<'source> { match ref_expr.as_ref() { Expr::Var(_) => (), _ => { - return Err(anyhow!( + let err_str = format!( "{}:{}:{} error: encountered `{}` while expecting identifier", span.source.file(), span.line, span.col, span.text() - )); + ); + return Err(ParserError::DetailedError(err_str)); } } } @@ -952,13 +974,14 @@ impl<'source> Parser<'source> { 1 => (None, refs[0].clone()), _ => { let span = &vars[2]; - return Err(anyhow!( + let err_str = format!( "{}:{}:{} error: encountered `{}` while expecting `in`", span.source.file(), span.line, span.col, span.text() - )); + ); + return Err(ParserError::DetailedError(err_str)); } }; @@ -1036,7 +1059,10 @@ impl<'source> Parser<'source> { // This is likely an array or set. // Restore the state. *self = state; - return Err(anyhow!("encountered , when expecting {}", end_delim)); + return Err(ParserError::DetailedError(format!( + "encountered , when expecting {}", + end_delim + ))); } literals.push(stmt); @@ -1107,14 +1133,11 @@ impl<'source> Parser<'source> { span.start = start; match self.token_text() { "." | "[" if self.tok.1.start != self.end => { - bail!( - "{}", - self.source.error( - self.tok.1.line, - self.tok.1.col - 1, - format!("invalid whitespace before {}", self.token_text()).as_str() - ) - ); + bail!(self.source.error( + self.tok.1.line, + self.tok.1.col - 1, + format!("invalid whitespace before {}", self.token_text()).as_str(), + )); } "." => { // Read identifier. @@ -1124,14 +1147,11 @@ impl<'source> Parser<'source> { // Disallow any whitespace between . and identifier. if field.start != sep_pos + 1 { - bail!( - "{}", - self.source.error( - field.line, - field.col - 1, - "invalid whitespace between . and identifier" - ) - ); + bail!(self.source.error( + field.line, + field.col - 1, + "invalid whitespace between . and identifier", + )); } refr = Expr::RefDot { span, @@ -1144,7 +1164,7 @@ impl<'source> Parser<'source> { let index = match &self.tok.0 { TokenKind::String => Expr::String(Self::span_and_value(self.tok.1.clone())), _ => { - return Err(self.source.error( + bail!(self.source.error( self.tok.1.line, self.tok.1.col, "expected string", @@ -1183,7 +1203,7 @@ impl<'source> Parser<'source> { } Expr::Var(Self::span_and_value(v)) } else { - return Err(self.source.error( + bail!(self.source.error( span.line, span.col, "expecting identifier. Failed to parse rule-ref.", @@ -1196,14 +1216,11 @@ impl<'source> Parser<'source> { match self.token_text() { // . and [ must not have any space between the previous token. "." | "[" if self.tok.1.start != self.end => { - bail!( - "{}", - self.source.error( - self.tok.1.line, - self.tok.1.col - 1, - format!("invalid whitespace before {}", self.token_text()).as_str() - ) - ); + bail!(self.source.error( + self.tok.1.line, + self.tok.1.col - 1, + format!("invalid whitespace before {}", self.token_text()).as_str(), + )); } "." => { let sep_pos = self.tok.1.start; @@ -1213,14 +1230,11 @@ impl<'source> Parser<'source> { // Disallow any whitespace between . and identifier. if field.start != sep_pos + 1 { - bail!( - "{}", - self.source.error( - field.line, - field.col - 1, - "invalid whitespace between . and identifier" - ) - ); + bail!(self.source.error( + field.line, + field.col - 1, + "invalid whitespace between . and identifier", + )); } term = Expr::RefDot { span, @@ -1421,11 +1435,11 @@ impl<'source> Parser<'source> { match self.token_text() { "{" => { - return Err(self.source.error( + bail!(self.source.error( self.tok.1.line, self.tok.1.col, "expected `else` keyword", - )) + )); } "else" => self.next_token()?, _ => break, @@ -1461,7 +1475,7 @@ impl<'source> Parser<'source> { if self.token_text() == "if" { self.warn_future_keyword(); } - return Err(self.source.error( + bail!(self.source.error( self.tok.1.line, self.tok.1.col, "expected assignment or query after `else`", @@ -1603,7 +1617,7 @@ impl<'source> Parser<'source> { }; if shadow { - return Err(self.source.error( + bail!(self.source.error( import.span.line, import.span.col, format!( @@ -1634,7 +1648,7 @@ impl<'source> Parser<'source> { let comps = Self::get_path_ref_components(&refr)?; span.end = self.end; if !matches!(comps[0].text(), "data" | "future" | "input" | "rego") { - return Err(self.source.error( + bail!(self.source.error( comps[0].line, comps[0].col, "import path must begin with one of: {data, future, input, rego}", @@ -1654,7 +1668,7 @@ impl<'source> Parser<'source> { let var = if self.token_text() == "as" { if is_future_kw { - return Err(self.source.error( + bail!(self.source.error( self.tok.1.line, self.tok.1.col, "`future` imports cannot be aliased", @@ -1664,11 +1678,9 @@ impl<'source> Parser<'source> { self.next_token()?; let var = self.parse_var()?; if var.text() == "_" { - return Err(self.source.error( - var.line, - var.col, - "`_` cannot be used as alias", - )); + bail!(self + .source + .error(var.line, var.col, "`_` cannot be used as alias",)); } Some(var) } else { diff --git a/src/scheduler.rs b/src/scheduler.rs index 83f974ba..09775030 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -9,7 +9,21 @@ use crate::utils::*; use std::collections::{BTreeMap, BTreeSet, VecDeque}; use std::string::String; -use anyhow::{bail, Result}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum SchedulerError { + #[error("package scope missing")] + MissingPackageScope, + #[error("lexer error: {0}")] + LexerError(#[from] LexerError), + #[error("non-empty definitions")] + NonEmptyDefinitions, + #[error(transparent)] + UtilsError(#[from] UtilsError), +} + +pub(crate) type Result = std::result::Result; #[derive(Debug)] pub struct Definition { @@ -458,10 +472,10 @@ impl Analyzer { fn analyze_module(&mut self, m: &Module) -> Result<()> { let path = get_path_string(&m.package.refr, Some("data"))?; - let scope = match self.packages.get(&path) { - Some(s) => s, - _ => bail!("internal error: package scope missing"), - }; + let scope = self + .packages + .get(&path) + .ok_or(SchedulerError::MissingPackageScope)?; self.current_module_path = path; self.scopes.push(scope.clone()); for r in &m.policy { @@ -658,9 +672,9 @@ impl Analyzer { } } } - bail!(v - .0 - .error(format!("use of undefined variable `{name}` is unsafe").as_str())); + let err = + v.0.error(format!("use of undefined variable `{name}` is unsafe").as_str()); + Err(err)?; } Ok(false) } @@ -804,7 +818,7 @@ impl Analyzer { ) => { if lhs_items.len() != rhs_items.len() { let span = rhs.span(); - bail!(span.error("mismatch in number of array elements")); + Err(span.error("mismatch in number of array elements"))?; } for (idx, lhs_elem) in lhs_items.iter().enumerate() { @@ -931,13 +945,14 @@ impl Analyzer { let name = var.source_str(); if let Some(r#use) = first_use.get(&name) { if r#use.line < var.line || (r#use.line == var.line && r#use.col < var.col) { - bail!(r#use.error( + let err = r#use.error( format!( "var `{name}` used before definition below.{}", var.message("definition", "") ) - .as_str() - )); + .as_str(), + ); + Err(err)?; } } Ok(()) @@ -1066,7 +1081,7 @@ impl Analyzer { uv.append(&mut with_mods_used_vars.clone()); comprs.append(&mut with_mods_comprs.clone()); if !definitions.is_empty() { - bail!("internal error: non empty definitions"); + return Err(SchedulerError::NonEmptyDefinitions); } used_vars.extend(uv); self.process_comprs( @@ -1207,7 +1222,8 @@ impl Analyzer { self.order.insert(query.clone(), ord); } Err(err) => { - bail!(query.span.error(&err.to_string())) + let err = query.span.error(&err.to_string()); + Err(err)?; } _ => (), } diff --git a/src/utils.rs b/src/utils.rs index a03ffade..cf6f1356 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,9 +6,27 @@ use crate::builtins::*; use crate::lexer::*; use std::collections::BTreeMap; +use thiserror::Error; -use anyhow::{bail, Result}; -pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result { +#[macro_export] +macro_rules! bail { + ($err:literal $(,)?) => { + return Err(std::convert::From::from($err)) + }; + ($err:expr $(,)?) => { + return Err(std::convert::From::from($err)) + }; +} + +#[derive(Error, Debug)] +pub enum UtilsError { + #[error("not a simple ref: {0}")] + NotASimpleRef(String), + #[error("fullpath {full_path} was previously defined with {arity} arguments.")] + PreviouslyDefined { full_path: String, arity: u8 }, +} + +pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result { let mut comps: Vec<&str> = vec![]; let mut expr = Some(refr); while expr.is_some() { @@ -27,7 +45,10 @@ pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result { comps.push(v.0.text()); expr = None; } - _ => bail!("internal error: not a simple ref {expr:?}"), + _ => { + let expr = format!("{:?}", expr); + return Err(UtilsError::NotASimpleRef(expr)); + } } } if let Some(d) = document { @@ -43,7 +64,7 @@ fn get_extra_arg_impl( expr: &Expr, module: Option<&str>, functions: &FunctionTable, -) -> Result>> { +) -> Result>, UtilsError> { if let Expr::Call { fcn, params, .. } = expr { let full_path = get_path_string(fcn, module)?; let n_args = if let Some((_, n_args, _)) = functions.get(&full_path) { @@ -83,14 +104,13 @@ pub fn get_extra_arg( } } -pub fn gather_functions(modules: &[Ref]) -> Result { +pub fn gather_functions(modules: &[Ref]) -> Result { let mut table = FunctionTable::new(); for module in modules { let module_path = get_path_string(&module.package.refr, Some("data"))?; for rule in &module.policy { if let Rule::Spec { - span, head: RuleHead::Func { refr, args, .. }, .. } = rule.as_ref() @@ -99,10 +119,10 @@ pub fn gather_functions(modules: &[Ref]) -> Result { if let Some((functions, arity, _)) = table.get_mut(&full_path) { if args.len() as u8 != *arity { - bail!(span.error( - format!("{full_path} was previously defined with {arity} arguments.") - .as_str() - )); + return Err(UtilsError::PreviouslyDefined { + full_path, + arity: *arity, + }); } functions.push(rule.clone()); } else { @@ -117,7 +137,7 @@ pub fn gather_functions(modules: &[Ref]) -> Result { Ok(table) } -pub fn get_root_var(mut expr: &Expr) -> Result { +pub fn get_root_var(mut expr: &Expr) -> Result { let empty = expr.span().source_str().clone_empty(); loop { match expr { diff --git a/src/value.rs b/src/value.rs index b152008f..49f01724 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::number::Number; +use crate::number::{Number, NumberError}; use core::fmt; use std::collections::{BTreeMap, BTreeSet}; @@ -10,10 +10,10 @@ use std::ops; use std::path::Path; use std::str::FromStr; -use anyhow::{anyhow, bail, Result}; use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor}; use serde::ser::{SerializeMap, Serializer}; use serde::{Deserialize, Serialize}; +use thiserror::Error; use crate::Rc; @@ -59,6 +59,45 @@ pub enum Value { Undefined, } +#[derive(Debug)] +pub enum Primitive { + Bool, + I128, + U128, + I64, + U64, + F64, + String, + Number, + Array, + Set, + Object, +} + +#[derive(Error, Debug)] +pub enum ValueError { + #[error("json deserialization failed: {0}")] + JsonDeserializationFailed(#[from] serde_json::Error), + #[error("yaml deserialization failed: {0}")] + YamlDeserializationFailed(#[from] serde_yaml::Error), + #[error("io failed: {0}")] + IoFailed(#[from] std::io::Error), + #[error("number conversion failed: {0}")] + NumberConversionFailed(#[from] NumberError), + #[error("not a {0:?}")] + NotA(Primitive), + #[error("unexpected error")] + Unexpected, + #[error("value for key {key} generated multiple times: {first}, {second}")] + ValueGeneratedMultipleTimes { + key: String, + first: String, + second: String, + }, + #[error("could not merge")] + MergeFailed, +} + #[doc(hidden)] impl Serialize for Value { fn serialize(&self, serializer: S) -> Result @@ -308,7 +347,7 @@ impl Value { /// # Ok(()) /// # } /// ``` - pub fn from_json_str(json: &str) -> Result { + pub fn from_json_str(json: &str) -> Result { Ok(serde_json::from_str(json)?) } @@ -326,11 +365,9 @@ impl Value { /// # Ok(()) /// # } /// ``` - pub fn from_json_file>(path: P) -> Result { - match std::fs::read_to_string(&path) { - Ok(c) => Self::from_json_str(c.as_str()), - Err(e) => bail!("Failed to read {}. {e}", path.as_ref().display()), - } + pub fn from_json_file>(path: P) -> Result { + let c = std::fs::read_to_string(&path)?; + Self::from_json_str(c.as_str()) } /// Serialize a value to JSON. @@ -392,25 +429,23 @@ impl Value { /// # Ok(()) /// # } /// ``` - pub fn to_json_str(&self) -> Result { + pub fn to_json_str(&self) -> Result { Ok(serde_json::to_string_pretty(self)?) } /// Deserialize a value from YAML. /// Note: Deserialization from YAML does not support arbitrary precision numbers. #[cfg(feature = "yaml")] - pub fn from_yaml_str(yaml: &str) -> Result { + pub fn from_yaml_str(yaml: &str) -> Result { Ok(serde_yaml::from_str(yaml)?) } /// Deserialize a value from a file containing YAML. /// Note: Deserialization from YAML does not support arbitrary precision numbers. #[cfg(feature = "yaml")] - pub fn from_yaml_file(path: &String) -> Result { - match std::fs::read_to_string(path) { - Ok(c) => Self::from_yaml_str(c.as_str()), - Err(e) => bail!("Failed to read {path}. {e}"), - } + pub fn from_yaml_file(path: &String) -> Result { + let c = std::fs::read_to_string(path)?; + Self::from_yaml_str(c.as_str()) } } @@ -649,10 +684,8 @@ impl Value { /// # Ok(()) /// # } /// ``` - pub fn from_numeric_string(s: &str) -> Result { - Ok(Value::Number( - Number::from_str(s).map_err(|_| anyhow!("not a valid numeric string"))?, - )) + pub fn from_numeric_string(s: &str) -> Result { + Ok(Value::Number(Number::from_str(s)?)) } } @@ -765,10 +798,10 @@ impl Value { /// assert_eq!(v.as_bool()?, &true); /// # Ok(()) /// # } - pub fn as_bool(&self) -> Result<&bool> { + pub fn as_bool(&self) -> Result<&bool, ValueError> { match self { Value::Bool(b) => Ok(b), - _ => Err(anyhow!("not a bool")), + _ => Err(ValueError::NotA(Primitive::Bool)), } } @@ -780,10 +813,10 @@ impl Value { /// *v.as_bool_mut()? = false; /// # Ok(()) /// # } - pub fn as_bool_mut(&mut self) -> Result<&mut bool> { + pub fn as_bool_mut(&mut self) -> Result<&mut bool, ValueError> { match self { Value::Bool(b) => Ok(b), - _ => Err(anyhow!("not a bool")), + _ => Err(ValueError::NotA(Primitive::Bool)), } } @@ -802,15 +835,15 @@ impl Value { /// assert!(v.as_u128().is_err()); /// # Ok(()) /// # } - pub fn as_u128(&self) -> Result { + pub fn as_u128(&self) -> Result { match self { Value::Number(b) => { if let Some(n) = b.as_u128() { return Ok(n); } - bail!("not a u128"); + Err(ValueError::NotA(Primitive::U128)) } - _ => Err(anyhow!("not a u128")), + _ => Err(ValueError::NotA(Primitive::U128)), } } @@ -829,15 +862,15 @@ impl Value { /// assert!(v.as_i128().is_err()); /// # Ok(()) /// # } - pub fn as_i128(&self) -> Result { + pub fn as_i128(&self) -> Result { match self { Value::Number(b) => { if let Some(n) = b.as_i128() { return Ok(n); } - bail!("not a i128"); + Err(ValueError::NotA(Primitive::I128)) } - _ => Err(anyhow!("not a i128")), + _ => Err(ValueError::NotA(Primitive::I128)), } } @@ -856,15 +889,15 @@ impl Value { /// assert!(v.as_u64().is_err()); /// # Ok(()) /// # } - pub fn as_u64(&self) -> Result { + pub fn as_u64(&self) -> Result { match self { Value::Number(b) => { if let Some(n) = b.as_u64() { return Ok(n); } - bail!("not a u64"); + Err(ValueError::NotA(Primitive::U64)) } - _ => Err(anyhow!("not a u64")), + _ => Err(ValueError::NotA(Primitive::U64)), } } @@ -883,15 +916,15 @@ impl Value { /// assert!(v.as_i64().is_err()); /// # Ok(()) /// # } - pub fn as_i64(&self) -> Result { + pub fn as_i64(&self) -> Result { match self { Value::Number(b) => { if let Some(n) = b.as_i64() { return Ok(n); } - bail!("not an i64"); + Err(ValueError::NotA(Primitive::I64)) } - _ => Err(anyhow!("not an i64")), + _ => Err(ValueError::NotA(Primitive::I64)), } } @@ -909,15 +942,15 @@ impl Value { /// assert!(v.as_i64().is_err()); /// # Ok(()) /// # } - pub fn as_f64(&self) -> Result { + pub fn as_f64(&self) -> Result { match self { Value::Number(b) => { if let Some(n) = b.as_f64() { return Ok(n); } - bail!("not a f64"); + Err(ValueError::NotA(Primitive::F64)) } - _ => Err(anyhow!("not a f64")), + _ => Err(ValueError::NotA(Primitive::F64)), } } @@ -929,10 +962,10 @@ impl Value { /// assert_eq!(v.as_string()?.as_ref(), "Hello"); /// # Ok(()) /// # } - pub fn as_string(&self) -> Result<&Rc> { + pub fn as_string(&self) -> Result<&Rc, ValueError> { match self { Value::String(s) => Ok(s), - _ => Err(anyhow!("not a string")), + _ => Err(ValueError::NotA(Primitive::String)), } } @@ -944,26 +977,26 @@ impl Value { /// *v.as_string_mut()? = "World".into(); /// # Ok(()) /// # } - pub fn as_string_mut(&mut self) -> Result<&mut Rc> { + pub fn as_string_mut(&mut self) -> Result<&mut Rc, ValueError> { match self { Value::String(s) => Ok(s), - _ => Err(anyhow!("not a string")), + _ => Err(ValueError::NotA(Primitive::String)), } } #[doc(hidden)] - pub fn as_number(&self) -> Result<&Number> { + pub fn as_number(&self) -> Result<&Number, ValueError> { match self { Value::Number(n) => Ok(n), - _ => Err(anyhow!("not a number")), + _ => Err(ValueError::NotA(Primitive::Number)), } } #[doc(hidden)] - pub fn as_number_mut(&mut self) -> Result<&mut Number> { + pub fn as_number_mut(&mut self) -> Result<&mut Number, ValueError> { match self { Value::Number(n) => Ok(n), - _ => Err(anyhow!("not a number")), + _ => Err(ValueError::NotA(Primitive::Number)), } } @@ -975,10 +1008,10 @@ impl Value { /// assert_eq!(v.as_array()?[0], Value::from("Hello")); /// # Ok(()) /// # } - pub fn as_array(&self) -> Result<&Vec> { + pub fn as_array(&self) -> Result<&Vec, ValueError> { match self { Value::Array(a) => Ok(a), - _ => Err(anyhow!("not an array")), + _ => Err(ValueError::NotA(Primitive::Array)), } } @@ -990,10 +1023,10 @@ impl Value { /// v.as_array_mut()?.push(Value::from("World")); /// # Ok(()) /// # } - pub fn as_array_mut(&mut self) -> Result<&mut Vec> { + pub fn as_array_mut(&mut self) -> Result<&mut Vec, ValueError> { match self { Value::Array(a) => Ok(Rc::make_mut(a)), - _ => Err(anyhow!("not an array")), + _ => Err(ValueError::NotA(Primitive::Array)), } } @@ -1011,10 +1044,10 @@ impl Value { /// assert_eq!(v.as_set()?.first(), Some(&Value::from("Hello"))); /// # Ok(()) /// # } - pub fn as_set(&self) -> Result<&BTreeSet> { + pub fn as_set(&self) -> Result<&BTreeSet, ValueError> { match self { Value::Set(s) => Ok(s), - _ => Err(anyhow!("not a set")), + _ => Err(ValueError::NotA(Primitive::Set)), } } @@ -1032,10 +1065,10 @@ impl Value { /// v.as_set_mut()?.insert(Value::from("World")); /// # Ok(()) /// # } - pub fn as_set_mut(&mut self) -> Result<&mut BTreeSet> { + pub fn as_set_mut(&mut self) -> Result<&mut BTreeSet, ValueError> { match self { Value::Set(s) => Ok(Rc::make_mut(s)), - _ => Err(anyhow!("not a set")), + _ => Err(ValueError::NotA(Primitive::Set)), } } @@ -1056,10 +1089,10 @@ impl Value { /// ); /// # Ok(()) /// # } - pub fn as_object(&self) -> Result<&BTreeMap> { + pub fn as_object(&self) -> Result<&BTreeMap, ValueError> { match self { Value::Object(m) => Ok(m), - _ => Err(anyhow!("not an object")), + _ => Err(ValueError::NotA(Primitive::Object)), } } @@ -1077,16 +1110,19 @@ impl Value { /// v.as_object_mut()?.insert(Value::from("Good"), Value::from("Bye")); /// # Ok(()) /// # } - pub fn as_object_mut(&mut self) -> Result<&mut BTreeMap> { + pub fn as_object_mut(&mut self) -> Result<&mut BTreeMap, ValueError> { match self { Value::Object(m) => Ok(Rc::make_mut(m)), - _ => Err(anyhow!("not an object")), + _ => Err(ValueError::NotA(Primitive::Object)), } } } impl Value { - pub(crate) fn make_or_get_value_mut<'a>(&'a mut self, paths: &[&str]) -> Result<&'a mut Value> { + pub(crate) fn make_or_get_value_mut<'a>( + &'a mut self, + paths: &[&str], + ) -> Result<&'a mut Value, ValueError> { if paths.is_empty() { return Ok(self); } @@ -1105,18 +1141,18 @@ impl Value { Value::Object(map) => match Rc::make_mut(map).get_mut(&key) { Some(v) if paths.len() == 1 => Ok(v), Some(v) => Self::make_or_get_value_mut(v, &paths[1..]), - _ => bail!("internal error: unexpected"), + _ => Err(ValueError::Unexpected), }, Value::Undefined if paths.len() > 1 => { *self = Value::new_object(); Self::make_or_get_value_mut(self, paths) } Value::Undefined => Ok(self), - _ => bail!("internal error: make: not an selfect {self:?}"), + _ => Err(ValueError::NotA(Primitive::Object)), } } - pub(crate) fn merge(&mut self, mut new: Value) -> Result<()> { + pub(crate) fn merge(&mut self, mut new: Value) -> Result<(), ValueError> { if self == &new { return Ok(()); } @@ -1129,18 +1165,20 @@ impl Value { for (k, v) in new.iter() { match map.get(k) { Some(pv) if *pv != *v => { - bail!( - "value for key `{}` generated multiple times: `{}` and `{}`", - serde_json::to_string_pretty(&k)?, - serde_json::to_string_pretty(&pv)?, - serde_json::to_string_pretty(&v)?, - ) + let key = serde_json::to_string_pretty(&k)?; + let first = serde_json::to_string_pretty(&pv)?; + let second = serde_json::to_string_pretty(&v)?; + let err = + ValueError::ValueGeneratedMultipleTimes { key, first, second }; + return Err(err); } _ => Rc::make_mut(map).insert(k.clone(), v.clone()), }; } } - _ => bail!("error: could not merge value"), + _ => { + return Err(ValueError::MergeFailed); + } }; Ok(()) } diff --git a/tests/aci/main.rs b/tests/aci/main.rs index 6b4f0284..c6c2e934 100644 --- a/tests/aci/main.rs +++ b/tests/aci/main.rs @@ -60,7 +60,8 @@ fn eval_test_case(dir: &Path, case: &TestCase) -> Result { } let result = Value::from(values); // Make result json compatible. (E.g: avoid sets). - Value::from_json_str(&result.to_string()) + let value = Value::from_json_str(&result.to_string())?; + Ok(value) } fn run_aci_tests(dir: &Path) -> Result<()> { diff --git a/tests/engine/mod.rs b/tests/engine/mod.rs index 7b8fa2b8..d6491a11 100644 --- a/tests/engine/mod.rs +++ b/tests/engine/mod.rs @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use anyhow::{bail, Result}; use regorus::*; +type Result = std::result::Result>; + #[test] fn extension() -> Result<()> { fn repeat(mut params: Vec) -> Result { diff --git a/tests/opa.rs b/tests/opa.rs index 59b2a798..5b3b1fa5 100644 --- a/tests/opa.rs +++ b/tests/opa.rs @@ -122,7 +122,8 @@ fn eval_test_case(case: &TestCase) -> Result { let result = Value::from(values); // Make result json compatible. (E.g: avoid sets). - Value::from_json_str(&result.to_string()) + let value = Value::from_json_str(&result.to_string())?; + Ok(value) } fn json_schema_tests_check(actual: &Value, expected: &Value) -> bool { diff --git a/tests/parser/mod.rs b/tests/parser/mod.rs index 7f611fda..4ca36a35 100644 --- a/tests/parser/mod.rs +++ b/tests/parser/mod.rs @@ -603,7 +603,8 @@ fn match_import(i: &Import, v: &Value) -> Result<()> { _ => Err(i .span .source - .error(i.span.line, i.span.col, "import does not have `as` binding")), + .error(i.span.line, i.span.col, "import does not have `as` binding") + .into()), } } @@ -679,7 +680,7 @@ fn yaml_test_impl(file: &str) -> Result<()> { } println!("{actual}"); } - _ => return Err(actual), + _ => return Err(actual.into()), }, }