From 84faf1ff2273166c2d9113e2494c950c7a3ca7b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 03:33:03 +0000 Subject: [PATCH 1/6] Bump rand from 0.9.2 to 0.9.3 Bumps [rand](https://github.com/rust-random/rand) from 0.9.2 to 0.9.3. - [Release notes](https://github.com/rust-random/rand/releases) - [Changelog](https://github.com/rust-random/rand/blob/0.9.3/CHANGELOG.md) - [Commits](https://github.com/rust-random/rand/compare/rand_core-0.9.2...0.9.3) --- updated-dependencies: - dependency-name: rand dependency-version: 0.9.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f69bb03c..d0a2450a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -732,9 +732,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index 655019b7..2f4fffcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ js-sys = "0.3.85" libc = "0.2.182" memchr = "2.8.0" num_enum = "0.7.5" -rand = "0.9.2" +rand = "0.9.3" regex-automata = "0.4.14" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" From 21b3103d68a8eb566da8f1532bbe78d9326b394f Mon Sep 17 00:00:00 2001 From: Hrushikesh Deshpande Date: Thu, 23 Apr 2026 18:30:55 -0400 Subject: [PATCH 2/6] ci: add Semgrep OSS scanning workflow --- .github/workflows/semgrep.yml | 40 ++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 4090692f..62b7ccdd 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -1,24 +1,30 @@ +name: Semgrep OSS scan on: pull_request: {} + push: + branches: [main, master] workflow_dispatch: {} - push: - branches: - - main - - master schedule: - - cron: '0 0 * * *' -name: Semgrep config + - cron: '0 0 20 * *' +concurrency: + group: semgrep-${{ github.event_name }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +permissions: + contents: read jobs: semgrep: - name: semgrep/ci - runs-on: ubuntu-latest - env: - SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} - SEMGREP_URL: https://cloudflare.semgrep.dev - SEMGREP_APP_URL: https://cloudflare.semgrep.dev - SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version - container: - image: semgrep/semgrep + name: semgrep-oss + runs-on: ubuntu-slim steps: - - uses: actions/checkout@v4 - - run: semgrep ci + - uses: actions/checkout@v5 + with: + fetch-depth: 1 + - id: cache-semgrep + uses: actions/cache@v5 + with: + path: ~/.local + key: semgrep-1.160.0-${{ runner.os }} + - if: steps.cache-semgrep.outputs.cache-hit != 'true' + run: pip install --user semgrep==1.160.0 + - run: echo "$HOME/.local/bin" >> "$GITHUB_PATH" + - run: semgrep scan --config=auto From 02741bcb2864f92713c5662c0ca6cb88ccc4b176 Mon Sep 17 00:00:00 2001 From: Elie ROUDNINSKI Date: Mon, 11 May 2026 15:52:40 +0200 Subject: [PATCH 3/6] Add parser nesting depth limit --- engine/src/ast/field_expr.rs | 3 +- engine/src/ast/function_expr.rs | 41 ++++++++++++++- engine/src/ast/logical_expr.rs | 90 ++++++++++++++++++++++++++++++--- engine/src/ast/parse.rs | 43 +++++++++++++++- engine/src/lex.rs | 7 +++ 5 files changed, 175 insertions(+), 9 deletions(-) diff --git a/engine/src/ast/field_expr.rs b/engine/src/ast/field_expr.rs index 2a171a1e..44e4e87a 100644 --- a/engine/src/ast/field_expr.rs +++ b/engine/src/ast/field_expr.rs @@ -255,7 +255,8 @@ impl<'i, 's> LexWith<'i, &FilterParser<'s>> for IdentifierExpr { match item { Identifier::Field(field) => Ok((IdentifierExpr::Field(field.to_owned()), input)), Identifier::Function(function) => { - FunctionCallExpr::lex_with_function(input, parser, function) + let nested_parser = parser.with_increased_nesting(skip_space(input))?; + FunctionCallExpr::lex_with_function(input, &nested_parser, function) .map(|(call, input)| (IdentifierExpr::FunctionCallExpr(call), input)) } } diff --git a/engine/src/ast/function_expr.rs b/engine/src/ast/function_expr.rs index c9808941..ef6deab7 100644 --- a/engine/src/ast/function_expr.rs +++ b/engine/src/ast/function_expr.rs @@ -512,8 +512,9 @@ impl GetType for FunctionCallExpr { impl<'i> LexWith<'i, &FilterParser<'_>> for FunctionCallExpr { fn lex_with(input: &'i str, parser: &FilterParser<'_>) -> LexResult<'i, Self> { let (function, rest) = FunctionRef::lex_with(input, parser.scheme)?; + let nested_parser = parser.with_increased_nesting(skip_space(rest))?; - Self::lex_with_function(rest, parser, function) + Self::lex_with_function(rest, &nested_parser, function) } } @@ -566,6 +567,44 @@ mod tests { args.next()?.ok() } + #[test] + fn test_function_call_nesting_limit() { + let mut parser = FilterParser::new(&SCHEME); + parser.set_max_nesting_depth(2); + + assert_err!( + parser.lex_as::("echo ( echo ( echo ( http.host ) ) )"), + LexErrorKind::NestingLimitExceeded { limit: 2 }, + "( http.host ) ) )" + ); + } + + #[test] + fn test_value_expr_function_call_nesting_limit() { + let mut parser = FilterParser::new(&SCHEME); + parser.set_max_nesting_depth(2); + + assert_err!( + parser.lex_as::("echo ( echo ( echo ( http.host ) ) )"), + LexErrorKind::NestingLimitExceeded { limit: 2 }, + "( http.host ) ) )" + ); + } + + #[test] + fn test_logical_argument_nesting_limit_is_counted_via_function_and_parentheses() { + let mut parser = FilterParser::new(&SCHEME); + parser.set_max_nesting_depth(1); + + assert_err!( + parser.lex_as::( + "any ( ( http.request.headers.is_empty or http.request.headers.is_empty ) )" + ), + LexErrorKind::NestingLimitExceeded { limit: 1 }, + "( http.request.headers.is_empty or http.request.headers.is_empty ) )" + ); + } + fn len_function<'a>(args: FunctionArgs<'_, 'a>) -> Option> { match args.next()? { Ok(LhsValue::Bytes(bytes)) => Some(LhsValue::Int(i64::try_from(bytes.len()).unwrap())), diff --git a/engine/src/ast/logical_expr.rs b/engine/src/ast/logical_expr.rs index 4f1790a0..bb3e60a8 100644 --- a/engine/src/ast/logical_expr.rs +++ b/engine/src/ast/logical_expr.rs @@ -82,18 +82,20 @@ impl LogicalExpr { } fn lex_simple_expr<'i>(input: &'i str, parser: &FilterParser<'_>) -> LexResult<'i, Self> { - Ok(if let Ok(input) = expect(input, "(") { - let input = skip_space(input); - let (expr, input) = LogicalExpr::lex_with(input, parser)?; + Ok(if let Ok(rest) = expect(input, "(") { + let nested_parser = parser.with_increased_nesting(input)?; + let input = skip_space(rest); + let (expr, input) = LogicalExpr::lex_with(input, &nested_parser)?; let input = skip_space(input); let input = expect(input, ")")?; ( LogicalExpr::Parenthesized(Box::new(ParenthesizedExpr { expr })), input, ) - } else if let Ok((op, input)) = UnaryOp::lex(input) { - let input = skip_space(input); - let (arg, input) = Self::lex_simple_expr(input, parser)?; + } else if let Ok((op, rest)) = UnaryOp::lex(input) { + let nested_parser = parser.with_increased_nesting(input)?; + let input = skip_space(rest); + let (arg, input) = Self::lex_simple_expr(input, &nested_parser)?; ( LogicalExpr::Unary { op, @@ -550,6 +552,46 @@ fn test() { } ); + assert_ok!( + FilterParser::new(scheme).lex_as("t and (t or t)"), + LogicalExpr::Combining { + op: LogicalOp::And, + items: vec![ + t_expr(), + LogicalExpr::Parenthesized(Box::new(ParenthesizedExpr { + expr: LogicalExpr::Combining { + op: LogicalOp::Or, + items: vec![t_expr(), t_expr()], + }, + })), + ], + } + ); + + assert_ok!( + FilterParser::new(scheme).lex_as("t and (t or (t and t))"), + LogicalExpr::Combining { + op: LogicalOp::And, + items: vec![ + t_expr(), + LogicalExpr::Parenthesized(Box::new(ParenthesizedExpr { + expr: LogicalExpr::Combining { + op: LogicalOp::Or, + items: vec![ + t_expr(), + LogicalExpr::Parenthesized(Box::new(ParenthesizedExpr { + expr: LogicalExpr::Combining { + op: LogicalOp::And, + items: vec![t_expr(), t_expr()], + }, + })), + ], + }, + })), + ], + } + ); + { let expr = assert_ok!( FilterParser::new(scheme).lex_as("at and af"), @@ -879,6 +921,42 @@ fn test() { not_expr(parenthesized_expr(not_expr(not_expr(at_expr())))) ); + { + let mut parser = FilterParser::new(scheme); + parser.set_max_nesting_depth(1); + assert_err!( + parser.lex_as::("((t))"), + LexErrorKind::NestingLimitExceeded { limit: 1 }, + "(t))" + ); + } + + { + let mut parser = FilterParser::new(scheme); + parser.set_max_nesting_depth(1); + assert_err!( + parser.lex_as::("!!t"), + LexErrorKind::NestingLimitExceeded { limit: 1 }, + "!t" + ); + } + + { + let mut parser = FilterParser::new(scheme); + parser.set_max_nesting_depth(2); + assert_ok!(parser.lex_as("!!t"), not_expr(not_expr(t_expr()))); + } + + { + let mut parser = FilterParser::new(scheme); + parser.set_max_nesting_depth(0); + assert_err!( + parser.lex_as::("t and (t or t)"), + LexErrorKind::NestingLimitExceeded { limit: 0 }, + "(t or t)" + ); + } + { let expr = assert_ok!( FilterParser::new(scheme).lex_as("not t && f"), diff --git a/engine/src/ast/parse.rs b/engine/src/ast/parse.rs index 562703d5..a9b79ffc 100644 --- a/engine/src/ast/parse.rs +++ b/engine/src/ast/parse.rs @@ -106,6 +106,9 @@ pub struct ParserSettings { /// Maximum number of star metacharacters allowed in a wildcard. /// Default: unlimited pub wildcard_star_limit: usize, + /// Maximum nesting depth allowed while parsing. + /// Default: 128 + pub max_nesting_depth: u16, } impl Default for ParserSettings { @@ -117,6 +120,7 @@ impl Default for ParserSettings { // Default value extracted from the regex crate. regex_dfa_size_limit: 2 * (1 << 20), wildcard_star_limit: usize::MAX, + max_nesting_depth: 128, } } } @@ -126,6 +130,7 @@ impl Default for ParserSettings { pub struct FilterParser<'s> { pub(crate) scheme: &'s Scheme, pub(crate) settings: ParserSettings, + current_nesting_depth: u16, } impl<'s> FilterParser<'s> { @@ -135,13 +140,18 @@ impl<'s> FilterParser<'s> { Self { scheme, settings: ParserSettings::default(), + current_nesting_depth: 0, } } /// Creates a new parser with the specified settings. #[inline] pub fn with_settings(scheme: &'s Scheme, settings: ParserSettings) -> Self { - Self { scheme, settings } + Self { + scheme, + settings, + current_nesting_depth: 0, + } } /// Returns the [`Scheme`](struct@Scheme) for which this parser has been constructor for. @@ -158,6 +168,25 @@ impl<'s> FilterParser<'s> { L::lex_with(input, self) } + #[inline] + pub(crate) fn with_increased_nesting<'i>( + &self, + span: &'i str, + ) -> Result { + if self.current_nesting_depth >= self.settings.max_nesting_depth { + Err(( + LexErrorKind::NestingLimitExceeded { + limit: self.settings.max_nesting_depth, + }, + span, + )) + } else { + let mut nested = self.clone(); + nested.current_nesting_depth += 1; + Ok(nested) + } + } + /// Parses a filter expression into an AST form. pub fn parse<'i>(&self, input: &'i str) -> Result> { complete(self.lex_as(input.trim())).map_err(|err| ParseError::new(input, err)) @@ -209,4 +238,16 @@ impl<'s> FilterParser<'s> { pub fn wildcard_get_star_limit(&self) -> usize { self.settings.wildcard_star_limit } + + /// Set the maximum nesting depth allowed while parsing. + #[inline] + pub fn set_max_nesting_depth(&mut self, max_nesting_depth: u16) { + self.settings.max_nesting_depth = max_nesting_depth; + } + + /// Get the maximum nesting depth allowed while parsing. + #[inline] + pub fn max_nesting_depth(&self) -> u16 { + self.settings.max_nesting_depth + } } diff --git a/engine/src/lex.rs b/engine/src/lex.rs index 4fcc5633..d6cdcf0d 100644 --- a/engine/src/lex.rs +++ b/engine/src/lex.rs @@ -150,6 +150,13 @@ pub enum LexErrorKind { /// Name of the list name: String, }, + + /// Maximum nesting depth exceeded while parsing. + #[error("maximum nesting depth exceeded (limit: {limit})")] + NestingLimitExceeded { + /// The configured maximum nesting depth. + limit: u16, + }, } pub type LexError<'i> = (LexErrorKind, &'i str); From c64460671755a79e2c9e0a7b69f818d38eece8cd Mon Sep 17 00:00:00 2001 From: Utkarsh Gupta Date: Tue, 12 May 2026 17:05:15 +0100 Subject: [PATCH 4/6] Add CompiledFunction type alias --- engine/src/ast/field_expr.rs | 13 ++++--------- engine/src/ast/function_expr.rs | 8 +++----- engine/src/functions/all.rs | 11 +++++------ engine/src/functions/any.rs | 11 +++++------ engine/src/functions/concat.rs | 12 ++++++------ engine/src/functions/mod.rs | 12 +++++++++--- engine/src/lib.rs | 8 ++++---- 7 files changed, 36 insertions(+), 39 deletions(-) diff --git a/engine/src/ast/field_expr.rs b/engine/src/ast/field_expr.rs index 44e4e87a..147b4362 100644 --- a/engine/src/ast/field_expr.rs +++ b/engine/src/ast/field_expr.rs @@ -799,9 +799,9 @@ mod tests { use crate::ast::logical_expr::LogicalExpr; use crate::execution_context::ExecutionContext; use crate::functions::{ - FunctionArgKind, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, - FunctionParam, FunctionParamError, SimpleFunctionDefinition, SimpleFunctionImpl, - SimpleFunctionOptParam, SimpleFunctionParam, + CompiledFunction, FunctionArgKind, FunctionArgs, FunctionDefinition, + FunctionDefinitionContext, FunctionParam, FunctionParamError, SimpleFunctionDefinition, + SimpleFunctionImpl, SimpleFunctionOptParam, SimpleFunctionParam, }; use crate::lhs_types::{Array, Map}; use crate::list_matcher::{ListDefinition, ListMatcher}; @@ -908,12 +908,7 @@ mod tests { &self, _: &mut dyn ExactSizeIterator>, _: Option, - ) -> Box< - dyn for<'i, 'a> Fn(FunctionArgs<'i, 'a>) -> Option> - + Sync - + Send - + 'static, - > { + ) -> CompiledFunction { Box::new(|args| { let value_array = Array::try_from(args.next().unwrap().unwrap()).unwrap(); let keep_array = Array::try_from(args.next().unwrap().unwrap()).unwrap(); diff --git a/engine/src/ast/function_expr.rs b/engine/src/ast/function_expr.rs index ef6deab7..c0656c91 100644 --- a/engine/src/ast/function_expr.rs +++ b/engine/src/ast/function_expr.rs @@ -8,7 +8,7 @@ use crate::ast::logical_expr::{LogicalExpr, UnaryOp}; use crate::compiler::Compiler; use crate::filter::{CompiledExpr, CompiledValueExpr, CompiledValueResult}; use crate::functions::{ - ExactSizeChain, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, FunctionParam, + CompiledFunction, ExactSizeChain, FunctionDefinition, FunctionDefinitionContext, FunctionParam, FunctionParamError, }; use crate::lex::{Lex, LexError, LexErrorKind, LexResult, LexWith, expect, skip_space, span}; @@ -272,11 +272,9 @@ impl ValueExpr for FunctionCallExpr { let first = args.remove(0); #[inline(always)] - fn compute<'s, 'a, I: ExactSizeIterator>>( + fn compute<'a, I: ExactSizeIterator>>( first: CompiledValueResult<'a>, - call: &( - dyn for<'b> Fn(FunctionArgs<'_, 'b>) -> Option> + Sync + Send + 's - ), + call: &CompiledFunction, return_type: Type, f: impl Fn(LhsValue<'a>) -> I, ) -> CompiledValueResult<'a> { diff --git a/engine/src/functions/all.rs b/engine/src/functions/all.rs index b78cb8e8..b2b56533 100644 --- a/engine/src/functions/all.rs +++ b/engine/src/functions/all.rs @@ -1,6 +1,6 @@ use crate::{ - FunctionArgKind, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, FunctionParam, - FunctionParamError, GetType, LhsValue, ParserSettings, Type, + CompiledFunction, FunctionArgKind, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, + FunctionParam, FunctionParamError, GetType, LhsValue, ParserSettings, Type, }; use std::iter::once; @@ -58,12 +58,11 @@ impl FunctionDefinition for AllFunction { (1, Some(0)) } - fn compile<'s>( - &'s self, + fn compile( + &self, _: &mut dyn ExactSizeIterator>, _: Option, - ) -> Box Fn(FunctionArgs<'i, 'a>) -> Option> + Sync + Send + 'static> - { + ) -> CompiledFunction { Box::new(all_impl) } } diff --git a/engine/src/functions/any.rs b/engine/src/functions/any.rs index a9f90b76..60e35d91 100644 --- a/engine/src/functions/any.rs +++ b/engine/src/functions/any.rs @@ -1,6 +1,6 @@ use crate::{ - FunctionArgKind, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, FunctionParam, - FunctionParamError, GetType, LhsValue, ParserSettings, Type, + CompiledFunction, FunctionArgKind, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, + FunctionParam, FunctionParamError, GetType, LhsValue, ParserSettings, Type, }; use std::iter::once; @@ -58,12 +58,11 @@ impl FunctionDefinition for AnyFunction { (1, Some(0)) } - fn compile<'s>( - &'s self, + fn compile( + &self, _: &mut dyn ExactSizeIterator>, _: Option, - ) -> Box Fn(FunctionArgs<'i, 'a>) -> Option> + Sync + Send + 'static> - { + ) -> CompiledFunction { Box::new(any_impl) } } diff --git a/engine/src/functions/concat.rs b/engine/src/functions/concat.rs index 1d493d93..db026b4f 100644 --- a/engine/src/functions/concat.rs +++ b/engine/src/functions/concat.rs @@ -1,6 +1,7 @@ use crate::{ - Array, Bytes, ExpectedType, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, - FunctionParam, FunctionParamError, GetType, LhsValue, ParserSettings, Type, + Array, Bytes, CompiledFunction, ExpectedType, FunctionArgs, FunctionDefinition, + FunctionDefinitionContext, FunctionParam, FunctionParamError, GetType, LhsValue, + ParserSettings, Type, }; use std::iter::once; @@ -90,12 +91,11 @@ impl FunctionDefinition for ConcatFunction { (2, None) } - fn compile<'s>( - &'s self, + fn compile( + &self, _: &mut dyn ExactSizeIterator>, _: Option, - ) -> Box Fn(FunctionArgs<'i, 'a>) -> Option> + Sync + Send + 'static> - { + ) -> CompiledFunction { Box::new(|args| { while let Some(arg) = args.next() { match arg { diff --git a/engine/src/functions/mod.rs b/engine/src/functions/mod.rs index 5129791a..2af36b10 100644 --- a/engine/src/functions/mod.rs +++ b/engine/src/functions/mod.rs @@ -373,6 +373,13 @@ impl std::fmt::Debug for FunctionDefinitionContext { } } +/// A compiled function that can be called during filter execution. +/// +/// Returned by [`FunctionDefinition::compile`] after a function call expression +/// has been validated and compiled. +pub type CompiledFunction = + Box Fn(FunctionArgs<'i, 'a>) -> Option> + Sync + Send + 'static>; + /// Trait to implement function pub trait FunctionDefinition: Debug + Send + Sync { /// Custom context to store information during parsing @@ -404,7 +411,7 @@ pub trait FunctionDefinition: Debug + Send + Sync { &self, params: &mut dyn ExactSizeIterator>, ctx: Option, - ) -> Box Fn(FunctionArgs<'i, 'a>) -> Option> + Sync + Send + 'static>; + ) -> CompiledFunction; } // Simple function APIs @@ -530,8 +537,7 @@ impl FunctionDefinition for SimpleFunctionDefinition { &self, params: &mut dyn ExactSizeIterator>, _: Option, - ) -> Box Fn(FunctionArgs<'i, 'a>) -> Option> + Sync + Send + 'static> - { + ) -> CompiledFunction { let params_count = params.len(); let opt_params = &self.opt_params[(params_count - self.params.len())..]; let implementation = self.implementation; diff --git a/engine/src/lib.rs b/engine/src/lib.rs index e9953d1d..e003eb9b 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -96,10 +96,10 @@ pub use self::filter::{ CompiledExpr, CompiledOneExpr, CompiledValueExpr, CompiledVecExpr, Filter, FilterValue, }; pub use self::functions::{ - AllFunction, AnyFunction, ConcatFunction, FunctionArgInvalidConstantError, FunctionArgKind, - FunctionArgKindMismatchError, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, - FunctionParam, FunctionParamError, SimpleFunctionArgKind, SimpleFunctionDefinition, - SimpleFunctionImpl, SimpleFunctionOptParam, SimpleFunctionParam, + AllFunction, AnyFunction, CompiledFunction, ConcatFunction, FunctionArgInvalidConstantError, + FunctionArgKind, FunctionArgKindMismatchError, FunctionArgs, FunctionDefinition, + FunctionDefinitionContext, FunctionParam, FunctionParamError, SimpleFunctionArgKind, + SimpleFunctionDefinition, SimpleFunctionImpl, SimpleFunctionOptParam, SimpleFunctionParam, }; pub use self::lex::LexErrorKind; pub use self::lhs_types::{Array, Bytes, Map, MapIter, TypedArray, TypedMap}; From 95faace0730c78974492e9c3bd9df23084779271 Mon Sep 17 00:00:00 2001 From: Utkarsh Gupta Date: Tue, 12 May 2026 17:06:38 +0100 Subject: [PATCH 5/6] Extract _impl functions --- engine/src/functions/all.rs | 1 - engine/src/functions/any.rs | 1 - engine/src/functions/concat.rs | 48 ++++++++++++++++++---------------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/engine/src/functions/all.rs b/engine/src/functions/all.rs index b2b56533..152aed41 100644 --- a/engine/src/functions/all.rs +++ b/engine/src/functions/all.rs @@ -4,7 +4,6 @@ use crate::{ }; use std::iter::once; -#[inline] fn all_impl<'a>(args: FunctionArgs<'_, 'a>) -> Option> { let arg = args.next().expect("expected 1 argument, got 0"); if args.next().is_some() { diff --git a/engine/src/functions/any.rs b/engine/src/functions/any.rs index 60e35d91..66199d33 100644 --- a/engine/src/functions/any.rs +++ b/engine/src/functions/any.rs @@ -4,7 +4,6 @@ use crate::{ }; use std::iter::once; -#[inline] fn any_impl<'a>(args: FunctionArgs<'_, 'a>) -> Option> { let arg = args.next().expect("expected 1 argument, got 0"); if args.next().is_some() { diff --git a/engine/src/functions/concat.rs b/engine/src/functions/concat.rs index db026b4f..681b099e 100644 --- a/engine/src/functions/concat.rs +++ b/engine/src/functions/concat.rs @@ -20,6 +20,7 @@ impl ConcatFunction { } } +#[inline] fn concat_array<'a>(accumulator: Array<'a>, args: FunctionArgs<'_, 'a>) -> Array<'a> { let mut args = args.flat_map(|arg| arg.ok()); let Some(first) = args.next() else { @@ -44,6 +45,7 @@ fn concat_array<'a>(accumulator: Array<'a>, args: FunctionArgs<'_, 'a>) -> Array Array::try_from_vec(val_type, vec).unwrap() } +#[inline] fn concat_bytes<'a>(mut accumulator: Vec, args: FunctionArgs<'_, 'a>) -> Bytes<'a> { for arg in args { match arg { @@ -55,6 +57,25 @@ fn concat_bytes<'a>(mut accumulator: Vec, args: FunctionArgs<'_, 'a>) -> Byt accumulator.into() } +fn concat_impl<'a>(args: FunctionArgs<'_, 'a>) -> Option> { + while let Some(arg) = args.next() { + match arg { + Ok(LhsValue::Array(array)) => { + return Some(LhsValue::Array(concat_array(array, args))); + } + Ok(LhsValue::Bytes(bytes)) => { + return Some(LhsValue::Bytes(concat_bytes( + bytes.into_owned().into(), + args, + ))); + } + Err(_) => (), + _ => unreachable!(), + } + } + None +} + pub(crate) const EXPECTED_TYPES: [ExpectedType; 2] = [ExpectedType::Array, ExpectedType::Type(Type::Bytes)]; @@ -96,24 +117,7 @@ impl FunctionDefinition for ConcatFunction { _: &mut dyn ExactSizeIterator>, _: Option, ) -> CompiledFunction { - Box::new(|args| { - while let Some(arg) = args.next() { - match arg { - Ok(LhsValue::Array(array)) => { - return Some(LhsValue::Array(concat_array(array, args))); - } - Ok(LhsValue::Bytes(bytes)) => { - return Some(LhsValue::Bytes(concat_bytes( - bytes.into_owned().into(), - args, - ))); - } - Err(_) => (), - _ => unreachable!(), - } - } - None - }) + Box::new(concat_impl) } } @@ -133,7 +137,7 @@ mod tests { .into_iter(); assert_eq!( Some(LhsValue::Bytes(Bytes::Borrowed(b"helloworld"))), - CONCAT_FN.compile(&mut std::iter::empty(), None)(&mut args) + concat_impl(&mut args) ); } @@ -148,7 +152,7 @@ mod tests { .into_iter(); assert_eq!( Some(LhsValue::Bytes(Bytes::Borrowed(b"helloworldhello2world2"))), - CONCAT_FN.compile(&mut std::iter::empty(), None)(&mut args) + concat_impl(&mut args) ); } @@ -159,7 +163,7 @@ mod tests { let mut args = vec![Ok(arg1), Ok(arg2)].into_iter(); assert_eq!( Some(LhsValue::Array(Array::from_iter([1, 2, 3, 4, 5, 6]))), - CONCAT_FN.compile(&mut std::iter::empty(), None)(&mut args) + concat_impl(&mut args) ); } @@ -167,7 +171,7 @@ mod tests { #[should_panic] fn test_concat_function_bad_arg_type() { let mut args = vec![Ok(LhsValue::from(2))].into_iter(); - CONCAT_FN.compile(&mut std::iter::empty(), None)(&mut args); + concat_impl(&mut args); } #[test] From 034b8889c4dff09bbe561b00b252ccbbccf90ada Mon Sep 17 00:00:00 2001 From: Elie ROUDNINSKI Date: Mon, 18 May 2026 17:34:55 +0200 Subject: [PATCH 6/6] Add any/all missing array tests --- engine/src/ast/field_expr.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/engine/src/ast/field_expr.rs b/engine/src/ast/field_expr.rs index 147b4362..36f1f89d 100644 --- a/engine/src/ast/field_expr.rs +++ b/engine/src/ast/field_expr.rs @@ -799,7 +799,7 @@ mod tests { use crate::ast::logical_expr::LogicalExpr; use crate::execution_context::ExecutionContext; use crate::functions::{ - CompiledFunction, FunctionArgKind, FunctionArgs, FunctionDefinition, + AllFunction, CompiledFunction, FunctionArgKind, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, FunctionParam, FunctionParamError, SimpleFunctionDefinition, SimpleFunctionImpl, SimpleFunctionOptParam, SimpleFunctionParam, }; @@ -967,6 +967,8 @@ mod tests { tcp.port: Int, tcp.ports: Array(Int), array.of.bool: Array(Bool), + map.bool.arr: Map(Array(Bool)), + map.bytes.arr: Map(Array(Bytes)), http.parts: Array(Array(Bytes)), }; builder @@ -983,6 +985,7 @@ mod tests { }, ) .unwrap(); + builder.add_function("all", AllFunction::default()).unwrap(); builder .add_function( "echo", @@ -2633,6 +2636,34 @@ mod tests { assert_eq!(expr.execute_one(ctx), false); } + #[test] + fn test_any_all_functions_with_missing_arrays() { + let ctx = &mut ExecutionContext::new(&SCHEME); + ctx.set_field_value( + field("map.bool.arr"), + Map::new(Type::Array(Type::Bool.into())), + ) + .unwrap(); + ctx.set_field_value( + field("map.bytes.arr"), + Map::new(Type::Array(Type::Bytes.into())), + ) + .unwrap(); + + let execute = |input| SCHEME.parse(input).unwrap().compile().execute(ctx).unwrap(); + + assert_eq!(execute(r#"any(map.bool.arr["missing"])"#), false); + assert_eq!(execute(r#"all(map.bool.arr["missing"])"#), false); + assert_eq!( + execute(r#"any(map.bytes.arr["missing"][*] matches "bar")"#), + false + ); + assert_eq!( + execute(r#"all(map.bytes.arr["missing"][*] matches "bar")"#), + true + ); + } + #[test] fn test_map_each_nested() { let expr = assert_ok!(