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(")") => {
36 let string_val = extract_string_value(value)?;
37 let pattern = extract_string_argument(op)?;
38 Ok(Value::Bool(string_val.contains(&pattern)))
39 },
40 op if op.starts_with("starts_with(") && op.ends_with(")") => {
41 let string_val = extract_string_value(value)?;
42 let pattern = extract_string_argument(op)?;
43 Ok(Value::Bool(string_val.starts_with(&pattern)))
44 },
45 op if op.starts_with("ends_with(") && op.ends_with(")") => {
46 let string_val = extract_string_value(value)?;
47 let pattern = extract_string_argument(op)?;
48 Ok(Value::Bool(string_val.ends_with(&pattern)))
49 },
50 op if op.starts_with("replace(") && op.ends_with(")") => {
51 let string_val = extract_string_value(value)?;
52 let (old, new) = extract_replace_arguments(op)?;
53 Ok(Value::String(string_val.replace(&old, &new)))
54 },
55 op if op.starts_with("substring(") && op.ends_with(")") => {
56 let string_val = extract_string_value(value)?;
57 let (start, length) = extract_substring_arguments(op)?;
58 let result = extract_substring(string_val, start, length)?;
59 Ok(Value::String(result))
60 },
61 op if op.starts_with("split(") && op.ends_with(")") => {
62 let string_val = extract_string_value(value)?;
63 let delimiter = extract_string_argument(op)?;
64 let parts: Vec<Value> = string_val
65 .split(&delimiter)
66 .map(|s| Value::String(s.to_string()))
67 .collect();
68 Ok(Value::Array(parts))
69 },
70 op if op.starts_with("join(") && op.ends_with(")") => {
71 apply_join_operation(value, op)
73 },
74 _ => Err(Error::StringOperation(format!("Unknown string operation: {}", operation))),
75 }
76}
77
78fn extract_string_value(value: &Value) -> Result<&str, Error> {
80 match value {
81 Value::String(s) => Ok(s),
82 _ => Err(Error::StringOperation(
83 format!("String operations can only be applied to string values, got: {}", get_type_name(value))
84 )),
85 }
86}
87
88fn apply_join_operation(value: &Value, operation: &str) -> Result<Value, Error> {
90 if let Value::Array(arr) = value {
91 let delimiter = extract_string_argument(operation)?;
92
93 let string_parts: Result<Vec<String>, Error> = arr
94 .iter()
95 .map(|v| match v {
96 Value::String(s) => Ok(s.clone()),
97 Value::Number(n) => Ok(n.to_string()),
98 Value::Bool(b) => Ok(b.to_string()),
99 Value::Null => Ok("null".to_string()),
100 _ => Err(Error::StringOperation("Cannot join non-primitive values".to_string())),
101 })
102 .collect();
103
104 let parts = string_parts?;
105 Ok(Value::String(parts.join(&delimiter)))
106 } else {
107 Err(Error::StringOperation("join can only be applied to arrays".to_string()))
108 }
109}
110
111fn get_type_name(value: &Value) -> &'static str {
113 match value {
114 Value::String(_) => "string",
115 Value::Number(_) => "number",
116 Value::Bool(_) => "boolean",
117 Value::Array(_) => "array",
118 Value::Object(_) => "object",
119 Value::Null => "null",
120 }
121}
122
123fn extract_string_argument(operation: &str) -> Result<String, Error> {
125 let start_pos = operation.find('(').ok_or_else(|| {
126 Error::StringOperation("Missing opening parenthesis".to_string())
127 })? + 1;
128 let end_pos = operation.rfind(')').ok_or_else(|| {
129 Error::StringOperation("Missing closing parenthesis".to_string())
130 })?;
131
132 if start_pos >= end_pos {
133 return Err(Error::StringOperation("Invalid argument format".to_string()));
134 }
135
136 let arg = &operation[start_pos..end_pos];
137
138 let cleaned = if (arg.starts_with('"') && arg.ends_with('"')) ||
140 (arg.starts_with('\'') && arg.ends_with('\'')) {
141 &arg[1..arg.len()-1]
142 } else {
143 arg
144 };
145
146 Ok(cleaned.to_string())
147}
148
149fn extract_replace_arguments(operation: &str) -> Result<(String, String), Error> {
151 let start_pos = operation.find('(').ok_or_else(|| {
152 Error::StringOperation("Missing opening parenthesis".to_string())
153 })? + 1;
154 let end_pos = operation.rfind(')').ok_or_else(|| {
155 Error::StringOperation("Missing closing parenthesis".to_string())
156 })?;
157
158 let args_str = &operation[start_pos..end_pos];
159
160 let parts: Vec<&str> = args_str.split(',').map(|s| s.trim()).collect();
162 if parts.len() != 2 {
163 return Err(Error::StringOperation("replace requires exactly 2 arguments".to_string()));
164 }
165
166 let old = clean_string_argument(parts[0])?;
167 let new = clean_string_argument(parts[1])?;
168
169 Ok((old, new))
170}
171
172fn extract_substring_arguments(operation: &str) -> Result<(usize, Option<usize>), Error> {
174 let start_pos = operation.find('(').ok_or_else(|| {
175 Error::StringOperation("Missing opening parenthesis".to_string())
176 })? + 1;
177 let end_pos = operation.rfind(')').ok_or_else(|| {
178 Error::StringOperation("Missing closing parenthesis".to_string())
179 })?;
180
181 let args_str = &operation[start_pos..end_pos];
182
183 let parts: Vec<&str> = args_str.split(',').map(|s| s.trim()).collect();
185
186 let start = parts[0].parse::<usize>().map_err(|_| {
187 Error::StringOperation("Invalid start position for substring".to_string())
188 })?;
189
190 let length = if parts.len() > 1 {
191 Some(parts[1].parse::<usize>().map_err(|_| {
192 Error::StringOperation("Invalid length for substring".to_string())
193 })?)
194 } else {
195 None
196 };
197
198 Ok((start, length))
199}
200
201fn clean_string_argument(arg: &str) -> Result<String, Error> {
203 let cleaned = if (arg.starts_with('"') && arg.ends_with('"')) ||
204 (arg.starts_with('\'') && arg.ends_with('\'')) {
205 &arg[1..arg.len()-1]
206 } else {
207 arg
208 };
209
210 Ok(cleaned.to_string())
211}
212
213fn extract_substring(text: &str, start: usize, length: Option<usize>) -> Result<String, Error> {
215 let chars: Vec<char> = text.chars().collect();
216
217 if start >= chars.len() {
218 return Ok("".to_string());
219 }
220
221 let end = match length {
222 Some(len) => std::cmp::min(start + len, chars.len()),
223 None => chars.len(),
224 };
225
226 Ok(chars[start..end].iter().collect())
227}
228
229pub fn apply_string_pipeline(value: &Value, operations: &[&str]) -> Result<Value, Error> {
231 let mut current_value = value.clone();
232
233 for operation in operations {
234 current_value = apply_string_operation(¤t_value, operation)?;
235 }
236
237 Ok(current_value)
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_basic_string_operations() {
246 let value = Value::String("Hello World".to_string());
247
248 let result = apply_string_operation(&value, "upper").unwrap();
250 assert_eq!(result, Value::String("HELLO WORLD".to_string()));
251
252 let result = apply_string_operation(&value, "lower").unwrap();
254 assert_eq!(result, Value::String("hello world".to_string()));
255
256 let padded = Value::String(" hello ".to_string());
258 let result = apply_string_operation(&padded, "trim").unwrap();
259 assert_eq!(result, Value::String("hello".to_string()));
260
261 let result = apply_string_operation(&value, "length").unwrap();
263 assert_eq!(result, Value::Number(11.into()));
264
265 let result = apply_string_operation(&value, "reverse").unwrap();
267 assert_eq!(result, Value::String("dlroW olleH".to_string()));
268 }
269
270 #[test]
271 fn test_string_search_operations() {
272 let value = Value::String("Hello World".to_string());
273
274 let result = apply_string_operation(&value, r#"contains("World")"#).unwrap();
276 assert_eq!(result, Value::Bool(true));
277
278 let result = apply_string_operation(&value, r#"contains("xyz")"#).unwrap();
279 assert_eq!(result, Value::Bool(false));
280
281 let result = apply_string_operation(&value, r#"starts_with("Hello")"#).unwrap();
283 assert_eq!(result, Value::Bool(true));
284
285 let result = apply_string_operation(&value, r#"ends_with("World")"#).unwrap();
287 assert_eq!(result, Value::Bool(true));
288 }
289
290 #[test]
291 fn test_replace_operation() {
292 let value = Value::String("Hello World".to_string());
293
294 let result = apply_string_operation(&value, r#"replace("World", "Rust")"#).unwrap();
295 assert_eq!(result, Value::String("Hello Rust".to_string()));
296 }
297
298 #[test]
299 fn test_substring_operation() {
300 let value = Value::String("Hello World".to_string());
301
302 let result = apply_string_operation(&value, "substring(0, 5)").unwrap();
304 assert_eq!(result, Value::String("Hello".to_string()));
305
306 let result = apply_string_operation(&value, "substring(6)").unwrap();
308 assert_eq!(result, Value::String("World".to_string()));
309 }
310
311 #[test]
312 fn test_split_operation() {
313 let value = Value::String("apple,banana,cherry".to_string());
314
315 let result = apply_string_operation(&value, r#"split(",")"#).unwrap();
316 if let Value::Array(arr) = result {
317 assert_eq!(arr.len(), 3);
318 assert_eq!(arr[0], Value::String("apple".to_string()));
319 assert_eq!(arr[1], Value::String("banana".to_string()));
320 assert_eq!(arr[2], Value::String("cherry".to_string()));
321 } else {
322 panic!("Expected array result");
323 }
324 }
325
326 #[test]
327 fn test_join_operation() {
328 let value = Value::Array(vec![
329 Value::String("apple".to_string()),
330 Value::String("banana".to_string()),
331 Value::String("cherry".to_string()),
332 ]);
333
334 let result = apply_string_operation(&value, r#"join(", ")"#).unwrap();
335 assert_eq!(result, Value::String("apple, banana, cherry".to_string()));
336 }
337
338 #[test]
339 fn test_string_pipeline() {
340 let value = Value::String(" Hello World ".to_string());
341 let operations = vec!["trim", "upper"];
342
343 let result = apply_string_pipeline(&value, &operations).unwrap();
344 assert_eq!(result, Value::String("HELLO WORLD".to_string()));
345 }
346
347 #[test]
348 fn test_non_string_error() {
349 let value = Value::Number(42.into());
350 let result = apply_string_operation(&value, "upper");
351 assert!(result.is_err());
352 }
353}