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_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)"));
}
}