Skip to content

Commit

Permalink
Support Timestamp type (#4)
Browse files Browse the repository at this point in the history
* Build caching

* Test caching

* Added support for timestamp types
  • Loading branch information
clarkmcc committed May 29, 2023
1 parent a928988 commit f83f50e
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 3 deletions.
3 changes: 2 additions & 1 deletion interpreter/benches/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
("max", "max(1, 2, 3)"),
("max negative", "max(-1, 0, 1)"),
("max float", "max(-1.0, 0.0, 1.0)"),
("duration", "duration('1s')"),
("duration", "duration('1s')"),
("timestamp", "timestamp('2023-05-28T00:00:00Z')")
// ("complex", "Account{user_id: 123}.user_id == 123"),
];
// https://gist.github.com/rhnvrm/db4567fcd87b2cb8e997999e1366d406
Expand Down
2 changes: 2 additions & 0 deletions interpreter/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ impl<'a> Default for Context<'a> {
ctx.add_function("all", functions::all);
ctx.add_function("max", functions::max);
ctx.add_function("duration", functions::duration);
ctx.add_function("timestamp", functions::timestamp);
ctx.add_function("startsWith", functions::starts_with);
ctx.add_function("string", functions::string);
ctx
}
}
94 changes: 93 additions & 1 deletion interpreter/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::duration::parse_duration;
use crate::objects::CelType;
use crate::ExecutionError;
use cel_parser::Expression;
use chrono::Duration;
use chrono::{DateTime, Duration, FixedOffset};
use std::convert::TryInto;
use std::mem;
use std::rc::Rc;
Expand Down Expand Up @@ -125,6 +125,34 @@ pub fn contains(
.into())
}

// Performs a type conversion on the target. The following conversions are currently
// supported:
// * `string` - Returns a copy of the target string.
// * `timestamp` - Returns the timestamp in RFC3339 format.
//
// todo: In order to be fully compatible with the CEL specification, this function should
// also support the following conversions:
// * `int`
// * `uint`
// * `double`
// * `bytes`
// * `duration`
pub fn string(
target: Option<&CelType>,
_: &[Expression],
_: &Context,
) -> Result<CelType, ExecutionError> {
let target = target.unwrap();
Ok(match target {
CelType::String(_) => target.clone(),
CelType::Timestamp(t) => CelType::String(t.to_rfc3339().into()),
_ => Err(ExecutionError::function_error(
"string",
&format!("cannot convert {:?} to string", target),
))?,
})
}

/// Returns true if a string starts with another string.
///
/// # Example
Expand Down Expand Up @@ -240,6 +268,25 @@ pub fn duration(
.into()
}

pub fn timestamp(
target: Option<&CelType>,
args: &[Expression],
ctx: &Context,
) -> Result<CelType, ExecutionError> {
if target.is_some() {
return Err(ExecutionError::not_supported_as_method(
"timestamp",
target.cloned().unwrap(),
));
}
let value = CelType::resolve(get_arg(0, args)?, ctx)?;
match value {
CelType::String(v) => CelType::Timestamp(_timestamp(v.as_str())?),
_ => return Err(ExecutionError::unsupported_target_type(value)),
}
.into()
}

/// Filters the provided list by applying an expression to each input item
/// and including the input item in the resulting list, only if the expression
/// returned true.
Expand Down Expand Up @@ -440,6 +487,11 @@ fn _duration(i: &str) -> Result<Duration, ExecutionError> {
Ok(duration)
}

fn _timestamp(i: &str) -> Result<DateTime<FixedOffset>, ExecutionError> {
DateTime::parse_from_rfc3339(i)
.map_err(|e| ExecutionError::function_error("timestamp", &e.to_string()))
}

fn is_numeric(target: &&CelType) -> bool {
matches!(
target,
Expand Down Expand Up @@ -537,6 +589,14 @@ mod tests {
("duration equal 3", "duration('1h') == duration('60m')"),
("duration comparison 1", "duration('1m') > duration('1s')"),
("duration comparison 2", "duration('1m') < duration('1h')"),
(
"duration subtraction",
"duration('1h') - duration('1m') == duration('59m')",
),
(
"duration addition",
"duration('1h') + duration('1m') == duration('1h1m')",
),
]
.iter()
.for_each(assert_script);
Expand All @@ -551,4 +611,36 @@ mod tests {
.iter()
.for_each(assert_script);
}

#[test]
fn test_timestamp() {
vec![
(
"comparison",
"timestamp('2023-05-29T00:00:00Z') > timestamp('2023-05-28T00:00:00Z')",
),
(
"comparison",
"timestamp('2023-05-29T00:00:00Z') < timestamp('2023-05-30T00:00:00Z')",
),
(
"subtracting duration",
"timestamp('2023-05-29T00:00:00Z') - duration('24h') == timestamp('2023-05-28T00:00:00Z')",
),
(
"subtracting date",
"timestamp('2023-05-29T00:00:00Z') - timestamp('2023-05-28T00:00:00Z') == duration('24h')",
),
(
"adding duration",
"timestamp('2023-05-28T00:00:00Z') + duration('24h') == timestamp('2023-05-29T00:00:00Z')",
),
(
"timestamp string",
"timestamp('2023-05-28T00:00:00Z').string() == '2023-05-28T00:00:00+00:00'",
),
]
.iter()
.for_each(assert_script);
}
}
22 changes: 21 additions & 1 deletion interpreter/src/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::context::Context;
use crate::ExecutionError;
use crate::ExecutionError::NoSuchKey;
use cel_parser::{ArithmeticOp, Atom, Expression, Member, RelationOp, UnaryOp};
use chrono::Duration;
use chrono::{DateTime, Duration, FixedOffset};
use core::ops;
use std::cmp::Ordering;
use std::collections::HashMap;
Expand Down Expand Up @@ -124,6 +124,7 @@ pub enum CelType {
Bytes(Rc<Vec<u8>>),
Bool(bool),
Duration(Duration),
Timestamp(DateTime<FixedOffset>),
Null,
}

Expand All @@ -145,6 +146,7 @@ impl Ord for CelType {
(CelType::Bool(a), CelType::Bool(b)) => a.cmp(b),
(CelType::Null, CelType::Null) => Ordering::Equal,
(CelType::Duration(a), CelType::Duration(b)) => a.cmp(b),
(CelType::Timestamp(a), CelType::Timestamp(b)) => a.cmp(b),
_ => unreachable!(),
}
}
Expand Down Expand Up @@ -429,6 +431,7 @@ impl<'a> CelType {
CelType::Bool(v) => *v,
CelType::Null => false,
CelType::Duration(v) => v.num_nanoseconds().map(|n| n != 0).unwrap_or(false),
CelType::Timestamp(v) => v.timestamp_nanos() > 0,
CelType::Function(_, _) => false,
}
}
Expand Down Expand Up @@ -485,6 +488,13 @@ impl ops::Add<CelType> for CelType {
}
CelType::Map(CelMap { map: Rc::new(new) })
}
(CelType::Duration(l), CelType::Duration(r)) => CelType::Duration(l + r),
(CelType::Timestamp(l), CelType::Duration(r)) => {
CelType::Timestamp(l + Duration::nanoseconds(r.num_nanoseconds().unwrap()))
}
(CelType::Duration(l), CelType::Timestamp(r)) => {
CelType::Timestamp(r + Duration::nanoseconds(l.num_nanoseconds().unwrap()))
}
_ => unimplemented!(),
}
}
Expand All @@ -505,6 +515,16 @@ impl ops::Sub<CelType> for CelType {
(CelType::Float(l), CelType::Int(r)) => CelType::Float(l - r as f64),
(CelType::UInt(l), CelType::Float(r)) => CelType::Float(l as f64 - r),
(CelType::Float(l), CelType::UInt(r)) => CelType::Float(l - r as f64),
// todo: implement checked sub for these over-flowable operations
(CelType::Duration(l), CelType::Duration(r)) => CelType::Duration(
Duration::nanoseconds(l.num_nanoseconds().unwrap() - r.num_nanoseconds().unwrap()),
),
(CelType::Timestamp(l), CelType::Duration(r)) => {
CelType::Timestamp(l - Duration::nanoseconds(r.num_nanoseconds().unwrap()))
}
(CelType::Timestamp(l), CelType::Timestamp(r)) => CelType::Duration(
Duration::nanoseconds(l.timestamp_nanos() - r.timestamp_nanos()),
),
_ => unimplemented!(),
}
}
Expand Down

0 comments on commit f83f50e

Please sign in to comment.