Skip to content

Commit 12c5257

Browse files
committed
ENH: add cat string operator
1 parent 7d2be93 commit 12c5257

File tree

4 files changed

+65
-1
lines changed

4 files changed

+65
-1
lines changed

src/data.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Data functions and operators
2-
//!
2+
3+
// TODO: it's possible that "missing", "var", et al. could be implemented
4+
// as operators. They were originally done as parsers because there wasn't
5+
// yet a LazyOperator concept.
36

47
use crate::error::Error;
58
use crate::value::Evaluated;

src/lib.rs

+16
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,17 @@ mod jsonlogic_tests {
852852
]
853853
}
854854

855+
fn cat_cases() -> Vec<(Value, Value, Result<Value, ()>)> {
856+
vec![
857+
(json!({"cat": []}), json!({}), Ok(json!(""))),
858+
(json!({"cat": [1]}), json!({}), Err(())),
859+
(json!({"cat": ["a"]}), json!({}), Ok(json!("a"))),
860+
(json!({"cat": ["a", "b"]}), json!({}), Ok(json!("ab"))),
861+
(json!({"cat": ["a", "b", "c"]}), json!({}), Ok(json!("abc"))),
862+
(json!({"cat": ["a", "b", 1]}), json!({}), Err(())),
863+
]
864+
}
865+
855866
fn lt_cases() -> Vec<(Value, Value, Result<Value, ()>)> {
856867
vec![
857868
(json!({"<": [1, 2]}), json!({}), Ok(json!(true))),
@@ -1176,6 +1187,11 @@ mod jsonlogic_tests {
11761187
merge_cases().into_iter().for_each(assert_jsonlogic)
11771188
}
11781189

1190+
#[test]
1191+
fn test_cat_op() {
1192+
cat_cases().into_iter().for_each(assert_jsonlogic)
1193+
}
1194+
11791195
#[test]
11801196
fn test_lt_op() {
11811197
lt_cases().into_iter().for_each(assert_jsonlogic)

src/op/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
//! Operators
22
//!
3+
//! This module contains the global operator map, which defines the available
4+
//! JsonLogic operations. Note that some "operations", notably data-related
5+
//! operations like "var" and "missing", are not included here, because they are
6+
//! implemented as parsers rather than operators.
7+
8+
// TODO: it's possible that "missing", "var", et al. could be implemented
9+
// as operators. They were originally done differently because there wasn't
10+
// yet a LazyOperator concept.
311

412
use phf::phf_map;
513
use serde_json::{Map, Number, Value};
@@ -12,6 +20,7 @@ use crate::{js_op, Parser};
1220
mod array;
1321
mod logic;
1422
mod numeric;
23+
mod string;
1524

1625
pub const OPERATOR_MAP: phf::Map<&'static str, Operator> = phf_map! {
1726
"==" => Operator {
@@ -164,6 +173,11 @@ pub const OPERATOR_MAP: phf::Map<&'static str, Operator> = phf_map! {
164173
operator: array::in_,
165174
num_params: NumParams::Exactly(2),
166175
},
176+
"cat" => Operator {
177+
symbol: "cat",
178+
operator: string::cat,
179+
num_params: NumParams::Any,
180+
},
167181
};
168182

169183
pub const LAZY_OPERATOR_MAP: phf::Map<&'static str, LazyOperator> = phf_map! {

src/op/string.rs

+31
Original file line numberDiff line numberDiff line change
@@ -1 +1,32 @@
11
//! String Operations
2+
3+
use serde_json::Value;
4+
5+
use crate::error::Error;
6+
7+
/// Concatenate strings.
8+
///
9+
/// Note: the reference implementation just uses JS' builtin string
10+
/// concatenation with implicit casting, so e.g. `cast("foo", {})`
11+
/// evaluates to `"foo[object Object]". Here we explicitly require all
12+
/// arguments to be strings, because the specification explicitly defines
13+
/// `cat` as a string operation.
14+
pub fn cat(items: &Vec<&Value>) -> Result<Value, Error> {
15+
let mut rv = String::from("");
16+
items
17+
.into_iter()
18+
.map(|i| match i {
19+
Value::String(i_string) => Ok(i_string),
20+
_ => Err(Error::InvalidArgument {
21+
value: (**i).clone(),
22+
operation: "cat".into(),
23+
reason: "All arguments to `cat` must be strings".into(),
24+
}),
25+
})
26+
.fold(Ok(&mut rv), |acc: Result<&mut String, Error>, i| {
27+
let rv = acc?;
28+
rv.push_str(i?);
29+
Ok(rv)
30+
})?;
31+
Ok(Value::String(rv))
32+
}

0 commit comments

Comments
 (0)