-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
enhancement(remap): Add join function (#6313)
* Add basic function scaffolding Signed-off-by: Luc Perkins <luc@timber.io> * Add initial implementation of join logic plus tests Signed-off-by: Luc Perkins <luc@timber.io> * Add more tests Signed-off-by: Luc Perkins <luc@timber.io> * Add comment explaining vector length comparison Signed-off-by: Luc Perkins <luc@timber.io> * Re-use array variable Signed-off-by: Luc Perkins <luc@timber.io> * Fix formatting issue Signed-off-by: Luc Perkins <luc@timber.io> * Adopt Clippy suggestion Signed-off-by: Luc Perkins <luc@timber.io> * Simplify type def Signed-off-by: Luc Perkins <luc@timber.io> * Refactor string collection logic Signed-off-by: Luc Perkins <luc@timber.io> * Remove string conversion logic Signed-off-by: Luc Perkins <luc@timber.io> * Remove unnecessary type signature Signed-off-by: Luc Perkins <luc@timber.io> * Add inner type def for value array Signed-off-by: Luc Perkins <luc@timber.io> * Add mixed type array test Signed-off-by: Luc Perkins <luc@timber.io> * Add inner type constraint for join array Signed-off-by: Luc Perkins <luc@timber.io> * Change function name and add docs Signed-off-by: Luc Perkins <luc@timber.io> * Add behavior test Signed-off-by: Luc Perkins <luc@timber.io> * Add tests for new array type constraint function Signed-off-by: Luc Perkins <luc@timber.io> * Set fallible to true for non-arrays Signed-off-by: Luc Perkins <luc@timber.io> * Add more tests Signed-off-by: Luc Perkins <luc@timber.io> * Fix behavior test Signed-off-by: Luc Perkins <luc@timber.io> * Make maps fallible in array type function Signed-off-by: Luc Perkins <luc@timber.io> * Add test for maps Signed-off-by: Luc Perkins <luc@timber.io> * Set fallible to true unless array + mismatched kinds Signed-off-by: Luc Perkins <luc@timber.io> * Revamp inner type logic Signed-off-by: Luc Perkins <luc@timber.io>
- Loading branch information
1 parent
fe17fe4
commit 103a0a7
Showing
5 changed files
with
282 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
use remap::prelude::*; | ||
use std::borrow::Cow; | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
pub struct Join; | ||
|
||
impl Function for Join { | ||
fn identifier(&self) -> &'static str { | ||
"join" | ||
} | ||
|
||
fn parameters(&self) -> &'static [Parameter] { | ||
&[ | ||
Parameter { | ||
keyword: "value", | ||
accepts: |v| matches!(v, Value::Array(_)), | ||
required: true, | ||
}, | ||
Parameter { | ||
keyword: "separator", | ||
accepts: |v| matches!(v, Value::Bytes(_)), | ||
required: false, | ||
}, | ||
] | ||
} | ||
|
||
fn compile(&self, mut arguments: ArgumentList) -> Result<Box<dyn Expression>> { | ||
let value = arguments.required("value")?.boxed(); | ||
let separator = arguments.optional("separator").map(Expr::boxed); | ||
|
||
Ok(Box::new(JoinFn { value, separator })) | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
struct JoinFn { | ||
value: Box<dyn Expression>, | ||
separator: Option<Box<dyn Expression>>, | ||
} | ||
|
||
impl Expression for JoinFn { | ||
fn execute(&self, state: &mut state::Program, object: &mut dyn Object) -> Result<Value> { | ||
let array = self.value.execute(state, object)?.try_array()?; | ||
|
||
let string_vec = array | ||
.iter() | ||
.map(|s| s.try_bytes_utf8_lossy().map_err(Into::into)) | ||
.collect::<Result<Vec<Cow<'_, str>>>>() | ||
.map_err(|_| "all array items must be strings")?; | ||
|
||
let separator: String = self | ||
.separator | ||
.as_ref() | ||
.map(|s| { | ||
s.execute(state, object) | ||
.and_then(|v| Value::try_bytes(v).map_err(Into::into)) | ||
}) | ||
.transpose()? | ||
.map(|s| String::from_utf8_lossy(&s).to_string()) | ||
.unwrap_or_else(|| "".into()); | ||
|
||
let joined = string_vec.join(&separator); | ||
|
||
Ok(Value::from(joined)) | ||
} | ||
|
||
fn type_def(&self, state: &state::Compiler) -> TypeDef { | ||
use value::Kind; | ||
|
||
let separator_type = self | ||
.separator | ||
.as_ref() | ||
.map(|separator| separator.type_def(state).fallible_unless(Kind::Bytes)); | ||
|
||
self.value | ||
.type_def(state) | ||
.fallible_unless(Kind::Array) | ||
.merge_optional(separator_type) | ||
.fallible_unless_array_has_inner_type(Kind::Bytes) | ||
.with_constraint(Kind::Bytes) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
use value::Kind; | ||
|
||
test_type_def![ | ||
value_string_array_infallible { | ||
expr: |_| JoinFn { | ||
value: array!["one", "two", "three"].boxed(), | ||
separator: Some(lit!(", ").boxed()), | ||
}, | ||
def: TypeDef { | ||
fallible: false, | ||
kind: Kind::Bytes, | ||
..Default::default() | ||
}, | ||
} | ||
|
||
value_mixed_array_fallible { | ||
expr: |_| JoinFn { | ||
value: array!["one", 1].boxed(), | ||
separator: Some(lit!(", ").boxed()), | ||
}, | ||
def: TypeDef { | ||
fallible: true, | ||
kind: Kind::Bytes, | ||
..Default::default() | ||
}, | ||
} | ||
|
||
value_literal_fallible { | ||
expr: |_| JoinFn { | ||
value: lit!(427).boxed(), | ||
separator: None, | ||
}, | ||
def: TypeDef { | ||
fallible: true, | ||
kind: Kind::Bytes, | ||
..Default::default() | ||
}, | ||
} | ||
|
||
separator_integer_fallible { | ||
expr: |_| JoinFn { | ||
value: array!["one", "two", "three"].boxed(), | ||
separator: Some(lit!(427).boxed()), | ||
}, | ||
def: TypeDef { | ||
fallible: true, | ||
kind: Kind::Bytes, | ||
..Default::default() | ||
}, | ||
} | ||
|
||
both_types_wrong_fallible { | ||
expr: |_| JoinFn { | ||
value: lit!(true).boxed(), | ||
separator: Some(lit!(427).boxed()), | ||
}, | ||
def: TypeDef { | ||
fallible: true, | ||
kind: Kind::Bytes, | ||
..Default::default() | ||
}, | ||
} | ||
]; | ||
|
||
test_function![ | ||
join => Join; | ||
|
||
with_comma_separator { | ||
args: func_args![value: array!["one", "two", "three"], separator: lit!(", ")], | ||
want: Ok(value!("one, two, three")), | ||
} | ||
|
||
with_space_separator { | ||
args: func_args![value: array!["one", "two", "three"], separator: lit!(" ")], | ||
want: Ok(value!("one two three")), | ||
} | ||
|
||
without_separator { | ||
args: func_args![value: array!["one", "two", "three"]], | ||
want: Ok(value!("onetwothree")), | ||
} | ||
|
||
non_string_array_item_throws_error { | ||
args: func_args![value: array!["one", "two", 3]], | ||
want: Err("function call error: all array items must be strings"), | ||
} | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters