Skip to content

Commit

Permalink
feat: New VRL functions to support scripting string operations (#18)
Browse files Browse the repository at this point in the history
Adds new string functions to support scripting string operations that
can't be supported by the default, built-in VRL functions.

Ref: LOG-17381
  • Loading branch information
penick authored Jun 29, 2023
1 parent 6f6e17e commit d82ab80
Show file tree
Hide file tree
Showing 13 changed files with 1,884 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lib/stdlib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ sha-1 = { version = "0.10", optional = true }
sha-2 = { package = "sha2", version = "0.10", optional = true }
sha-3 = { package = "sha3", version = "0.10", optional = true }
strip-ansi-escapes = { version = "0.1", optional = true }
substring = { version = "1.4.5", optional = true }
syslog_loose = { version = "0.18", optional = true }
tracing = { version = "0.1", optional = true }
url = { version = "2", optional = true }
Expand Down Expand Up @@ -169,6 +170,7 @@ default = [
"mezmo_parse_int",
"mezmo_relational_comparison",
"mezmo_to_string",
"mezmo_string_operations",
"mod",
"now",
"object",
Expand Down Expand Up @@ -335,6 +337,7 @@ mezmo_is_truthy = []
mezmo_parse_int = ["parse_int"]
mezmo_parse_float = ["to_float"]
mezmo_relational_comparison = []
mezmo_string_operations = ["substring"]
mezmo_to_string = ["to_string"]
mod = []
now = ["dep:chrono"]
Expand Down
67 changes: 67 additions & 0 deletions lib/stdlib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,31 @@ mod mezmo_parse_float;
mod mezmo_parse_int;
#[cfg(feature = "mezmo_relational_comparison")]
mod mezmo_relational_comparison;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_char_at;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_index_of;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_last_index_of;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_pad_end;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_pad_start;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_repeat;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_string_at;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_string_slice;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_substring;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_trim_end;
#[cfg(feature = "mezmo_string_operations")]
mod mezmo_trim_start;
#[cfg(feature = "mezmo_to_string")]
mod mezmo_to_string;

#[cfg(feature = "mod")]
mod mod_func;
#[cfg(feature = "now")]
Expand Down Expand Up @@ -540,6 +563,28 @@ pub use mezmo_parse_float::MezmoParseFloat;
pub use mezmo_parse_int::MezmoParseInt;
#[cfg(feature = "mezmo_relational_comparison")]
pub use mezmo_relational_comparison::{MezmoGt, MezmoGte, MezmoLt, MezmoLte};
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_char_at::MezmoCharAt;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_index_of::MezmoIndexOf;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_last_index_of::MezmoLastIndexOf;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_pad_end::MezmoPadEnd;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_pad_start::MezmoPadStart;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_repeat::MezmoRepeat;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_string_at::MezmoStringAt;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_string_slice::MezmoStringSlice;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_substring::MezmoSubstring;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_trim_end::MezmoTrimEnd;
#[cfg(feature = "mezmo_string_operations")]
pub use mezmo_trim_start::MezmoTrimStart;
#[cfg(feature = "mezmo_to_string")]
pub use mezmo_to_string::MezmoToString;
#[cfg(feature = "mod")]
Expand Down Expand Up @@ -891,6 +936,28 @@ pub fn all() -> Vec<Box<dyn vrl::Function>> {
Box::new(MezmoLt),
#[cfg(feature = "mezmo_relational_comparison")]
Box::new(MezmoLte),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoCharAt),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoIndexOf),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoLastIndexOf),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoPadEnd),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoPadStart),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoRepeat),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoStringAt),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoStringSlice),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoSubstring),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoTrimEnd),
#[cfg(feature = "mezmo_string_operations")]
Box::new(MezmoTrimStart),
#[cfg(feature = "mezmo_to_string")]
Box::new(MezmoToString),
#[cfg(feature = "mod")]
Expand Down
130 changes: 130 additions & 0 deletions lib/stdlib/src/mezmo_char_at.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use std::borrow::Cow;

use ::value::Value;
use compiler::{value::VrlValueConvert, Expression};
use vrl::prelude::*;
use vrl_core::Resolved;

fn char_at(s: Cow<'_, str>, index: i64) -> Value {
if index >= 0 {
s.chars()
.nth(index as usize)
.map(|c| c.to_string())
.unwrap_or(String::new())
.into()
} else {
String::new().into()
}
}

/// Returns the char at the given index as a string. Negative and out of range
/// indexes return an empty string.
///
/// Behaves like the JavaScript's String.prototype.charAt() method.
#[derive(Clone, Copy, Debug)]
pub struct MezmoCharAt;

impl Function for MezmoCharAt {
fn identifier(&self) -> &'static str {
"mezmo_char_at"
}

fn parameters(&self) -> &'static [Parameter] {
&[
Parameter {
keyword: "value",
kind: kind::BYTES,
required: true,
},
Parameter {
keyword: "index",
kind: kind::INTEGER,
required: true,
},
]
}

fn examples(&self) -> &'static [Example] {
&[Example {
title: "basic",
source: "mezmo_char_at(\"abc\", 0)",
result: Ok("a"),
}]
}

fn compile(
&self,
_state: &state::TypeState,
_ctx: &mut FunctionCompileContext,
arguments: ArgumentList,
) -> Compiled {
let value = arguments.required("value");
let index = arguments.required("index");

Ok(MezmoCharAtFn { value, index }.as_expr())
}
}

#[derive(Debug, Clone)]
struct MezmoCharAtFn {
value: Box<dyn Expression>,
index: Box<dyn Expression>,
}

impl FunctionExpression for MezmoCharAtFn {
fn resolve(&self, ctx: &mut Context) -> Resolved {
let value = self.value.resolve(ctx)?;
let index = self.index.resolve(ctx)?;

Ok(char_at(value.try_bytes_utf8_lossy()?, index.try_integer()?))
}

fn type_def(&self, _state: &state::TypeState) -> TypeDef {
TypeDef::bytes().infallible()
}
}

#[cfg(test)]
mod tests {
use super::*;

test_function![
mezmo_char_at => MezmoCharAt;

basic {
args: func_args![value: "abc", index: 0],
want: Ok("a"),
tdef: TypeDef::bytes().infallible(),
}

negative_index {
args: func_args![value: "abc", index: -3],
want: Ok(""),
tdef: TypeDef::bytes().infallible(),
}

invalid_index {
args: func_args![value: "abc", index: 4],
want: Ok(""),
tdef: TypeDef::bytes().infallible(),
}

empty {
args: func_args![value: "", index: 0],
want: Ok(""),
tdef: TypeDef::bytes().infallible(),
}

empty_non_zero {
args: func_args![value: "", index: 1],
want: Ok(""),
tdef: TypeDef::bytes().infallible(),
}

empty_negative {
args: func_args![value: "", index: -1],
want: Ok(""),
tdef: TypeDef::bytes().infallible(),
}
];
}
Loading

0 comments on commit d82ab80

Please sign in to comment.