use simd_json::OwnedValue as Value;
use simd_json::StaticNode;
use super::type_error;
use crate::error::EvalError;
pub fn eval_type(value: &Value) -> Value {
let type_str = match value {
Value::Static(StaticNode::Null) => "null",
Value::Static(StaticNode::Bool(_)) => "boolean",
Value::Static(StaticNode::I64(_) | StaticNode::U64(_) | StaticNode::F64(_)) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
};
Value::String(type_str.to_string())
}
pub fn eval_not(value: &Value) -> Value {
let is_falsy = matches!(
value,
Value::Static(StaticNode::Null) | Value::Static(StaticNode::Bool(false))
);
Value::Static(StaticNode::Bool(is_falsy))
}
pub fn eval_csv(value: &Value) -> Result<Value, EvalError> {
let arr = match value {
Value::Array(a) => a,
_ => {
return Err(type_error(format!(
"@csv requires array, got {}",
type_name(value)
)));
}
};
let fields: Vec<String> = arr.iter().map(format_csv_field).collect();
Ok(Value::String(fields.join(",")))
}
pub fn eval_tsv(value: &Value) -> Result<Value, EvalError> {
let arr = match value {
Value::Array(a) => a,
_ => {
return Err(type_error(format!(
"@tsv requires array, got {}",
type_name(value)
)));
}
};
let fields: Vec<String> = arr.iter().map(format_tsv_field).collect();
Ok(Value::String(fields.join("\t")))
}
fn type_name(value: &Value) -> &'static str {
match value {
Value::Static(StaticNode::Null) => "null",
Value::Static(StaticNode::Bool(_)) => "boolean",
Value::Static(StaticNode::I64(_) | StaticNode::U64(_) | StaticNode::F64(_)) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
fn format_csv_field(value: &Value) -> String {
match value {
Value::String(s) => {
format!("\"{}\"", s.replace('"', "\"\""))
}
_ => value_to_string(value),
}
}
fn format_tsv_field(value: &Value) -> String {
let s = value_to_string(value);
s.replace('\\', "\\\\")
.replace('\t', "\\t")
.replace('\n', "\\n")
.replace('\r', "\\r")
}
fn value_to_string(value: &Value) -> String {
match value {
Value::String(s) => s.clone(),
Value::Static(StaticNode::Null) => String::new(),
Value::Static(StaticNode::Bool(b)) => b.to_string(),
Value::Static(StaticNode::I64(n)) => n.to_string(),
Value::Static(StaticNode::U64(n)) => n.to_string(),
Value::Static(StaticNode::F64(n)) => n.to_string(),
Value::Array(_) | Value::Object(_) => simd_json::to_string(value).unwrap_or_default(),
}
}
#[cfg(test)]
mod tests {
use crate::filter::builtins::{Builtin, eval};
use simd_json::{OwnedValue as Value, json};
#[test]
fn test_type_null() {
assert_eq!(
eval(&Builtin::Type, &json!(null)).unwrap(),
vec![json!("null")]
);
}
#[test]
fn test_type_boolean() {
assert_eq!(
eval(&Builtin::Type, &json!(true)).unwrap(),
vec![json!("boolean")]
);
assert_eq!(
eval(&Builtin::Type, &json!(false)).unwrap(),
vec![json!("boolean")]
);
}
#[test]
fn test_type_number() {
assert_eq!(
eval(&Builtin::Type, &json!(42)).unwrap(),
vec![json!("number")]
);
assert_eq!(
eval(&Builtin::Type, &json!(3.15)).unwrap(),
vec![json!("number")]
);
}
#[test]
fn test_type_string() {
assert_eq!(
eval(&Builtin::Type, &json!("hello")).unwrap(),
vec![json!("string")]
);
}
#[test]
fn test_type_array() {
assert_eq!(
eval(&Builtin::Type, &json!([1, 2, 3])).unwrap(),
vec![json!("array")]
);
}
#[test]
fn test_type_object() {
assert_eq!(
eval(&Builtin::Type, &json!({"a": 1})).unwrap(),
vec![json!("object")]
);
}
#[test]
fn test_empty() {
let empty: Vec<Value> = vec![];
assert_eq!(
eval(&Builtin::Empty, &json!({"anything": "here"})).unwrap(),
empty
);
assert_eq!(eval(&Builtin::Empty, &json!(null)).unwrap(), empty);
}
#[test]
fn test_not_null() {
assert_eq!(
eval(&Builtin::Not, &json!(null)).unwrap(),
vec![json!(true)]
);
}
#[test]
fn test_not_false() {
assert_eq!(
eval(&Builtin::Not, &json!(false)).unwrap(),
vec![json!(true)]
);
}
#[test]
fn test_not_true() {
assert_eq!(
eval(&Builtin::Not, &json!(true)).unwrap(),
vec![json!(false)]
);
}
#[test]
fn test_not_truthy_values() {
assert_eq!(eval(&Builtin::Not, &json!(0)).unwrap(), vec![json!(false)]);
assert_eq!(eval(&Builtin::Not, &json!("")).unwrap(), vec![json!(false)]);
assert_eq!(eval(&Builtin::Not, &json!([])).unwrap(), vec![json!(false)]);
assert_eq!(eval(&Builtin::Not, &json!({})).unwrap(), vec![json!(false)]);
}
#[test]
fn test_csv_simple() {
assert_eq!(
eval(&Builtin::Csv, &json!(["Alice", 30, "NYC"])).unwrap(),
vec![json!("\"Alice\",30,\"NYC\"")]
);
}
#[test]
fn test_csv_with_comma() {
assert_eq!(
eval(&Builtin::Csv, &json!(["Hello, World"])).unwrap(),
vec![json!("\"Hello, World\"")]
);
}
#[test]
fn test_csv_with_quotes() {
assert_eq!(
eval(&Builtin::Csv, &json!(["She said \"Hi\""])).unwrap(),
vec![json!("\"She said \"\"Hi\"\"\"")]
);
}
#[test]
fn test_csv_with_newline() {
assert_eq!(
eval(&Builtin::Csv, &json!(["Line1\nLine2"])).unwrap(),
vec![json!("\"Line1\nLine2\"")]
);
}
#[test]
fn test_csv_empty_array() {
assert_eq!(eval(&Builtin::Csv, &json!([])).unwrap(), vec![json!("")]);
}
#[test]
fn test_csv_null_value() {
assert_eq!(
eval(&Builtin::Csv, &json!([null, "x"])).unwrap(),
vec![json!(",\"x\"")]
);
}
#[test]
fn test_csv_non_array_error() {
assert!(eval(&Builtin::Csv, &json!("not an array")).is_err());
}
#[test]
fn test_tsv_simple() {
assert_eq!(
eval(&Builtin::Tsv, &json!(["Alice", 30, "NYC"])).unwrap(),
vec![json!("Alice\t30\tNYC")]
);
}
#[test]
fn test_tsv_with_tab() {
assert_eq!(
eval(&Builtin::Tsv, &json!(["a\tb"])).unwrap(),
vec![json!("a\\tb")]
);
}
#[test]
fn test_tsv_with_newline() {
assert_eq!(
eval(&Builtin::Tsv, &json!(["a\nb"])).unwrap(),
vec![json!("a\\nb")]
);
}
#[test]
fn test_tsv_with_backslash() {
assert_eq!(
eval(&Builtin::Tsv, &json!(["a\\b"])).unwrap(),
vec![json!("a\\\\b")]
);
}
#[test]
fn test_tsv_non_array_error() {
assert!(eval(&Builtin::Tsv, &json!(42)).is_err());
}
}