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        // **拡張: OR条件対応のcontains**
36        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            // パイプ区切りでOR条件をサポート
41            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        // **拡張: OR条件対応のstarts_with**
49        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        // **拡張: OR条件対応のends_with**
61        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("contains(") && op.ends_with(")") => {
72        //    let string_val = extract_string_value(value)?;
73        //    let pattern = extract_string_argument(op)?;
74        //    Ok(Value::Bool(string_val.contains(&pattern)))
75        //},
76        //op if op.starts_with("starts_with(") && op.ends_with(")") => {
77        //    let string_val = extract_string_value(value)?;
78        //    let pattern = extract_string_argument(op)?;
79        //    Ok(Value::Bool(string_val.starts_with(&pattern)))
80        //},
81        //op if op.starts_with("ends_with(") && op.ends_with(")") => {
82        //    let string_val = extract_string_value(value)?;
83        //    let pattern = extract_string_argument(op)?;
84        //    Ok(Value::Bool(string_val.ends_with(&pattern)))
85        //},
86        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            // **新機能: split(...)[index] 形式の処理**
102            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            // join操作は配列に対して適用
115            apply_join_operation(value, op)
116        },
117        _ => Err(Error::StringOperation(format!("Unknown string operation: {}", operation))),
118    }
119}
120
121/// split(...)[index] 形式の処理
122fn apply_split_with_index(value: &Value, operation: &str) -> Result<Value, Error> {
123    // "split(\" \")[0]" のような形式を解析
124    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; // ")[" の後
129    let bracket_end = operation.len() - 1; // 最後の ']'
130    
131    if !operation.ends_with(']') {
132        return Err(Error::StringOperation("Missing closing bracket in split index".to_string()));
133    }
134    
135    // split部分とindex部分を分離
136    let split_part = &operation[..split_end + 1]; // "split(\" \")"
137    let index_part = &operation[bracket_start..bracket_end]; // "0"
138    
139    // まずsplitを実行
140    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    // インデックスを解析
145    let index = index_part.parse::<usize>().map_err(|_| {
146        Error::StringOperation(format!("Invalid array index: {}", index_part))
147    })?;
148    
149    // インデックスでアクセス
150    if let Some(part) = parts.get(index) {
151        Ok(Value::String(part.to_string()))
152    } else {
153        // インデックスが範囲外の場合は空文字列を返す(配列の動作に合わせる)
154        Ok(Value::String("".to_string()))
155    }
156}
157
158/// 文字列値を抽出(エラーハンドリングを統一)
159fn 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
168/// join操作を配列に適用
169fn 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
191/// 値の型名を取得
192fn 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
203/// 文字列引数を抽出(例: contains("pattern") → "pattern")
204fn 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    // 引用符を除去
219    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
229/// replace引数を抽出(例: replace("old", "new") → ("old", "new"))
230fn 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    // カンマで分割
241    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
252/// substring引数を抽出(例: substring(0, 5) → (0, 5))
253fn 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    // カンマで分割
264    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
281/// 引用符をクリーニング
282fn 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
293/// 部分文字列を抽出
294fn 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
309/// 複数の文字列操作をパイプラインで適用
310pub 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(&current_value, operation)?;
315    }
316    
317    Ok(current_value)
318}
319
320/// 複数フィールドに対して同じ操作を適用(ケース1)
321pub 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    // 各フィールドに同じ操作を適用
325    for field_path in field_paths {
326        // フィールド値を取得
327        let field_value = extract_field_value_from_item(item, field_path)?;
328        
329        // 操作を適用
330        let transformed_value = apply_string_operation(&field_value, operation)?;
331        
332        // フィールドを更新
333        updated_item = update_field_in_item(updated_item, field_path, transformed_value)?;
334    }
335    
336    Ok(updated_item)
337}
338
339/// アイテムからフィールド値を抽出
340fn 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..]; // '.' を除去
350    
351    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
358/// アイテムのフィールドを更新
359fn update_field_in_item(item: Value, field_path: &str, new_value: Value) -> Result<Value, Error> {
360    if field_path == "." {
361        // ルート値の場合は直接置き換え
362        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..]; // '.' を除去
370    
371    if let Value::Object(mut obj) = item {
372        obj.insert(field_name.to_string(), new_value);
373        Ok(Value::Object(obj))
374    } else {
375        // オブジェクトでない場合は新しいオブジェクトを作成
376        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    // "split(\" \")[0:3]" のような形式を解析
384    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; // ")[" の後
389    let bracket_end = operation.len() - 1; // 最後の ']'
390    
391    if !operation.ends_with(']') {
392        return Err(Error::StringOperation("Missing closing bracket in split slice".to_string()));
393    }
394    
395    // split部分とslice部分を分離
396    let split_part = &operation[..split_end + 1]; // "split(\" \")"
397    let slice_part = &operation[bracket_start..bracket_end]; // "0:3"
398    
399    // まずsplitを実行
400    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    // スライス記法を解析
405    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        // 単一インデックスの場合
417        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())) // 範囲外は空文字列
425        }
426    }
427}
428
429/// 文字列配列用のスライス記法解析
430fn 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        // 負のインデックス対応
440        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        // 負のインデックス対応
454        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
467/// 文字列配列にスライスを適用
468fn 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    // 範囲チェック
475    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
485/// contains のOR条件処理
486fn 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
492/// starts_with のOR条件処理
493fn 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
499/// ends_with のOR条件処理
500fn 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        // upper
515        let result = apply_string_operation(&value, "upper").unwrap();
516        assert_eq!(result, Value::String("HELLO WORLD".to_string()));
517        
518        // lower
519        let result = apply_string_operation(&value, "lower").unwrap();
520        assert_eq!(result, Value::String("hello world".to_string()));
521        
522        // trim
523        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        // length
528        let result = apply_string_operation(&value, "length").unwrap();
529        assert_eq!(result, Value::Number(11.into()));
530        
531        // reverse
532        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        // contains
541        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        // starts_with
548        let result = apply_string_operation(&value, r#"starts_with("Hello")"#).unwrap();
549        assert_eq!(result, Value::Bool(true));
550        
551        // ends_with
552        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        // substring with length
569        let result = apply_string_operation(&value, "substring(0, 5)").unwrap();
570        assert_eq!(result, Value::String("Hello".to_string()));
571        
572        // substring without length (to end)
573        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}