1pub fn ternary_to_jq(expr: &str) -> String {
7 if let Some(pos) = find_top_level_question_mark(expr) {
9 let (condition, rest) = expr.split_at(pos);
10 let rest = &rest[1..]; if let Some(colon_pos) = find_matching_colon(rest) {
13 let true_expr = &rest[..colon_pos].trim();
14 let false_expr = &rest[colon_pos + 1..].trim();
15
16 return format!(
18 "if {} then {} else {} end",
19 ternary_to_jq(condition.trim()),
20 ternary_to_jq(true_expr),
21 ternary_to_jq(false_expr)
22 );
23 }
24 }
25
26 let mut result = String::new();
29 let mut paren_depth = 0;
30 let mut paren_start = None;
31 let mut in_quotes = false;
32 let mut quote_char = None;
33
34 for (i, c) in expr.char_indices() {
35 match c {
36 '\'' | '"' => {
37 if !in_quotes {
38 in_quotes = true;
39 quote_char = Some(c);
40 } else if Some(c) == quote_char {
41 in_quotes = false;
42 quote_char = None;
43 }
44 }
45 '(' if !in_quotes => {
46 if paren_depth == 0 {
47 paren_start = Some(i);
48 }
49 paren_depth += 1;
50 }
51 ')' if !in_quotes => {
52 paren_depth -= 1;
53 if paren_depth == 0
54 && let Some(start) = paren_start
55 {
56 let content = &expr[start + 1..i];
58 let processed = ternary_to_jq(content);
59 result.push('(');
60 result.push_str(&processed);
61 result.push(')');
62 paren_start = None;
63 continue;
64 }
65 }
66 _ => {}
67 }
68
69 if paren_depth == 0 {
70 result.push(c);
71 }
72 }
73
74 if result.is_empty() {
75 expr.to_string()
76 } else {
77 result
78 }
79}
80
81fn find_top_level_question_mark(expr: &str) -> Option<usize> {
83 let mut in_quotes = false;
84 let mut quote_char = None;
85 let mut paren_depth = 0;
86 let mut bracket_depth = 0;
87
88 for (i, c) in expr.char_indices() {
89 match c {
90 '\'' | '"' => {
91 if !in_quotes {
92 in_quotes = true;
93 quote_char = Some(c);
94 } else if Some(c) == quote_char {
95 in_quotes = false;
96 quote_char = None;
97 }
98 }
99 '(' => paren_depth += 1,
100 ')' => paren_depth -= 1,
101 '[' => bracket_depth += 1,
102 ']' => bracket_depth -= 1,
103 '?' if !in_quotes && paren_depth == 0 && bracket_depth == 0 => {
104 return Some(i);
105 }
106 _ => {}
107 }
108 }
109
110 None
111}
112
113fn find_matching_colon(expr: &str) -> Option<usize> {
116 let mut in_quotes = false;
117 let mut quote_char = None;
118 let mut paren_depth = 0;
119 let mut bracket_depth = 0;
120 let mut ternary_depth = 0; for (i, c) in expr.char_indices() {
123 match c {
124 '\'' | '"' => {
125 if !in_quotes {
126 in_quotes = true;
127 quote_char = Some(c);
128 } else if Some(c) == quote_char {
129 in_quotes = false;
130 quote_char = None;
131 }
132 }
133 '(' => paren_depth += 1,
134 ')' => paren_depth -= 1,
135 '[' => bracket_depth += 1,
136 ']' => bracket_depth -= 1,
137 '?' if !in_quotes && paren_depth == 0 && bracket_depth == 0 => {
138 ternary_depth += 1;
139 }
140 ':' if !in_quotes && paren_depth == 0 && bracket_depth == 0 => {
141 if ternary_depth == 0 {
142 return Some(i);
143 }
144 ternary_depth -= 1;
145 }
146 _ => {}
147 }
148 }
149
150 None
151}
152
153pub fn process_extract_value(value: &str) -> String {
155 if value.contains('?') && value.contains(':') {
157 ternary_to_jq(value)
158 } else {
159 value.to_string()
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_ternary_basic() {
169 let input = ".status == 200 ? \"OK\" : \"Error\"";
170 let expected = "if .status == 200 then \"OK\" else \"Error\" end";
171 assert_eq!(ternary_to_jq(input), expected);
172 }
173
174 #[test]
175 fn test_ternary_with_jq() {
176 let input = ".items | length > 0 ? .items[0] : null";
177 let expected = "if .items | length > 0 then .items[0] else null end";
178 assert_eq!(ternary_to_jq(input), expected);
179 }
180
181 #[test]
182 fn test_ternary_nested() {
183 let input = ".a > 0 ? (.a > 10 ? \"big\" : \"medium\") : \"small\"";
185 let expected =
186 "if .a > 0 then (if .a > 10 then \"big\" else \"medium\" end) else \"small\" end";
187 assert_eq!(ternary_to_jq(input), expected);
188 }
189
190 #[test]
191 fn test_not_ternary() {
192 let input = ".data | .value";
193 assert_eq!(ternary_to_jq(input), input);
194 }
195
196 #[test]
197 fn test_ternary_in_quotes() {
198 let input = ".text == \"a ? b : c\" ? \"match\" : \"no match\"";
199 let expected = "if .text == \"a ? b : c\" then \"match\" else \"no match\" end";
200 assert_eq!(ternary_to_jq(input), expected);
201 }
202
203 #[test]
204 fn test_process_extract_value_ternary() {
205 let input = ".status == 200 ? \"OK\" : \"Error\"";
206 let result = process_extract_value(input);
207 assert!(result.starts_with("if"));
208 assert!(result.ends_with("end"));
209 }
210
211 #[test]
212 fn test_process_extract_value_jq() {
213 let input = "if .status == 200 then \"OK\" else \"Error\" end";
214 let result = process_extract_value(input);
215 assert_eq!(result, input);
216 }
217
218 #[test]
219 fn test_process_extract_value_simple() {
220 let input = ".value";
221 let result = process_extract_value(input);
222 assert_eq!(result, input);
223 }
224
225 #[test]
226 fn test_ternary_nested_deep() {
227 let input =
229 ".a > 0 ? (.a > 10 ? (.a > 20 ? \"very big\" : \"big\") : \"medium\") : \"small\"";
230 let result = ternary_to_jq(input);
231 assert!(result.contains("if .a > 0 then"));
232 assert!(result.contains("if .a > 10 then"));
233 assert!(result.contains("if .a > 20 then"));
234 assert_eq!(result.matches(" end").count(), 3);
235 }
236
237 #[test]
238 fn test_ternary_multiple_sequential() {
239 let input = ".a > 0 ? .b > 0 ? \"both positive\" : \"a only\" : \"none\"";
241 let result = ternary_to_jq(input);
242 assert!(result.starts_with("if .a > 0 then"));
244 assert!(result.ends_with("end"));
245 println!("Sequential: {}", result);
246 }
247
248 #[test]
249 fn test_ternary_jq_validation() {
250 let input = ".a > 0 ? (.a > 10 ? \"big\" : \"medium\") : \"small\"";
252 let result = ternary_to_jq(input);
253 assert_eq!(result.matches("if ").count(), 2);
255 assert_eq!(result.matches(" then ").count(), 2);
256 assert_eq!(result.matches(" else ").count(), 2);
257 assert_eq!(result.matches(" end").count(), 2);
258 println!("Nested jq: {}", result);
259 }
260
261 #[test]
262 fn test_ternary_with_parentheses_preserved() {
263 let input = ".a > 0 ? (.a > 10 ? \"big\" : \"medium\") : \"small\"";
265 let result = ternary_to_jq(input);
266 assert!(result.contains("(if .a > 10 then"));
267 assert!(result.contains("end)"));
268 }
269}