pub fn ternary_to_jq(expr: &str) -> String {
if let Some(pos) = find_top_level_question_mark(expr) {
let (condition, rest) = expr.split_at(pos);
let rest = &rest[1..];
if let Some(colon_pos) = find_matching_colon(rest) {
let true_expr = &rest[..colon_pos].trim();
let false_expr = &rest[colon_pos + 1..].trim();
return format!(
"if {} then {} else {} end",
ternary_to_jq(condition.trim()),
ternary_to_jq(true_expr),
ternary_to_jq(false_expr)
);
}
}
let mut result = String::new();
let mut paren_depth = 0;
let mut paren_start = None;
let mut in_quotes = false;
let mut quote_char = None;
for (i, c) in expr.char_indices() {
match c {
'\'' | '"' => {
if !in_quotes {
in_quotes = true;
quote_char = Some(c);
} else if Some(c) == quote_char {
in_quotes = false;
quote_char = None;
}
}
'(' if !in_quotes => {
if paren_depth == 0 {
paren_start = Some(i);
}
paren_depth += 1;
}
')' if !in_quotes => {
paren_depth -= 1;
if paren_depth == 0
&& let Some(start) = paren_start
{
let content = &expr[start + 1..i];
let processed = ternary_to_jq(content);
result.push('(');
result.push_str(&processed);
result.push(')');
paren_start = None;
continue;
}
}
_ => {}
}
if paren_depth == 0 {
result.push(c);
}
}
if result.is_empty() {
expr.to_string()
} else {
result
}
}
fn find_top_level_question_mark(expr: &str) -> Option<usize> {
let mut in_quotes = false;
let mut quote_char = None;
let mut paren_depth = 0;
let mut bracket_depth = 0;
for (i, c) in expr.char_indices() {
match c {
'\'' | '"' => {
if !in_quotes {
in_quotes = true;
quote_char = Some(c);
} else if Some(c) == quote_char {
in_quotes = false;
quote_char = None;
}
}
'(' => paren_depth += 1,
')' => paren_depth -= 1,
'[' => bracket_depth += 1,
']' => bracket_depth -= 1,
'?' if !in_quotes && paren_depth == 0 && bracket_depth == 0 => {
return Some(i);
}
_ => {}
}
}
None
}
fn find_matching_colon(expr: &str) -> Option<usize> {
let mut in_quotes = false;
let mut quote_char = None;
let mut paren_depth = 0;
let mut bracket_depth = 0;
let mut ternary_depth = 0;
for (i, c) in expr.char_indices() {
match c {
'\'' | '"' => {
if !in_quotes {
in_quotes = true;
quote_char = Some(c);
} else if Some(c) == quote_char {
in_quotes = false;
quote_char = None;
}
}
'(' => paren_depth += 1,
')' => paren_depth -= 1,
'[' => bracket_depth += 1,
']' => bracket_depth -= 1,
'?' if !in_quotes && paren_depth == 0 && bracket_depth == 0 => {
ternary_depth += 1;
}
':' if !in_quotes && paren_depth == 0 && bracket_depth == 0 => {
if ternary_depth == 0 {
return Some(i);
}
ternary_depth -= 1;
}
_ => {}
}
}
None
}
pub fn process_extract_value(value: &str) -> String {
if value.contains('?') && value.contains(':') {
ternary_to_jq(value)
} else {
value.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ternary_basic() {
let input = ".status == 200 ? \"OK\" : \"Error\"";
let expected = "if .status == 200 then \"OK\" else \"Error\" end";
assert_eq!(ternary_to_jq(input), expected);
}
#[test]
fn test_ternary_with_jq() {
let input = ".items | length > 0 ? .items[0] : null";
let expected = "if .items | length > 0 then .items[0] else null end";
assert_eq!(ternary_to_jq(input), expected);
}
#[test]
fn test_ternary_nested() {
let input = ".a > 0 ? (.a > 10 ? \"big\" : \"medium\") : \"small\"";
let expected =
"if .a > 0 then (if .a > 10 then \"big\" else \"medium\" end) else \"small\" end";
assert_eq!(ternary_to_jq(input), expected);
}
#[test]
fn test_not_ternary() {
let input = ".data | .value";
assert_eq!(ternary_to_jq(input), input);
}
#[test]
fn test_ternary_in_quotes() {
let input = ".text == \"a ? b : c\" ? \"match\" : \"no match\"";
let expected = "if .text == \"a ? b : c\" then \"match\" else \"no match\" end";
assert_eq!(ternary_to_jq(input), expected);
}
#[test]
fn test_process_extract_value_ternary() {
let input = ".status == 200 ? \"OK\" : \"Error\"";
let result = process_extract_value(input);
assert!(result.starts_with("if"));
assert!(result.ends_with("end"));
}
#[test]
fn test_process_extract_value_jq() {
let input = "if .status == 200 then \"OK\" else \"Error\" end";
let result = process_extract_value(input);
assert_eq!(result, input);
}
#[test]
fn test_process_extract_value_simple() {
let input = ".value";
let result = process_extract_value(input);
assert_eq!(result, input);
}
#[test]
fn test_ternary_nested_deep() {
let input =
".a > 0 ? (.a > 10 ? (.a > 20 ? \"very big\" : \"big\") : \"medium\") : \"small\"";
let result = ternary_to_jq(input);
assert!(result.contains("if .a > 0 then"));
assert!(result.contains("if .a > 10 then"));
assert!(result.contains("if .a > 20 then"));
assert_eq!(result.matches(" end").count(), 3);
}
#[test]
fn test_ternary_multiple_sequential() {
let input = ".a > 0 ? .b > 0 ? \"both positive\" : \"a only\" : \"none\"";
let result = ternary_to_jq(input);
assert!(result.starts_with("if .a > 0 then"));
assert!(result.ends_with("end"));
println!("Sequential: {}", result);
}
#[test]
fn test_ternary_jq_validation() {
let input = ".a > 0 ? (.a > 10 ? \"big\" : \"medium\") : \"small\"";
let result = ternary_to_jq(input);
assert_eq!(result.matches("if ").count(), 2);
assert_eq!(result.matches(" then ").count(), 2);
assert_eq!(result.matches(" else ").count(), 2);
assert_eq!(result.matches(" end").count(), 2);
println!("Nested jq: {}", result);
}
#[test]
fn test_ternary_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = ".status == 200 ? \"OK\" : \"Error\"";
let jq = ternary_to_jq(input);
let response = json!({"status": 200});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("OK"));
let response = json!({"status": 500});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("Error"));
}
#[test]
fn test_ternary_nested_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = ".a > 0 ? (.a > 10 ? \"big\" : \"medium\") : \"small\"";
let jq = ternary_to_jq(input);
let response = json!({"a": 5});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("medium"));
let response = json!({"a": 15});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("big"));
let response = json!({"a": -5});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("small"));
}
#[test]
fn test_ternary_sequential_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = ".a > 0 ? .b > 0 ? \"both positive\" : \"a only\" : \"none\"";
let jq = ternary_to_jq(input);
let response = json!({"a": 1, "b": 1});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("both positive"));
let response = json!({"a": 1, "b": -1});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("a only"));
let response = json!({"a": -1, "b": 1});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("none"));
}
#[test]
fn test_ternary_with_parentheses_preserved() {
let input = ".a > 0 ? (.a > 10 ? \"big\" : \"medium\") : \"small\"";
let result = ternary_to_jq(input);
assert!(result.contains("(if .a > 10 then"));
assert!(result.contains("end)"));
}
#[test]
fn test_ternary_deep_nested_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input =
".a > 0 ? (.a > 10 ? (.a > 20 ? \"very big\" : \"big\") : \"medium\") : \"small\"";
let jq = ternary_to_jq(input);
let response = json!({"a": 25});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("very big"));
let response = json!({"a": 15});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("big"));
let response = json!({"a": 5});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("medium"));
let response = json!({"a": -5});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("small"));
}
#[test]
fn test_ternary_with_jq_pipe_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = ".items | length > 0 ? .items[0] : null";
let jq = ternary_to_jq(input);
let response = json!({"items": ["first", "second"]});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("first"));
let response = json!({"items": []});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!(null));
}
#[test]
fn test_ternary_string_comparison_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = ".status == \"ok\" ? \"success\" : \"failure\"";
let jq = ternary_to_jq(input);
let response = json!({"status": "ok"});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("success"));
let response = json!({"status": "error"});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("failure"));
}
#[test]
fn test_ternary_numeric_comparison_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = ".score >= 90 ? \"A\" : (.score >= 80 ? \"B\" : \"C\")";
let jq = ternary_to_jq(input);
let response = json!({"score": 95});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("A"));
let response = json!({"score": 85});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("B"));
let response = json!({"score": 70});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("C"));
}
#[test]
fn test_ternary_nested_in_expression_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = "(.a > 0 ? .a : 0) > 10";
let jq = ternary_to_jq(input);
let response = json!({"a": 15});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!(true));
let response = json!({"a": 5});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!(false));
let response = json!({"a": -5});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!(false));
}
#[test]
fn test_ternary_with_null_check_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = ".value != null ? .value : \"default\"";
let jq = ternary_to_jq(input);
let response = json!({"value": "custom"});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("custom"));
let response = json!({"value": null});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("default"));
}
#[test]
fn test_ternary_complex_boolean_logic_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = "(.a > 0 and .b > 0) ? \"both positive\" : \"at least one non-positive\"";
let jq = ternary_to_jq(input);
let response = json!({"a": 1, "b": 2});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("both positive"));
let response = json!({"a": -1, "b": 2});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("at least one non-positive"));
}
#[test]
fn test_ternary_extract_pattern_executes_in_jaq() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = ".status == 200 ? .access_token : .refresh_token";
let jq = ternary_to_jq(input);
let response = json!({"status": 200, "access_token": "abc123", "refresh_token": "xyz789"});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("abc123"));
let response = json!({"status": 401, "access_token": "abc123", "refresh_token": "xyz789"});
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], json!("xyz789"));
}
#[test]
fn test_ternary_multiple_sequential_full_execution() {
use crate::assert::engine::AssertionEngine;
use serde_json::json;
let engine = AssertionEngine::new();
let input = ".a > 0 ? .b > 0 ? \"both positive\" : \"a only\" : \"none\"";
let jq = ternary_to_jq(input);
assert!(jq.starts_with("if .a > 0 then"));
assert!(jq.contains("if .b > 0 then"));
assert_eq!(jq.matches(" end").count(), 2);
let test_cases = [
(json!({"a": 1, "b": 1}), json!("both positive")),
(json!({"a": 1, "b": -1}), json!("a only")),
(json!({"a": -1, "b": 1}), json!("none")),
(json!({"a": -1, "b": -1}), json!("none")),
];
for (response, expected) in test_cases {
let results = engine.query(&jq, &response).unwrap();
assert_eq!(results.len(), 1, "Failed for input: {:?}", response);
assert_eq!(results[0], expected, "Failed for input: {:?}", response);
}
}
}