Skip to content

Commit

Permalink
Cleanup features
Browse files Browse the repository at this point in the history
- Enabled default features specified in the spec.
- Removed patch versions from crates with stable minors.
- Added better support for number casting.
- Cleaned up imports.

Signed-off-by: Tin Švagelj <[email protected]>
  • Loading branch information
Caellian committed Nov 5, 2024
1 parent 5b02b08 commit 1bbe14e
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 75 deletions.
42 changes: 27 additions & 15 deletions example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,60 @@ name = "example"
version = "0.1.0"
edition = "2021"

[features]
axum = ["dep:axum", "dep:tokio", "dep:thiserror"]
serde = ["dep:serde", "cel-interpreter/serde"]
json = ["dep:serde_json", "cel-interpreter/json"]
chrono = ["dep:chrono", "cel-interpreter/chrono"]

[dependencies]
cel-interpreter = { path = "../interpreter", default-features = false }

chrono = { version = "0.4", optional = true }

serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true }

axum = { version = "0.7.5", default-features = false, features = [
"http1",
"json",
"tokio",
] }
cel-interpreter = { path = "../interpreter", features = ["json", "chrono", "regex"] }
chrono = "0.4.26"
serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.124"
thiserror = { version = "1.0.61", default-features = false }
], optional = true }
tokio = { version = "1.38.0", default-features = false, features = [
"macros",
"net",
"rt-multi-thread",
] }
], optional = true }
thiserror = { version = "1.0", optional = true }

[[bin]]
name = "simple"
name = "example-simple"
path = "src/simple.rs"

[[bin]]
name = "variables"
name = "example-variables"
path = "src/variables.rs"

[[bin]]
name = "functions"
name = "example-functions"
path = "src/functions.rs"
required-features = ["chrono"]

[[bin]]
name = "threads"
name = "example-threads"
path = "src/threads.rs"

[[bin]]
name = "serde"
name = "example-serde"
path = "src/serde.rs"
required-features = ["serde"]

[[bin]]
name = "axum"
name = "example-axum"
path = "src/axum.rs"
required-features = ["axum"]

[[bin]]
name = "json"
name = "example-json"
path = "src/json.rs"

required-features = ["json"]
17 changes: 11 additions & 6 deletions interpreter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ categories = ["compilers"]

[dependencies]
cel-parser = { path = "../parser", version = "0.8.0" }
thiserror = "1.0.40"
chrono = { version = "0.4.26", default-features = false, features = ["alloc"], optional = true }

nom = "7.1.3"
paste = "1.0.14"
serde = "1.0.196"

chrono = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
regex = { version = "1.10.5", optional = true }
serde_json = { version = "1.0.124", optional = true }
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
base64 = { version = "0.22.1", optional = true }

thiserror = "1.0"
paste = "1.0"

[dev-dependencies]
criterion = { version = "0.5.1", features = ["html_reports"] }
serde_bytes = "0.11.14"
Expand All @@ -28,6 +31,8 @@ name = "runtime"
harness = false

[features]
json = ["dep:base64", "dep:serde_json"]
default = ["regex", "chrono"]
json = ["serde", "dep:serde_json", "dep:base64"]
serde = ["dep:serde"]
regex = ["dep:regex"]
chrono = ["dep:chrono"]
9 changes: 3 additions & 6 deletions interpreter/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,10 +645,7 @@ pub fn max(Arguments(args): Arguments) -> Result<Value> {
#[cfg(test)]
mod tests {
use crate::context::Context;
use crate::testing::test_script;
#[cfg(feature = "regex")]
use crate::ExecutionError::FunctionError;
use std::collections::HashMap;
use crate::tests::test_script;

fn assert_script(input: &(&str, &str)) {
assert_eq!(test_script(input.1, None), Ok(true.into()), "{}", input.0);
Expand Down Expand Up @@ -679,7 +676,7 @@ mod tests {

for (name, script) in tests {
let mut ctx = Context::default();
ctx.add_variable_from_value("foo", HashMap::from([("bar", 1)]));
ctx.add_variable_from_value("foo", std::collections::HashMap::from([("bar", 1)]));
assert_eq!(test_script(script, Some(ctx)), Ok(true.into()), "{}", name);
}
}
Expand Down Expand Up @@ -943,7 +940,7 @@ mod tests {
test_script(
"'foobar'.matches('(foo') == true", None),
Err(
FunctionError {
crate::ExecutionError::FunctionError {
function: "matches".to_string(),
message: "'(foo' not a valid regex:\nregex parse error:\n (foo\n ^\nerror: unclosed group".to_string()
}
Expand Down
25 changes: 20 additions & 5 deletions interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ pub use cel_parser::Expression;
pub use context::Context;
pub use functions::FunctionContext;
pub use objects::{ResolveResult, Value};
#[cfg(feature = "chrono")]
mod duration;
pub mod functions;
mod magic;
pub mod objects;
mod resolvers;

#[cfg(feature = "chrono")]
mod duration;

#[cfg(feature = "serde")]
mod ser;
#[cfg(feature = "serde")]
pub use ser::to_value;

#[cfg(feature = "json")]
mod json;
#[cfg(test)]
mod testing;

use magic::FromContext;

Expand All @@ -45,6 +47,14 @@ pub enum ExecutionError {
/// but the type of the value was not supported as a key.
#[error("Unable to use value '{0:?}' as a key")]
UnsupportedKeyType(Value),
#[error(
"Casting number '{value:.2}' ({source_ty}) to {target_ty} type would cause an overflow"
)]
CastOverflow {
value: f64,
source_ty: &'static str,
target_ty: &'static str,
},
#[error("Unexpected type: got '{got}', want '{want}'")]
UnexpectedType { got: String, want: String },
/// Indicates that the script attempted to reference a key on a type that
Expand Down Expand Up @@ -173,11 +183,16 @@ impl TryFrom<&str> for Program {
mod tests {
use crate::context::Context;
use crate::objects::{ResolveResult, Value};
use crate::testing::test_script;
use crate::{ExecutionError, Program};
use std::collections::HashMap;
use std::convert::TryInto;

/// Tests the provided script and returns the result. An optional context can be provided.
pub(crate) fn test_script(script: &str, ctx: Option<Context>) -> ResolveResult {
let program = Program::compile(script).unwrap();
program.execute(&ctx.unwrap_or_default())
}

#[test]
fn parse() {
Program::compile("1 + 1").unwrap();
Expand Down
40 changes: 34 additions & 6 deletions interpreter/src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
#[macro_export]
macro_rules! impl_conversions {
// Capture pairs separated by commas, where each pair is separated by =>
($($target_type:ty => $value_variant:path),* $(,)?) => {
($($target_type:ty $(as $cast:ty)? => $value_variant:path),* $(,)?) => {
$(
impl FromValue for $target_type {
fn from_value(expr: &Value) -> Result<Self, ExecutionError> {
if let $value_variant(v) = expr {
Ok(v.clone())
$(if <$target_type>::MAX as $cast < *v {
return Err(ExecutionError::CastOverflow {
value: *v as f64,
source_ty: std::any::type_name::<$cast>(),
target_ty: std::any::type_name::<$target_type>(),
})
} else if <$target_type>::MIN as $cast > *v {
return Err(ExecutionError::CastOverflow {
value: *v as f64,
source_ty: std::any::type_name::<$cast>(),
target_ty: std::any::type_name::<$target_type>(),
})
})?
Ok(v.clone() $(as $cast as $target_type)?)
} else {
Err(ExecutionError::UnexpectedType {
got: format!("{:?}", expr),
Expand All @@ -20,7 +33,22 @@ macro_rules! impl_conversions {
fn from_value(expr: &Value) -> Result<Self, ExecutionError> {
match expr {
Value::Null => Ok(None),
$value_variant(v) => Ok(Some(v.clone())),
$value_variant(v) => {
$(if <$target_type>::MAX as $cast < *v {
return Err(ExecutionError::CastOverflow {
value: *v as f64,
source_ty: std::any::type_name::<$cast>(),
target_ty: std::any::type_name::<$target_type>(),
})
} else if <$target_type>::MIN as $cast > *v {
return Err(ExecutionError::CastOverflow {
value: *v as f64,
source_ty: std::any::type_name::<$cast>(),
target_ty: std::any::type_name::<$target_type>(),
})
})?
Ok(Some(v.clone() $(as $cast as $target_type)?))
},
_ => Err(ExecutionError::UnexpectedType {
got: format!("{:?}", expr),
want: stringify!($target_type).to_string(),
Expand All @@ -31,19 +59,19 @@ macro_rules! impl_conversions {

impl From<$target_type> for Value {
fn from(value: $target_type) -> Self {
$value_variant(value)
$value_variant(value $(as $cast)?)
}
}

impl $crate::magic::IntoResolveResult for $target_type {
fn into_resolve_result(self) -> ResolveResult {
Ok($value_variant(self))
Ok($value_variant(self $(as $cast)?))
}
}

impl $crate::magic::IntoResolveResult for Result<$target_type, ExecutionError> {
fn into_resolve_result(self) -> ResolveResult {
self.map($value_variant)
self.map(|it| $value_variant(it $(as $cast)?))
}
}

Expand Down
13 changes: 7 additions & 6 deletions interpreter/src/magic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ use std::marker::PhantomData;
use std::sync::Arc;

impl_conversions!(
i8 as i64 => Value::Int,
i16 as i64 => Value::Int,
i32 as i64 => Value::Int,
i64 => Value::Int,
u8 as u64 => Value::UInt,
u16 as u64 => Value::UInt,
u32 as u64 => Value::UInt,
u64 => Value::UInt,
f32 as f64 => Value::Float,
f64 => Value::Float,
Arc<String> => Value::String,
Arc<Vec<u8>> => Value::Bytes,
Expand All @@ -22,12 +29,6 @@ impl_conversions!(
chrono::DateTime<chrono::FixedOffset> => Value::Timestamp,
);

impl From<i32> for Value {
fn from(value: i32) -> Self {
Value::Int(value as i64)
}
}

/// Describes any type that can be converted from a [`Value`] into itself.
/// This is commonly used to convert from [`Value`] into primitive types,
/// e.g. from `Value::Bool(true) -> true`. This trait is auto-implemented
Expand Down
Loading

0 comments on commit 1bbe14e

Please sign in to comment.