1use serde_json::Value;
2use crate::Error;
3
4pub fn apply_string_operation(value: &Value, operation: &str) -> Result<Value, Error> {
6 match operation {
7 "upper" => {
8 let string_val = extract_string_value(value)?;
9 Ok(Value::String(string_val.to_uppercase()))
10 },
11 "lower" => {
12 let string_val = extract_string_value(value)?;
13 Ok(Value::String(string_val.to_lowercase()))
14 },
15 "trim" => {
16 let string_val = extract_string_value(value)?;
17 Ok(Value::String(string_val.trim().to_string()))
18 },
19 "trim_start" => {
20 let string_val = extract_string_value(value)?;
21 Ok(Value::String(string_val.trim_start().to_string()))
22 },
23 "trim_end" => {
24 let string_val = extract_string_value(value)?;
25 Ok(Value::String(string_val.trim_end().to_string()))
26 },
27 "length" => {
28 let string_val = extract_string_value(value)?;
29 Ok(Value::Number(serde_json::Number::from(string_val.chars().count())))
30 },
31 "reverse" => {
32 let string_val = extract_string_value(value)?;
33 Ok(Value::String(string_val.chars().rev().collect()))
34 },
35 op if op.starts_with("contains(") && op.ends_with(")") => {
37 let string_val = extract_string_value(value)?;
38 let pattern = extract_string_argument(op)?;
39
40 if pattern.contains('|') {
42 apply_contains_or_condition(string_val, &pattern)
43 } else {
44 Ok(Value::Bool(string_val.contains(&pattern)))
45 }
46 },
47
48 op if op.starts_with("starts_with(") && op.ends_with(")") => {
50 let string_val = extract_string_value(value)?;
51 let pattern = extract_string_argument(op)?;
52
53 if pattern.contains('|') {
54 apply_starts_with_or_condition(string_val, &pattern)
55 } else {
56 Ok(Value::Bool(string_val.starts_with(&pattern)))
57 }
58 },
59
60 op if op.starts_with("ends_with(") && op.ends_with(")") => {
62 let string_val = extract_string_value(value)?;
63 let pattern = extract_string_argument(op)?;
64
65 if pattern.contains('|') {
66 apply_ends_with_or_condition(string_val, &pattern)
67 } else {
68 Ok(Value::Bool(string_val.ends_with(&pattern)))
69 }
70 },
71 op if op.starts_with("replace(") && op.ends_with(")") => {
87 let string_val = extract_string_value(value)?;
88 let (old, new) = extract_replace_arguments(op)?;
89 Ok(Value::String(string_val.replace(&old, &new)))
90 },
91 op if op.starts_with("substring(") && op.ends_with(")") => {
92 let string_val = extract_string_value(value)?;
93 let (start, length) = extract_substring_arguments(op)?;
94 let result = extract_substring(string_val, start, length)?;
95 Ok(Value::String(result))
96 },
97 op if op.starts_with("split(") && op.contains(")[") => {
98 apply_split_with_slice_range(value, op)
99 },
100 op if op.starts_with("split(") && op.contains(")[") => {
101 apply_split_with_index(value, op)
103 },
104 op if op.starts_with("split(") && op.ends_with(")") => {
105 let string_val = extract_string_value(value)?;
106 let delimiter = extract_string_argument(op)?;
107 let parts: Vec<Value> = string_val
108 .split(&delimiter)
109 .map(|s| Value::String(s.to_string()))
110 .collect();
111 Ok(Value::Array(parts))
112 },
113 op if op.starts_with("join(") && op.ends_with(")") => {
114 apply_join_operation(value, op)
116 },
117 _ => Err(Error::StringOperation(format!("Unknown string operation: {}", operation))),
118 }
119}
120
121fn apply_split_with_index(value: &Value, operation: &str) -> Result<Value, Error> {
123 let split_end = operation.find(")[").ok_or_else(|| {
125 Error::StringOperation("Invalid split with index format".to_string())
126 })?;
127
128 let bracket_start = split_end + 2; let bracket_end = operation.len() - 1; if !operation.ends_with(']') {
132 return Err(Error::StringOperation("Missing closing bracket in split index".to_string()));
133 }
134
135 let split_part = &operation[..split_end + 1]; let index_part = &operation[bracket_start..bracket_end]; let string_val = extract_string_value(value)?;
141 let delimiter = extract_string_argument(split_part)?;
142 let parts: Vec<&str> = string_val.split(&delimiter).collect();
143
144 let index = index_part.parse::<usize>().map_err(|_| {
146 Error::StringOperation(format!("Invalid array index: {}", index_part))
147 })?;
148
149 if let Some(part) = parts.get(index) {
151 Ok(Value::String(part.to_string()))
152 } else {
153 Ok(Value::String("".to_string()))
155 }
156}
157
158fn extract_string_value(value: &Value) -> Result<&str, Error> {
160 match value {
161 Value::String(s) => Ok(s),
162 _ => Err(Error::StringOperation(
163 format!("String operations can only be applied to string values, got: {}", get_type_name(value))
164 )),
165 }
166}
167
168fn apply_join_operation(value: &Value, operation: &str) -> Result<Value, Error> {
170 if let Value::Array(arr) = value {
171 let delimiter = extract_string_argument(operation)?;
172
173 let string_parts: Result<Vec<String>, Error> = arr
174 .iter()
175 .map(|v| match v {
176 Value::String(s) => Ok(s.clone()),
177 Value::Number(n) => Ok(n.to_string()),
178 Value::Bool(b) => Ok(b.to_string()),
179 Value::Null => Ok("null".to_string()),
180 _ => Err(Error::StringOperation("Cannot join non-primitive values".to_string())),
181 })
182 .collect();
183
184 let parts = string_parts?;
185 Ok(Value::String(parts.join(&delimiter)))
186 } else {
187 Err(Error::StringOperation("join can only be applied to arrays".to_string()))
188 }
189}
190
191fn get_type_name(value: &Value) -> &'static str {
193 match value {
194 Value::String(_) => "string",
195 Value::Number(_) => "number",
196 Value::Bool(_) => "boolean",
197 Value::Array(_) => "array",
198 Value::Object(_) => "object",
199 Value::Null => "null",
200 }
201}
202
203fn extract_string_argument(operation: &str) -> Result<String, Error> {
205 let start_pos = operation.find('(').ok_or_else(|| {
206 Error::StringOperation("Missing opening parenthesis".to_string())
207 })? + 1;
208 let end_pos = operation.rfind(')').ok_or_else(|| {
209 Error::StringOperation("Missing closing parenthesis".to_string())
210 })?;
211
212 if start_pos >= end_pos {
213 return Err(Error::StringOperation("Invalid argument format".to_string()));
214 }
215
216 let arg = &operation[start_pos..end_pos];
217
218 let cleaned = if (arg.starts_with('"') && arg.ends_with('"')) ||
220 (arg.starts_with('\'') && arg.ends_with('\'')) {
221 &arg[1..arg.len()-1]
222 } else {
223 arg
224 };
225
226 Ok(cleaned.to_string())
227}
228
229fn extract_replace_arguments(operation: &str) -> Result<(String, String), Error> {
231 let start_pos = operation.find('(').ok_or_else(|| {
232 Error::StringOperation("Missing opening parenthesis".to_string())
233 })? + 1;
234 let end_pos = operation.rfind(')').ok_or_else(|| {
235 Error::StringOperation("Missing closing parenthesis".to_string())
236 })?;
237
238 let args_str = &operation[start_pos..end_pos];
239
240 let parts: Vec<&str> = args_str.split(',').map(|s| s.trim()).collect();
242 if parts.len() != 2 {
243 return Err(Error::StringOperation("replace requires exactly 2 arguments".to_string()));
244 }
245
246 let old = clean_string_argument(parts[0])?;
247 let new = clean_string_argument(parts[1])?;
248
249 Ok((old, new))
250}
251
252fn extract_substring_arguments(operation: &str) -> Result<(usize, Option<usize>), Error> {
254 let start_pos = operation.find('(').ok_or_else(|| {
255 Error::StringOperation("Missing opening parenthesis".to_string())
256 })? + 1;
257 let end_pos = operation.rfind(')').ok_or_else(|| {
258 Error::StringOperation("Missing closing parenthesis".to_string())
259 })?;
260
261 let args_str = &operation[start_pos..end_pos];
262
263 let parts: Vec<&str> = args_str.split(',').map(|s| s.trim()).collect();
265
266 let start = parts[0].parse::<usize>().map_err(|_| {
267 Error::StringOperation("Invalid start position for substring".to_string())
268 })?;
269
270 let length = if parts.len() > 1 {
271 Some(parts[1].parse::<usize>().map_err(|_| {
272 Error::StringOperation("Invalid length for substring".to_string())
273 })?)
274 } else {
275 None
276 };
277
278 Ok((start, length))
279}
280
281fn clean_string_argument(arg: &str) -> Result<String, Error> {
283 let cleaned = if (arg.starts_with('"') && arg.ends_with('"')) ||
284 (arg.starts_with('\'') && arg.ends_with('\'')) {
285 &arg[1..arg.len()-1]
286 } else {
287 arg
288 };
289
290 Ok(cleaned.to_string())
291}
292
293fn extract_substring(text: &str, start: usize, length: Option<usize>) -> Result<String, Error> {
295 let chars: Vec<char> = text.chars().collect();
296
297 if start >= chars.len() {
298 return Ok("".to_string());
299 }
300
301 let end = match length {
302 Some(len) => std::cmp::min(start + len, chars.len()),
303 None => chars.len(),
304 };
305
306 Ok(chars[start..end].iter().collect())
307}
308
309pub fn apply_string_pipeline(value: &Value, operations: &[&str]) -> Result<Value, Error> {
311 let mut current_value = value.clone();
312
313 for operation in operations {
314 current_value = apply_string_operation(¤t_value, operation)?;
315 }
316
317 Ok(current_value)
318}
319
320pub fn apply_operation_to_multiple_fields(item: &Value, field_paths: &[&str], operation: &str) -> Result<Value, Error> {
322 let mut updated_item = item.clone();
323
324 for field_path in field_paths {
326 let field_value = extract_field_value_from_item(item, field_path)?;
328
329 let transformed_value = apply_string_operation(&field_value, operation)?;
331
332 updated_item = update_field_in_item(updated_item, field_path, transformed_value)?;
334 }
335
336 Ok(updated_item)
337}
338
339fn extract_field_value_from_item(item: &Value, field_path: &str) -> Result<Value, Error> {
341 if field_path == "." {
342 return Ok(item.clone());
343 }
344
345 if !field_path.starts_with('.') {
346 return Err(Error::StringOperation(format!("Field path must start with '.': {}", field_path)));
347 }
348
349 let field_name = &field_path[1..]; if let Some(value) = item.get(field_name) {
352 Ok(value.clone())
353 } else {
354 Err(Error::StringOperation(format!("Field '{}' not found", field_name)))
355 }
356}
357
358fn update_field_in_item(item: Value, field_path: &str, new_value: Value) -> Result<Value, Error> {
360 if field_path == "." {
361 return Ok(new_value);
363 }
364
365 if !field_path.starts_with('.') {
366 return Err(Error::StringOperation(format!("Field path must start with '.': {}", field_path)));
367 }
368
369 let field_name = &field_path[1..]; if let Value::Object(mut obj) = item {
372 obj.insert(field_name.to_string(), new_value);
373 Ok(Value::Object(obj))
374 } else {
375 let mut new_obj = serde_json::Map::new();
377 new_obj.insert(field_name.to_string(), new_value);
378 Ok(Value::Object(new_obj))
379 }
380}
381
382fn apply_split_with_slice_range(value: &Value, operation: &str) -> Result<Value, Error> {
383 let split_end = operation.find(")[").ok_or_else(|| {
385 Error::StringOperation("Invalid split with slice format".to_string())
386 })?;
387
388 let bracket_start = split_end + 2; let bracket_end = operation.len() - 1; if !operation.ends_with(']') {
392 return Err(Error::StringOperation("Missing closing bracket in split slice".to_string()));
393 }
394
395 let split_part = &operation[..split_end + 1]; let slice_part = &operation[bracket_start..bracket_end]; let string_val = extract_string_value(value)?;
401 let delimiter = extract_string_argument(split_part)?;
402 let parts: Vec<String> = string_val.split(&delimiter).map(|s| s.to_string()).collect();
403
404 if slice_part.contains(':') {
406 let (start, end) = parse_slice_notation_for_split(slice_part, parts.len())?;
407 let sliced_parts = apply_slice_to_string_array(&parts, start, end);
408
409 let result: Vec<Value> = sliced_parts
410 .into_iter()
411 .map(|s| Value::String(s.to_string()))
412 .collect();
413
414 Ok(Value::Array(result))
415 } else {
416 let index = slice_part.parse::<usize>().map_err(|_| {
418 Error::StringOperation(format!("Invalid array index: {}", slice_part))
419 })?;
420
421 if let Some(part) = parts.get(index) {
422 Ok(Value::String(part.to_string()))
423 } else {
424 Ok(Value::String("".to_string())) }
426 }
427}
428
429fn parse_slice_notation_for_split(slice_str: &str, array_len: usize) -> Result<(Option<usize>, Option<usize>), Error> {
431 let parts: Vec<&str> = slice_str.split(':').collect();
432 if parts.len() != 2 {
433 return Err(Error::StringOperation("Invalid slice format, expected start:end".to_string()));
434 }
435
436 let start = if parts[0].is_empty() {
437 None
438 } else if parts[0].starts_with('-') {
439 let neg_idx = parts[0][1..].parse::<usize>().map_err(|_| {
441 Error::StringOperation(format!("Invalid negative index: {}", parts[0]))
442 })?;
443 Some(array_len.saturating_sub(neg_idx))
444 } else {
445 Some(parts[0].parse::<usize>().map_err(|_| {
446 Error::StringOperation(format!("Invalid start index: {}", parts[0]))
447 })?)
448 };
449
450 let end = if parts[1].is_empty() {
451 None
452 } else if parts[1].starts_with('-') {
453 let neg_idx = parts[1][1..].parse::<usize>().map_err(|_| {
455 Error::StringOperation(format!("Invalid negative index: {}", parts[1]))
456 })?;
457 Some(array_len.saturating_sub(neg_idx))
458 } else {
459 Some(parts[1].parse::<usize>().map_err(|_| {
460 Error::StringOperation(format!("Invalid end index: {}", parts[1]))
461 })?)
462 };
463
464 Ok((start, end))
465}
466
467fn apply_slice_to_string_array(array: &[String], start: Option<usize>, end: Option<usize>) -> Vec<String> {
469 let len = array.len();
470
471 let start_idx = start.unwrap_or(0);
472 let end_idx = end.unwrap_or(len);
473
474 let start_idx = start_idx.min(len);
476 let end_idx = end_idx.min(len);
477
478 if start_idx >= end_idx {
479 return Vec::new();
480 }
481
482 array[start_idx..end_idx].to_vec()
483}
484
485fn apply_contains_or_condition(text: &str, pattern: &str) -> Result<Value, Error> {
487 let patterns: Vec<&str> = pattern.split('|').map(|p| p.trim()).collect();
488 let result = patterns.iter().any(|p| text.contains(p));
489 Ok(Value::Bool(result))
490}
491
492fn apply_starts_with_or_condition(text: &str, pattern: &str) -> Result<Value, Error> {
494 let patterns: Vec<&str> = pattern.split('|').map(|p| p.trim()).collect();
495 let result = patterns.iter().any(|p| text.starts_with(p));
496 Ok(Value::Bool(result))
497}
498
499fn apply_ends_with_or_condition(text: &str, pattern: &str) -> Result<Value, Error> {
501 let patterns: Vec<&str> = pattern.split('|').map(|p| p.trim()).collect();
502 let result = patterns.iter().any(|p| text.ends_with(p));
503 Ok(Value::Bool(result))
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn test_basic_string_operations() {
512 let value = Value::String("Hello World".to_string());
513
514 let result = apply_string_operation(&value, "upper").unwrap();
516 assert_eq!(result, Value::String("HELLO WORLD".to_string()));
517
518 let result = apply_string_operation(&value, "lower").unwrap();
520 assert_eq!(result, Value::String("hello world".to_string()));
521
522 let padded = Value::String(" hello ".to_string());
524 let result = apply_string_operation(&padded, "trim").unwrap();
525 assert_eq!(result, Value::String("hello".to_string()));
526
527 let result = apply_string_operation(&value, "length").unwrap();
529 assert_eq!(result, Value::Number(11.into()));
530
531 let result = apply_string_operation(&value, "reverse").unwrap();
533 assert_eq!(result, Value::String("dlroW olleH".to_string()));
534 }
535
536 #[test]
537 fn test_string_search_operations() {
538 let value = Value::String("Hello World".to_string());
539
540 let result = apply_string_operation(&value, r#"contains("World")"#).unwrap();
542 assert_eq!(result, Value::Bool(true));
543
544 let result = apply_string_operation(&value, r#"contains("xyz")"#).unwrap();
545 assert_eq!(result, Value::Bool(false));
546
547 let result = apply_string_operation(&value, r#"starts_with("Hello")"#).unwrap();
549 assert_eq!(result, Value::Bool(true));
550
551 let result = apply_string_operation(&value, r#"ends_with("World")"#).unwrap();
553 assert_eq!(result, Value::Bool(true));
554 }
555
556 #[test]
557 fn test_replace_operation() {
558 let value = Value::String("Hello World".to_string());
559
560 let result = apply_string_operation(&value, r#"replace("World", "Rust")"#).unwrap();
561 assert_eq!(result, Value::String("Hello Rust".to_string()));
562 }
563
564 #[test]
565 fn test_substring_operation() {
566 let value = Value::String("Hello World".to_string());
567
568 let result = apply_string_operation(&value, "substring(0, 5)").unwrap();
570 assert_eq!(result, Value::String("Hello".to_string()));
571
572 let result = apply_string_operation(&value, "substring(6)").unwrap();
574 assert_eq!(result, Value::String("World".to_string()));
575 }
576
577 #[test]
578 fn test_split_operation() {
579 let value = Value::String("apple,banana,cherry".to_string());
580
581 let result = apply_string_operation(&value, r#"split(",")"#).unwrap();
582 if let Value::Array(arr) = result {
583 assert_eq!(arr.len(), 3);
584 assert_eq!(arr[0], Value::String("apple".to_string()));
585 assert_eq!(arr[1], Value::String("banana".to_string()));
586 assert_eq!(arr[2], Value::String("cherry".to_string()));
587 } else {
588 panic!("Expected array result");
589 }
590 }
591
592 #[test]
593 fn test_join_operation() {
594 let value = Value::Array(vec![
595 Value::String("apple".to_string()),
596 Value::String("banana".to_string()),
597 Value::String("cherry".to_string()),
598 ]);
599
600 let result = apply_string_operation(&value, r#"join(", ")"#).unwrap();
601 assert_eq!(result, Value::String("apple, banana, cherry".to_string()));
602 }
603
604 #[test]
605 fn test_string_pipeline() {
606 let value = Value::String(" Hello World ".to_string());
607 let operations = vec!["trim", "upper"];
608
609 let result = apply_string_pipeline(&value, &operations).unwrap();
610 assert_eq!(result, Value::String("HELLO WORLD".to_string()));
611 }
612
613 #[test]
614 fn test_non_string_error() {
615 let value = Value::Number(42.into());
616 let result = apply_string_operation(&value, "upper");
617 assert!(result.is_err());
618 }
619}