hawk_data/
string_ops.rs

1use serde_json::Value;
2use crate::Error;
3
4/// 文字列操作を適用する
5pub 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            // join操作は配列に対して適用
72            apply_join_operation(value, op)
73        },
74        _ => Err(Error::StringOperation(format!("Unknown string operation: {}", operation))),
75    }
76}
77
78/// 文字列値を抽出(エラーハンドリングを統一)
79fn 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
88/// join操作を配列に適用
89fn 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
111/// 値の型名を取得
112fn 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
123/// 文字列引数を抽出(例: contains("pattern") → "pattern")
124fn 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    // 引用符を除去
139    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
149/// replace引数を抽出(例: replace("old", "new") → ("old", "new"))
150fn 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    // カンマで分割
161    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
172/// substring引数を抽出(例: substring(0, 5) → (0, 5))
173fn 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    // カンマで分割
184    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
201/// 引用符をクリーニング
202fn 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
213/// 部分文字列を抽出
214fn 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
229/// 複数の文字列操作をパイプラインで適用
230pub 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(&current_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        // upper
249        let result = apply_string_operation(&value, "upper").unwrap();
250        assert_eq!(result, Value::String("HELLO WORLD".to_string()));
251        
252        // lower
253        let result = apply_string_operation(&value, "lower").unwrap();
254        assert_eq!(result, Value::String("hello world".to_string()));
255        
256        // trim
257        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        // length
262        let result = apply_string_operation(&value, "length").unwrap();
263        assert_eq!(result, Value::Number(11.into()));
264        
265        // reverse
266        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        // contains
275        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        // starts_with
282        let result = apply_string_operation(&value, r#"starts_with("Hello")"#).unwrap();
283        assert_eq!(result, Value::Bool(true));
284        
285        // ends_with
286        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        // substring with length
303        let result = apply_string_operation(&value, "substring(0, 5)").unwrap();
304        assert_eq!(result, Value::String("Hello".to_string()));
305        
306        // substring without length (to end)
307        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}