From 1328234cec05d5718361f26ea742359f55b9b531 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sun, 12 May 2024 21:11:42 -0700 Subject: [PATCH 1/3] Allow string coerce or array and object --- .github/workflows/rust.yml | 6 +++--- Cargo.toml | 14 +++++++------- src/parser.rs | 4 +--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 895cf17..b47f377 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,7 +16,7 @@ jobs: platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo test --all-features --all-targets @@ -26,7 +26,7 @@ jobs: platform: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo check --all-features --all-targets @@ -34,6 +34,6 @@ jobs: name: Clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@clippy - run: cargo clippy --all-features --tests -- -Dclippy::all -Dclippy::pedantic diff --git a/Cargo.toml b/Cargo.toml index a8a4ff5..0f9c8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,14 +16,14 @@ keywords = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anydate = "0.3.0" -anyhow = "1.0.75" -chrono = { version = "0.4.31", features = ["serde"] } -clap = { version = "4.4.7", features = ["derive"] } +anydate = "0.4.0" +anyhow = "1.0.83" +chrono = { version = "0.4.38", features = ["serde"] } +clap = { version = "4.5.4", features = ["derive"] } gjson = "0.8.1" -serde = { version = "1.0.192", features = ["derive"] } -serde_json = "1.0.108" -thiserror = "1.0.50" +serde = { version = "1.0.201", features = ["derive"] } +serde_json = "1.0.117" +thiserror = "1.0.60" [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } diff --git a/src/parser.rs b/src/parser.rs index fe2c5a4..615a3b6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -776,9 +776,7 @@ impl Expression for COERCEString { Value::DateTime(dt) => Ok(Value::String( dt.to_rfc3339_opts(SecondsFormat::AutoSi, true), )), - _ => Err(Error::UnsupportedCOERCE( - format!("{value} COERCE datetime",), - )), + Value::Array(_)|Value::Object(_) => {Ok(Value::String(value.to_string()))} } } } From 0d6124a144db34fad9ddf69654e311ae52343b08 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Mon, 13 May 2024 21:54:20 -0700 Subject: [PATCH 2/3] refactor for maint --- src/parser/coercions/constant.rs | 12 + src/parser/coercions/date_time.rs | 24 + src/parser/coercions/lowercase.rs | 17 + src/parser/coercions/mod.rs | 17 + src/parser/coercions/number.rs | 28 + src/parser/coercions/string.rs | 24 + src/parser/coercions/sub_str.rs | 33 ++ src/parser/coercions/title.rs | 25 + src/parser/coercions/uppercase.rs | 17 + src/parser/expressions/add.rs | 25 + src/parser/expressions/and.rs | 27 + src/parser/expressions/arr.rs | 17 + src/parser/expressions/between.rs | 35 ++ src/parser/expressions/bool.rs | 12 + src/parser/expressions/contains.rs | 22 + src/parser/expressions/contains_all.rs | 35 ++ src/parser/expressions/contains_any.rs | 36 ++ src/parser/expressions/div.rs | 20 + src/parser/expressions/ends_with.rs | 20 + src/parser/expressions/eq.rs | 16 + src/parser/expressions/gt.rs | 22 + src/parser/expressions/gte.rs | 22 + src/parser/expressions/in.rs | 20 + src/parser/expressions/lt.rs | 22 + src/parser/expressions/lte.rs | 22 + src/parser/expressions/mod.rs | 51 ++ src/parser/expressions/muti.rs | 20 + src/parser/expressions/not.rs | 17 + src/parser/expressions/null.rs | 10 + src/parser/expressions/num.rs | 12 + src/parser/expressions/or.rs | 27 + src/parser/expressions/selector_path.rs | 12 + src/parser/expressions/starts_with.rs | 20 + src/parser/expressions/str.rs | 12 + src/parser/expressions/sub.rs | 20 + src/parser/mod.rs | 5 + src/{parser.rs => parser/parse.rs} | 654 +----------------------- 37 files changed, 786 insertions(+), 644 deletions(-) create mode 100644 src/parser/coercions/constant.rs create mode 100644 src/parser/coercions/date_time.rs create mode 100644 src/parser/coercions/lowercase.rs create mode 100644 src/parser/coercions/mod.rs create mode 100644 src/parser/coercions/number.rs create mode 100644 src/parser/coercions/string.rs create mode 100644 src/parser/coercions/sub_str.rs create mode 100644 src/parser/coercions/title.rs create mode 100644 src/parser/coercions/uppercase.rs create mode 100644 src/parser/expressions/add.rs create mode 100644 src/parser/expressions/and.rs create mode 100644 src/parser/expressions/arr.rs create mode 100644 src/parser/expressions/between.rs create mode 100644 src/parser/expressions/bool.rs create mode 100644 src/parser/expressions/contains.rs create mode 100644 src/parser/expressions/contains_all.rs create mode 100644 src/parser/expressions/contains_any.rs create mode 100644 src/parser/expressions/div.rs create mode 100644 src/parser/expressions/ends_with.rs create mode 100644 src/parser/expressions/eq.rs create mode 100644 src/parser/expressions/gt.rs create mode 100644 src/parser/expressions/gte.rs create mode 100644 src/parser/expressions/in.rs create mode 100644 src/parser/expressions/lt.rs create mode 100644 src/parser/expressions/lte.rs create mode 100644 src/parser/expressions/mod.rs create mode 100644 src/parser/expressions/muti.rs create mode 100644 src/parser/expressions/not.rs create mode 100644 src/parser/expressions/null.rs create mode 100644 src/parser/expressions/num.rs create mode 100644 src/parser/expressions/or.rs create mode 100644 src/parser/expressions/selector_path.rs create mode 100644 src/parser/expressions/starts_with.rs create mode 100644 src/parser/expressions/str.rs create mode 100644 src/parser/expressions/sub.rs create mode 100644 src/parser/mod.rs rename src/{parser.rs => parser/parse.rs} (76%) diff --git a/src/parser/coercions/constant.rs b/src/parser/coercions/constant.rs new file mode 100644 index 0000000..5a92a7b --- /dev/null +++ b/src/parser/coercions/constant.rs @@ -0,0 +1,12 @@ +use crate::parser::{Expression, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct CoercedConst { + pub value: Value, +} + +impl Expression for CoercedConst { + fn calculate(&self, _json: &[u8]) -> crate::parser::parse::Result { + Ok(self.value.clone()) + } +} diff --git a/src/parser/coercions/date_time.rs b/src/parser/coercions/date_time.rs new file mode 100644 index 0000000..4fd222b --- /dev/null +++ b/src/parser/coercions/date_time.rs @@ -0,0 +1,24 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct COERCEDateTime { + pub value: BoxedExpression, +} + +impl Expression for COERCEDateTime { + fn calculate(&self, json: &[u8]) -> crate::parser::parse::Result { + let value = self.value.calculate(json)?; + + match value { + Value::String(ref s) => match anydate::parse_utc(s) { + Err(_) => Ok(Value::Null), + Ok(dt) => Ok(Value::DateTime(dt)), + }, + Value::Null => Ok(value), + value => Err(Error::UnsupportedCOERCE( + format!("{value} COERCE datetime",), + )), + } + } +} diff --git a/src/parser/coercions/lowercase.rs b/src/parser/coercions/lowercase.rs new file mode 100644 index 0000000..db7deba --- /dev/null +++ b/src/parser/coercions/lowercase.rs @@ -0,0 +1,17 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct CoerceLowercase { + pub value: BoxedExpression, +} + +impl Expression for CoerceLowercase { + fn calculate(&self, json: &[u8]) -> crate::parser::parse::Result { + let v = self.value.calculate(json)?; + match v { + Value::String(s) => Ok(Value::String(s.to_lowercase())), + v => Err(Error::UnsupportedCOERCE(format!("{v} COERCE lowercase",))), + } + } +} diff --git a/src/parser/coercions/mod.rs b/src/parser/coercions/mod.rs new file mode 100644 index 0000000..d9491db --- /dev/null +++ b/src/parser/coercions/mod.rs @@ -0,0 +1,17 @@ +mod constant; +mod date_time; +mod lowercase; +mod number; +mod string; +mod sub_str; +mod title; +mod uppercase; + +pub(super) use constant::CoercedConst; +pub(super) use date_time::COERCEDateTime; +pub(super) use lowercase::CoerceLowercase; +pub(super) use number::COERCENumber; +pub(super) use string::COERCEString; +pub(super) use sub_str::CoerceSubstr; +pub(super) use title::CoerceTitle; +pub(super) use uppercase::CoerceUppercase; diff --git a/src/parser/coercions/number.rs b/src/parser/coercions/number.rs new file mode 100644 index 0000000..7e9247d --- /dev/null +++ b/src/parser/coercions/number.rs @@ -0,0 +1,28 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct COERCENumber { + pub value: BoxedExpression, +} + +impl Expression for COERCENumber { + #[allow(clippy::cast_precision_loss)] + fn calculate(&self, json: &[u8]) -> crate::parser::parse::Result { + let value = self.value.calculate(json)?; + match value { + Value::String(s) => Ok(Value::Number( + s.parse::() + .map_err(|e| Error::UnsupportedCOERCE(e.to_string()))?, + )), + Value::Number(num) => Ok(Value::Number(num)), + Value::Bool(b) => Ok(Value::Number(if b { 1.0 } else { 0.0 })), + Value::DateTime(dt) => Ok(Value::Number( + dt.timestamp_nanos_opt().unwrap_or_default() as f64 + )), + _ => Err(Error::UnsupportedCOERCE( + format!("{value} COERCE datetime",), + )), + } + } +} diff --git a/src/parser/coercions/string.rs b/src/parser/coercions/string.rs new file mode 100644 index 0000000..3dab0bc --- /dev/null +++ b/src/parser/coercions/string.rs @@ -0,0 +1,24 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Expression, Value}; +use chrono::SecondsFormat; + +#[derive(Debug)] +pub(in crate::parser) struct COERCEString { + pub value: BoxedExpression, +} + +impl Expression for COERCEString { + fn calculate(&self, json: &[u8]) -> crate::parser::parse::Result { + let value = self.value.calculate(json)?; + match value { + Value::Null => Ok(Value::String("null".to_string())), + Value::String(s) => Ok(Value::String(s)), + Value::Number(num) => Ok(Value::String(num.to_string())), + Value::Bool(b) => Ok(Value::String(b.to_string())), + Value::DateTime(dt) => Ok(Value::String( + dt.to_rfc3339_opts(SecondsFormat::AutoSi, true), + )), + Value::Array(_) | Value::Object(_) => Ok(Value::String(value.to_string())), + } + } +} diff --git a/src/parser/coercions/sub_str.rs b/src/parser/coercions/sub_str.rs new file mode 100644 index 0000000..05e70d0 --- /dev/null +++ b/src/parser/coercions/sub_str.rs @@ -0,0 +1,33 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct CoerceSubstr { + pub value: BoxedExpression, + pub start_idx: Option, + pub end_idx: Option, +} + +impl Expression for CoerceSubstr { + fn calculate(&self, json: &[u8]) -> crate::parser::parse::Result { + let v = self.value.calculate(json)?; + match v { + Value::String(s) => match (self.start_idx, self.end_idx) { + (Some(start), Some(end)) => Ok(s + .get(start..end) + .map_or_else(|| Value::Null, |s| Value::String(s.to_string()))), + (Some(start), None) => Ok(s + .get(start..) + .map_or_else(|| Value::Null, |s| Value::String(s.to_string()))), + (None, Some(end)) => Ok(s + .get(..end) + .map_or_else(|| Value::Null, |s| Value::String(s.to_string()))), + _ => Err(Error::UnsupportedCOERCE(format!( + "COERCE substr for {s}, [{:?}:{:?}]", + self.start_idx, self.end_idx + ))), + }, + v => Err(Error::UnsupportedCOERCE(format!("{v} COERCE substr",))), + } + } +} diff --git a/src/parser/coercions/title.rs b/src/parser/coercions/title.rs new file mode 100644 index 0000000..2d90f09 --- /dev/null +++ b/src/parser/coercions/title.rs @@ -0,0 +1,25 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct CoerceTitle { + pub value: BoxedExpression, +} + +impl Expression for CoerceTitle { + fn calculate(&self, json: &[u8]) -> crate::parser::parse::Result { + let v = self.value.calculate(json)?; + match v { + Value::String(s) => { + let mut c = s.chars(); + match c.next() { + None => Ok(Value::String(s)), + Some(f) => Ok(Value::String( + f.to_uppercase().collect::() + c.as_str().to_lowercase().as_str(), + )), + } + } + v => Err(Error::UnsupportedCOERCE(format!("{v} COERCE title",))), + } + } +} diff --git a/src/parser/coercions/uppercase.rs b/src/parser/coercions/uppercase.rs new file mode 100644 index 0000000..cf7f6f9 --- /dev/null +++ b/src/parser/coercions/uppercase.rs @@ -0,0 +1,17 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct CoerceUppercase { + pub value: BoxedExpression, +} + +impl Expression for CoerceUppercase { + fn calculate(&self, json: &[u8]) -> crate::parser::parse::Result { + let v = self.value.calculate(json)?; + match v { + Value::String(s) => Ok(Value::String(s.to_uppercase())), + v => Err(Error::UnsupportedCOERCE(format!("{v} COERCE uppercase",))), + } + } +} diff --git a/src/parser/expressions/add.rs b/src/parser/expressions/add.rs new file mode 100644 index 0000000..af0565f --- /dev/null +++ b/src/parser/expressions/add.rs @@ -0,0 +1,25 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Add { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Add { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::String(s1), Value::String(ref s2)) => Ok(Value::String(s1 + s2)), + (Value::String(s1), Value::Null) => Ok(Value::String(s1)), + (Value::Null, Value::String(s2)) => Ok(Value::String(s2)), + (Value::Number(n1), Value::Number(n2)) => Ok(Value::Number(n1 + n2)), + (Value::Number(n1), Value::Null) => Ok(Value::Number(n1)), + (Value::Null, Value::Number(n2)) => Ok(Value::Number(n2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} + {r}",))), + } + } +} diff --git a/src/parser/expressions/and.rs b/src/parser/expressions/and.rs new file mode 100644 index 0000000..63eb4f6 --- /dev/null +++ b/src/parser/expressions/and.rs @@ -0,0 +1,27 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct And { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for And { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + + if let Value::Bool(is_true) = left { + if !is_true { + return Ok(left); + } + } + + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::Bool(b1), Value::Bool(b2)) => Ok(Value::Bool(b1 && b2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} && {r}",))), + } + } +} diff --git a/src/parser/expressions/arr.rs b/src/parser/expressions/arr.rs new file mode 100644 index 0000000..6a84799 --- /dev/null +++ b/src/parser/expressions/arr.rs @@ -0,0 +1,17 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Arr { + pub arr: Vec, +} + +impl Expression for Arr { + fn calculate(&self, json: &[u8]) -> Result { + let mut arr = Vec::new(); + for e in &self.arr { + arr.push(e.calculate(json)?); + } + Ok(Value::Array(arr)) + } +} diff --git a/src/parser/expressions/between.rs b/src/parser/expressions/between.rs new file mode 100644 index 0000000..89f412e --- /dev/null +++ b/src/parser/expressions/between.rs @@ -0,0 +1,35 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Between { + pub left: BoxedExpression, + pub right: BoxedExpression, + pub value: BoxedExpression, +} + +impl Expression for Between { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + let value = self.value.calculate(json)?; + + match (value, left, right) { + (Value::String(v), Value::String(lhs), Value::String(rhs)) => { + Ok(Value::Bool(v > lhs && v < rhs)) + } + (Value::Number(v), Value::Number(lhs), Value::Number(rhs)) => { + Ok(Value::Bool(v > lhs && v < rhs)) + } + (Value::DateTime(v), Value::DateTime(lhs), Value::DateTime(rhs)) => { + Ok(Value::Bool(v > lhs && v < rhs)) + } + (Value::Null, _, _) | (_, Value::Null, _) | (_, _, Value::Null) => { + Ok(Value::Bool(false)) + } + (v, lhs, rhs) => Err(Error::UnsupportedTypeComparison(format!( + "{v} BETWEEN {lhs} {rhs}", + ))), + } + } +} diff --git a/src/parser/expressions/bool.rs b/src/parser/expressions/bool.rs new file mode 100644 index 0000000..ba3bfac --- /dev/null +++ b/src/parser/expressions/bool.rs @@ -0,0 +1,12 @@ +use crate::parser::{Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Bool { + pub b: bool, +} + +impl Expression for Bool { + fn calculate(&self, _: &[u8]) -> Result { + Ok(Value::Bool(self.b)) + } +} diff --git a/src/parser/expressions/contains.rs b/src/parser/expressions/contains.rs new file mode 100644 index 0000000..722db12 --- /dev/null +++ b/src/parser/expressions/contains.rs @@ -0,0 +1,22 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Contains { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Contains { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + match (left, right) { + (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1.contains(&s2))), + (Value::Array(arr1), v) => Ok(Value::Bool(arr1.contains(&v))), + (l, r) => Err(Error::UnsupportedTypeComparison(format!( + "{l} CONTAINS {r}", + ))), + } + } +} diff --git a/src/parser/expressions/contains_all.rs b/src/parser/expressions/contains_all.rs new file mode 100644 index 0000000..41141ab --- /dev/null +++ b/src/parser/expressions/contains_all.rs @@ -0,0 +1,35 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct ContainsAll { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for ContainsAll { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + match (left, right) { + (Value::String(s1), Value::String(s2)) => { + let b1: Vec = s1.chars().collect(); + Ok(Value::Bool(s2.chars().all(|b| b1.contains(&b)))) + } + (Value::Array(arr1), Value::Array(arr2)) => { + Ok(Value::Bool(arr2.iter().all(|v| arr1.contains(v)))) + } + (Value::Array(arr), Value::String(s)) => Ok(Value::Bool( + s.chars() + .all(|v| arr.contains(&Value::String(v.to_string()))), + )), + (Value::String(s), Value::Array(arr)) => Ok(Value::Bool(arr.iter().all(|v| match v { + Value::String(s2) => s.contains(s2), + _ => false, + }))), + (l, r) => Err(Error::UnsupportedTypeComparison(format!( + "{l} CONTAINS_ALL {r}", + ))), + } + } +} diff --git a/src/parser/expressions/contains_any.rs b/src/parser/expressions/contains_any.rs new file mode 100644 index 0000000..3b76828 --- /dev/null +++ b/src/parser/expressions/contains_any.rs @@ -0,0 +1,36 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct ContainsAny { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for ContainsAny { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + match (left, right) { + (Value::String(s1), Value::String(s2)) => { + let b1: Vec = s1.chars().collect(); + // betting that lists are short and so less expensive than iterating one to create a hash set + Ok(Value::Bool(s2.chars().any(|b| b1.contains(&b)))) + } + (Value::Array(arr1), Value::Array(arr2)) => { + Ok(Value::Bool(arr2.iter().any(|v| arr1.contains(v)))) + } + (Value::Array(arr), Value::String(s)) => Ok(Value::Bool( + s.chars() + .any(|v| arr.contains(&Value::String(v.to_string()))), + )), + (Value::String(s), Value::Array(arr)) => Ok(Value::Bool(arr.iter().any(|v| match v { + Value::String(s2) => s.contains(s2), + _ => false, + }))), + (l, r) => Err(Error::UnsupportedTypeComparison(format!( + "{l} CONTAINS_ANY {r}", + ))), + } + } +} diff --git a/src/parser/expressions/div.rs b/src/parser/expressions/div.rs new file mode 100644 index 0000000..1516280 --- /dev/null +++ b/src/parser/expressions/div.rs @@ -0,0 +1,20 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Div { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Div { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::Number(n1), Value::Number(n2)) => Ok(Value::Number(n1 / n2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} / {r}",))), + } + } +} diff --git a/src/parser/expressions/ends_with.rs b/src/parser/expressions/ends_with.rs new file mode 100644 index 0000000..3fce26c --- /dev/null +++ b/src/parser/expressions/ends_with.rs @@ -0,0 +1,20 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct EndsWith { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for EndsWith { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1.ends_with(&s2))), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} + {r}",))), + } + } +} diff --git a/src/parser/expressions/eq.rs b/src/parser/expressions/eq.rs new file mode 100644 index 0000000..7c58c6e --- /dev/null +++ b/src/parser/expressions/eq.rs @@ -0,0 +1,16 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Eq { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Eq { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + Ok(Value::Bool(left == right)) + } +} diff --git a/src/parser/expressions/gt.rs b/src/parser/expressions/gt.rs new file mode 100644 index 0000000..5041f6f --- /dev/null +++ b/src/parser/expressions/gt.rs @@ -0,0 +1,22 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Gt { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Gt { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1 > s2)), + (Value::Number(n1), Value::Number(n2)) => Ok(Value::Bool(n1 > n2)), + (Value::DateTime(dt1), Value::DateTime(dt2)) => Ok(Value::Bool(dt1 > dt2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} > {r}",))), + } + } +} diff --git a/src/parser/expressions/gte.rs b/src/parser/expressions/gte.rs new file mode 100644 index 0000000..19b2303 --- /dev/null +++ b/src/parser/expressions/gte.rs @@ -0,0 +1,22 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Gte { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Gte { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1 >= s2)), + (Value::Number(n1), Value::Number(n2)) => Ok(Value::Bool(n1 >= n2)), + (Value::DateTime(dt1), Value::DateTime(dt2)) => Ok(Value::Bool(dt1 >= dt2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} >= {r}",))), + } + } +} diff --git a/src/parser/expressions/in.rs b/src/parser/expressions/in.rs new file mode 100644 index 0000000..be35672 --- /dev/null +++ b/src/parser/expressions/in.rs @@ -0,0 +1,20 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct In { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for In { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (v, Value::Array(a)) => Ok(Value::Bool(a.contains(&v))), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} + {r}",))), + } + } +} diff --git a/src/parser/expressions/lt.rs b/src/parser/expressions/lt.rs new file mode 100644 index 0000000..3dbb404 --- /dev/null +++ b/src/parser/expressions/lt.rs @@ -0,0 +1,22 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Lt { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Lt { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1 < s2)), + (Value::Number(n1), Value::Number(n2)) => Ok(Value::Bool(n1 < n2)), + (Value::DateTime(dt1), Value::DateTime(dt2)) => Ok(Value::Bool(dt1 < dt2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} < {r}",))), + } + } +} diff --git a/src/parser/expressions/lte.rs b/src/parser/expressions/lte.rs new file mode 100644 index 0000000..879fdfc --- /dev/null +++ b/src/parser/expressions/lte.rs @@ -0,0 +1,22 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Lte { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Lte { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1 <= s2)), + (Value::Number(n1), Value::Number(n2)) => Ok(Value::Bool(n1 <= n2)), + (Value::DateTime(dt1), Value::DateTime(dt2)) => Ok(Value::Bool(dt1 <= dt2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} <= {r}",))), + } + } +} diff --git a/src/parser/expressions/mod.rs b/src/parser/expressions/mod.rs new file mode 100644 index 0000000..be8d541 --- /dev/null +++ b/src/parser/expressions/mod.rs @@ -0,0 +1,51 @@ +mod add; +mod and; +mod arr; +mod between; +mod bool; +mod contains; +mod contains_all; +mod contains_any; +mod div; +mod ends_with; +mod eq; +mod gt; +mod gte; +mod r#in; +mod lt; +mod lte; +mod muti; +mod not; +mod null; +mod num; +mod or; +mod selector_path; +mod starts_with; +mod str; +mod sub; + +pub(super) use add::Add; +pub(super) use and::And; +pub(super) use arr::Arr; +pub(super) use between::Between; +pub(super) use bool::Bool; +pub(super) use contains::Contains; +pub(super) use contains_all::ContainsAll; +pub(super) use contains_any::ContainsAny; +pub(super) use div::Div; +pub(super) use ends_with::EndsWith; +pub(super) use eq::Eq; +pub(super) use gt::Gt; +pub(super) use gte::Gte; +pub(super) use lt::Lt; +pub(super) use lte::Lte; +pub(super) use muti::Mult; +pub(super) use not::Not; +pub(super) use null::Null; +pub(super) use num::Num; +pub(super) use or::Or; +pub(super) use r#in::In; +pub(super) use selector_path::SelectorPath; +pub(super) use starts_with::StartsWith; +pub(super) use str::Str; +pub(super) use sub::Sub; diff --git a/src/parser/expressions/muti.rs b/src/parser/expressions/muti.rs new file mode 100644 index 0000000..1f3d898 --- /dev/null +++ b/src/parser/expressions/muti.rs @@ -0,0 +1,20 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Mult { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Mult { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::Number(n1), Value::Number(n2)) => Ok(Value::Number(n1 * n2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} * {r}",))), + } + } +} diff --git a/src/parser/expressions/not.rs b/src/parser/expressions/not.rs new file mode 100644 index 0000000..7696c3a --- /dev/null +++ b/src/parser/expressions/not.rs @@ -0,0 +1,17 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Not { + pub value: BoxedExpression, +} + +impl Expression for Not { + fn calculate(&self, json: &[u8]) -> Result { + let v = self.value.calculate(json)?; + match v { + Value::Bool(b) => Ok(Value::Bool(!b)), + v => Err(Error::UnsupportedTypeComparison(format!("{v:?} for !"))), + } + } +} diff --git a/src/parser/expressions/null.rs b/src/parser/expressions/null.rs new file mode 100644 index 0000000..d30093b --- /dev/null +++ b/src/parser/expressions/null.rs @@ -0,0 +1,10 @@ +use crate::parser::{Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Null; + +impl Expression for Null { + fn calculate(&self, _: &[u8]) -> Result { + Ok(Value::Null) + } +} diff --git a/src/parser/expressions/num.rs b/src/parser/expressions/num.rs new file mode 100644 index 0000000..5ea1af9 --- /dev/null +++ b/src/parser/expressions/num.rs @@ -0,0 +1,12 @@ +use crate::parser::{Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Num { + pub n: f64, +} + +impl Expression for Num { + fn calculate(&self, _: &[u8]) -> Result { + Ok(Value::Number(self.n)) + } +} diff --git a/src/parser/expressions/or.rs b/src/parser/expressions/or.rs new file mode 100644 index 0000000..1c8e246 --- /dev/null +++ b/src/parser/expressions/or.rs @@ -0,0 +1,27 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Or { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Or { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + + if let Value::Bool(is_true) = left { + if is_true { + return Ok(left); + } + } + + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::Bool(b1), Value::Bool(b2)) => Ok(Value::Bool(b1 || b2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} || {r}",))), + } + } +} diff --git a/src/parser/expressions/selector_path.rs b/src/parser/expressions/selector_path.rs new file mode 100644 index 0000000..3efe965 --- /dev/null +++ b/src/parser/expressions/selector_path.rs @@ -0,0 +1,12 @@ +use crate::parser::{Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct SelectorPath { + pub ident: String, +} + +impl Expression for SelectorPath { + fn calculate(&self, json: &[u8]) -> Result { + Ok(unsafe { gjson::get_bytes(json, &self.ident).into() }) + } +} diff --git a/src/parser/expressions/starts_with.rs b/src/parser/expressions/starts_with.rs new file mode 100644 index 0000000..8a78d83 --- /dev/null +++ b/src/parser/expressions/starts_with.rs @@ -0,0 +1,20 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct StartsWith { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for StartsWith { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1.starts_with(&s2))), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} + {r}",))), + } + } +} diff --git a/src/parser/expressions/str.rs b/src/parser/expressions/str.rs new file mode 100644 index 0000000..dbf7008 --- /dev/null +++ b/src/parser/expressions/str.rs @@ -0,0 +1,12 @@ +use crate::parser::{Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Str { + pub s: String, +} + +impl Expression for Str { + fn calculate(&self, _: &[u8]) -> Result { + Ok(Value::String(self.s.clone())) + } +} diff --git a/src/parser/expressions/sub.rs b/src/parser/expressions/sub.rs new file mode 100644 index 0000000..7cf3720 --- /dev/null +++ b/src/parser/expressions/sub.rs @@ -0,0 +1,20 @@ +use crate::parser::parse::BoxedExpression; +use crate::parser::{Error, Expression, Result, Value}; + +#[derive(Debug)] +pub(in crate::parser) struct Sub { + pub left: BoxedExpression, + pub right: BoxedExpression, +} + +impl Expression for Sub { + fn calculate(&self, json: &[u8]) -> Result { + let left = self.left.calculate(json)?; + let right = self.right.calculate(json)?; + + match (left, right) { + (Value::Number(n1), Value::Number(n2)) => Ok(Value::Number(n1 - n2)), + (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} - {r}",))), + } + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..2b9538a --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,5 @@ +mod coercions; +mod expressions; +mod parse; + +pub use parse::{Error, Expression, Parser, Result, Value}; diff --git a/src/parser.rs b/src/parser/parse.rs similarity index 76% rename from src/parser.rs rename to src/parser/parse.rs index 615a3b6..26591aa 100644 --- a/src/parser.rs +++ b/src/parser/parse.rs @@ -15,8 +15,16 @@ //! use crate::lexer::{Token, TokenKind, Tokenizer}; +use crate::parser::coercions::{ + COERCEDateTime, COERCENumber, COERCEString, CoerceLowercase, CoerceSubstr, CoerceTitle, + CoerceUppercase, CoercedConst, +}; +use crate::parser::expressions::{ + Add, And, Arr, Between, Bool, Contains, ContainsAll, ContainsAny, Div, EndsWith, Eq, Gt, Gte, + In, Lt, Lte, Mult, Not, Null, Num, Or, SelectorPath, StartsWith, Str, Sub, +}; use anyhow::anyhow; -use chrono::{DateTime, SecondsFormat, Utc}; +use chrono::{DateTime, Utc}; use gjson::Kind; use serde::Serialize; use std::collections::{BTreeMap, HashMap}; @@ -334,7 +342,7 @@ pub trait Expression: Debug + Send + Sync { } /// Is an alias for a Box -type BoxedExpression = Box; +pub(in crate::parser) type BoxedExpression = Box; /// Parses a supplied expression and returns a `BoxedExpression`. pub struct Parser<'a> { @@ -701,648 +709,6 @@ impl<'a> Parser<'a> { } } -#[derive(Debug)] -struct Between { - left: BoxedExpression, - right: BoxedExpression, - value: BoxedExpression, -} - -impl Expression for Between { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - let value = self.value.calculate(json)?; - - match (value, left, right) { - (Value::String(v), Value::String(lhs), Value::String(rhs)) => { - Ok(Value::Bool(v > lhs && v < rhs)) - } - (Value::Number(v), Value::Number(lhs), Value::Number(rhs)) => { - Ok(Value::Bool(v > lhs && v < rhs)) - } - (Value::DateTime(v), Value::DateTime(lhs), Value::DateTime(rhs)) => { - Ok(Value::Bool(v > lhs && v < rhs)) - } - (Value::Null, _, _) | (_, Value::Null, _) | (_, _, Value::Null) => { - Ok(Value::Bool(false)) - } - (v, lhs, rhs) => Err(Error::UnsupportedTypeComparison(format!( - "{v} BETWEEN {lhs} {rhs}", - ))), - } - } -} - -#[derive(Debug)] -struct COERCENumber { - value: BoxedExpression, -} - -impl Expression for COERCENumber { - #[allow(clippy::cast_precision_loss)] - fn calculate(&self, json: &[u8]) -> Result { - let value = self.value.calculate(json)?; - match value { - Value::String(s) => Ok(Value::Number( - s.parse::() - .map_err(|e| Error::UnsupportedCOERCE(e.to_string()))?, - )), - Value::Number(num) => Ok(Value::Number(num)), - Value::Bool(b) => Ok(Value::Number(if b { 1.0 } else { 0.0 })), - Value::DateTime(dt) => Ok(Value::Number( - dt.timestamp_nanos_opt().unwrap_or_default() as f64 - )), - _ => Err(Error::UnsupportedCOERCE( - format!("{value} COERCE datetime",), - )), - } - } -} - -#[derive(Debug)] -struct COERCEString { - value: BoxedExpression, -} - -impl Expression for COERCEString { - fn calculate(&self, json: &[u8]) -> Result { - let value = self.value.calculate(json)?; - match value { - Value::Null => Ok(Value::String("null".to_string())), - Value::String(s) => Ok(Value::String(s)), - Value::Number(num) => Ok(Value::String(num.to_string())), - Value::Bool(b) => Ok(Value::String(b.to_string())), - Value::DateTime(dt) => Ok(Value::String( - dt.to_rfc3339_opts(SecondsFormat::AutoSi, true), - )), - Value::Array(_)|Value::Object(_) => {Ok(Value::String(value.to_string()))} - } - } -} - -#[derive(Debug)] -struct COERCEDateTime { - value: BoxedExpression, -} - -impl Expression for COERCEDateTime { - fn calculate(&self, json: &[u8]) -> Result { - let value = self.value.calculate(json)?; - - match value { - Value::String(ref s) => match anydate::parse_utc(s) { - Err(_) => Ok(Value::Null), - Ok(dt) => Ok(Value::DateTime(dt)), - }, - Value::Null => Ok(value), - value => Err(Error::UnsupportedCOERCE( - format!("{value} COERCE datetime",), - )), - } - } -} - -#[derive(Debug)] -struct Add { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Add { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::String(s1), Value::String(ref s2)) => Ok(Value::String(s1 + s2)), - (Value::String(s1), Value::Null) => Ok(Value::String(s1)), - (Value::Null, Value::String(s2)) => Ok(Value::String(s2)), - (Value::Number(n1), Value::Number(n2)) => Ok(Value::Number(n1 + n2)), - (Value::Number(n1), Value::Null) => Ok(Value::Number(n1)), - (Value::Null, Value::Number(n2)) => Ok(Value::Number(n2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} + {r}",))), - } - } -} - -#[derive(Debug)] -struct Sub { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Sub { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::Number(n1), Value::Number(n2)) => Ok(Value::Number(n1 - n2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} - {r}",))), - } - } -} - -#[derive(Debug)] -struct Mult { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Mult { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::Number(n1), Value::Number(n2)) => Ok(Value::Number(n1 * n2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} * {r}",))), - } - } -} - -#[derive(Debug)] -struct Div { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Div { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::Number(n1), Value::Number(n2)) => Ok(Value::Number(n1 / n2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} / {r}",))), - } - } -} - -#[derive(Debug)] -struct Eq { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Eq { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - Ok(Value::Bool(left == right)) - } -} - -#[derive(Debug)] -struct Gt { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Gt { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1 > s2)), - (Value::Number(n1), Value::Number(n2)) => Ok(Value::Bool(n1 > n2)), - (Value::DateTime(dt1), Value::DateTime(dt2)) => Ok(Value::Bool(dt1 > dt2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} > {r}",))), - } - } -} - -#[derive(Debug)] -struct Gte { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Gte { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1 >= s2)), - (Value::Number(n1), Value::Number(n2)) => Ok(Value::Bool(n1 >= n2)), - (Value::DateTime(dt1), Value::DateTime(dt2)) => Ok(Value::Bool(dt1 >= dt2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} >= {r}",))), - } - } -} - -#[derive(Debug)] -struct Lt { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Lt { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1 < s2)), - (Value::Number(n1), Value::Number(n2)) => Ok(Value::Bool(n1 < n2)), - (Value::DateTime(dt1), Value::DateTime(dt2)) => Ok(Value::Bool(dt1 < dt2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} < {r}",))), - } - } -} - -#[derive(Debug)] -struct Lte { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Lte { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1 <= s2)), - (Value::Number(n1), Value::Number(n2)) => Ok(Value::Bool(n1 <= n2)), - (Value::DateTime(dt1), Value::DateTime(dt2)) => Ok(Value::Bool(dt1 <= dt2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} <= {r}",))), - } - } -} - -#[derive(Debug)] -struct CoercedConst { - value: Value, -} - -impl Expression for CoercedConst { - fn calculate(&self, _json: &[u8]) -> Result { - Ok(self.value.clone()) - } -} - -#[derive(Debug)] -struct CoerceLowercase { - value: BoxedExpression, -} - -impl Expression for CoerceLowercase { - fn calculate(&self, json: &[u8]) -> Result { - let v = self.value.calculate(json)?; - match v { - Value::String(s) => Ok(Value::String(s.to_lowercase())), - v => Err(Error::UnsupportedCOERCE(format!("{v} COERCE lowercase",))), - } - } -} - -#[derive(Debug)] -struct CoerceUppercase { - value: BoxedExpression, -} - -impl Expression for CoerceUppercase { - fn calculate(&self, json: &[u8]) -> Result { - let v = self.value.calculate(json)?; - match v { - Value::String(s) => Ok(Value::String(s.to_uppercase())), - v => Err(Error::UnsupportedCOERCE(format!("{v} COERCE uppercase",))), - } - } -} - -#[derive(Debug)] -struct CoerceTitle { - value: BoxedExpression, -} - -impl Expression for CoerceTitle { - fn calculate(&self, json: &[u8]) -> Result { - let v = self.value.calculate(json)?; - match v { - Value::String(s) => { - let mut c = s.chars(); - match c.next() { - None => Ok(Value::String(s)), - Some(f) => Ok(Value::String( - f.to_uppercase().collect::() + c.as_str().to_lowercase().as_str(), - )), - } - } - v => Err(Error::UnsupportedCOERCE(format!("{v} COERCE title",))), - } - } -} - -#[derive(Debug)] -struct CoerceSubstr { - value: BoxedExpression, - start_idx: Option, - end_idx: Option, -} - -impl Expression for CoerceSubstr { - fn calculate(&self, json: &[u8]) -> Result { - let v = self.value.calculate(json)?; - match v { - Value::String(s) => match (self.start_idx, self.end_idx) { - (Some(start), Some(end)) => Ok(s - .get(start..end) - .map_or_else(|| Value::Null, |s| Value::String(s.to_string()))), - (Some(start), None) => Ok(s - .get(start..) - .map_or_else(|| Value::Null, |s| Value::String(s.to_string()))), - (None, Some(end)) => Ok(s - .get(..end) - .map_or_else(|| Value::Null, |s| Value::String(s.to_string()))), - _ => Err(Error::UnsupportedCOERCE(format!( - "COERCE substr for {s}, [{:?}:{:?}]", - self.start_idx, self.end_idx - ))), - }, - v => Err(Error::UnsupportedCOERCE(format!("{v} COERCE substr",))), - } - } -} - -#[derive(Debug)] -struct Not { - value: BoxedExpression, -} - -impl Expression for Not { - fn calculate(&self, json: &[u8]) -> Result { - let v = self.value.calculate(json)?; - match v { - Value::Bool(b) => Ok(Value::Bool(!b)), - v => Err(Error::UnsupportedTypeComparison(format!("{v:?} for !"))), - } - } -} - -#[derive(Debug)] -struct SelectorPath { - ident: String, -} - -impl Expression for SelectorPath { - fn calculate(&self, json: &[u8]) -> Result { - Ok(unsafe { gjson::get_bytes(json, &self.ident).into() }) - } -} - -#[derive(Debug)] -struct Str { - s: String, -} - -impl Expression for Str { - fn calculate(&self, _: &[u8]) -> Result { - Ok(Value::String(self.s.clone())) - } -} - -#[derive(Debug)] -struct Num { - n: f64, -} - -impl Expression for Num { - fn calculate(&self, _: &[u8]) -> Result { - Ok(Value::Number(self.n)) - } -} - -#[derive(Debug)] -struct Bool { - b: bool, -} - -impl Expression for Bool { - fn calculate(&self, _: &[u8]) -> Result { - Ok(Value::Bool(self.b)) - } -} - -#[derive(Debug)] -struct Null; - -impl Expression for Null { - fn calculate(&self, _: &[u8]) -> Result { - Ok(Value::Null) - } -} - -#[derive(Debug)] -struct Or { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Or { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - - if let Value::Bool(is_true) = left { - if is_true { - return Ok(left); - } - } - - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::Bool(b1), Value::Bool(b2)) => Ok(Value::Bool(b1 || b2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} || {r}",))), - } - } -} - -#[derive(Debug)] -struct And { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for And { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - - if let Value::Bool(is_true) = left { - if !is_true { - return Ok(left); - } - } - - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::Bool(b1), Value::Bool(b2)) => Ok(Value::Bool(b1 && b2)), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} && {r}",))), - } - } -} - -#[derive(Debug)] -struct Contains { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for Contains { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - match (left, right) { - (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1.contains(&s2))), - (Value::Array(arr1), v) => Ok(Value::Bool(arr1.contains(&v))), - (l, r) => Err(Error::UnsupportedTypeComparison(format!( - "{l} CONTAINS {r}", - ))), - } - } -} - -#[derive(Debug)] -struct ContainsAny { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for ContainsAny { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - match (left, right) { - (Value::String(s1), Value::String(s2)) => { - let b1: Vec = s1.chars().collect(); - // betting that lists are short and so less expensive than iterating one to create a hash set - Ok(Value::Bool(s2.chars().any(|b| b1.contains(&b)))) - } - (Value::Array(arr1), Value::Array(arr2)) => { - Ok(Value::Bool(arr2.iter().any(|v| arr1.contains(v)))) - } - (Value::Array(arr), Value::String(s)) => Ok(Value::Bool( - s.chars() - .any(|v| arr.contains(&Value::String(v.to_string()))), - )), - (Value::String(s), Value::Array(arr)) => Ok(Value::Bool(arr.iter().any(|v| match v { - Value::String(s2) => s.contains(s2), - _ => false, - }))), - (l, r) => Err(Error::UnsupportedTypeComparison(format!( - "{l} CONTAINS_ANY {r}", - ))), - } - } -} - -#[derive(Debug)] -struct ContainsAll { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for ContainsAll { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - match (left, right) { - (Value::String(s1), Value::String(s2)) => { - let b1: Vec = s1.chars().collect(); - Ok(Value::Bool(s2.chars().all(|b| b1.contains(&b)))) - } - (Value::Array(arr1), Value::Array(arr2)) => { - Ok(Value::Bool(arr2.iter().all(|v| arr1.contains(v)))) - } - (Value::Array(arr), Value::String(s)) => Ok(Value::Bool( - s.chars() - .all(|v| arr.contains(&Value::String(v.to_string()))), - )), - (Value::String(s), Value::Array(arr)) => Ok(Value::Bool(arr.iter().all(|v| match v { - Value::String(s2) => s.contains(s2), - _ => false, - }))), - (l, r) => Err(Error::UnsupportedTypeComparison(format!( - "{l} CONTAINS_ALL {r}", - ))), - } - } -} - -#[derive(Debug)] -struct StartsWith { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for StartsWith { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1.starts_with(&s2))), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} + {r}",))), - } - } -} - -#[derive(Debug)] -struct EndsWith { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for EndsWith { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (Value::String(s1), Value::String(s2)) => Ok(Value::Bool(s1.ends_with(&s2))), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} + {r}",))), - } - } -} - -#[derive(Debug)] -struct In { - left: BoxedExpression, - right: BoxedExpression, -} - -impl Expression for In { - fn calculate(&self, json: &[u8]) -> Result { - let left = self.left.calculate(json)?; - let right = self.right.calculate(json)?; - - match (left, right) { - (v, Value::Array(a)) => Ok(Value::Bool(a.contains(&v))), - (l, r) => Err(Error::UnsupportedTypeComparison(format!("{l} + {r}",))), - } - } -} - -#[derive(Debug)] -struct Arr { - arr: Vec, -} - -impl Expression for Arr { - fn calculate(&self, json: &[u8]) -> Result { - let mut arr = Vec::new(); - for e in &self.arr { - arr.push(e.calculate(json)?); - } - Ok(Value::Array(arr)) - } -} - /// Result type for the `parse` function. pub type Result = std::result::Result; From 3e414da7e1f9a3c805fb332cd4e002c776bf297b Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Mon, 13 May 2024 21:57:52 -0700 Subject: [PATCH 3/3] fix exporting coersions fn --- src/parser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2b9538a..12a356c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2,4 +2,4 @@ mod coercions; mod expressions; mod parse; -pub use parse::{Error, Expression, Parser, Result, Value}; +pub use parse::{coercions, Error, Expression, Parser, Result, Value};