Skip to main content

state_engine/common/
dot_array_accessor.rs

1// DotArrayAccessor
2// ドット記法での配列アクセスを提供
3//
4// PHPのDotArrayAccessorを完全再現
5// - missingKeys追跡機能
6// - 階層構造の自動作成(set時)
7// - インスタンスメソッド(get, getMissingKeys, clearMissingKeys)
8// - 静的メソッド(set, merge, unset)
9
10use serde_json::Value;
11
12/// ドット記法でのデータアクセスを提供
13pub struct DotArrayAccessor {
14    missing_keys: Vec<String>,
15}
16
17impl DotArrayAccessor {
18    /// 新しいインスタンスを作成
19    pub fn new() -> Self {
20        Self {
21            missing_keys: Vec::new(),
22        }
23    }
24
25    /// ドット記法で値を取得(missingKeys追跡付き)
26    ///
27    /// 例: get(&data, "user.profile.name")
28    ///
29    /// キーが見つからない場合はmissingKeysに記録してNoneを返す
30    pub fn get<'a>(&mut self, data: &'a Value, key: &str) -> Option<&'a Value> {
31        // ドットが無い場合は単純なキーアクセス
32        if !key.contains('.') {
33            if let Some(obj) = data.as_object() {
34                if !obj.contains_key(key) {
35                    self.missing_keys.push(key.to_string());
36                    return None;
37                }
38                return obj.get(key);
39            } else {
40                self.missing_keys.push(key.to_string());
41                return None;
42            }
43        }
44
45        // ドット記法のパスを分解
46        let segments: Vec<&str> = key.split('.').collect();
47        let mut current = data;
48
49        for segment in segments {
50            match current.get(segment) {
51                Some(next) => current = next,
52                None => {
53                    self.missing_keys.push(key.to_string());
54                    return None;
55                }
56            }
57        }
58
59        Some(current)
60    }
61
62    /// 取得失敗したキーの一覧を返す
63    pub fn get_missing_keys(&self) -> &[String] {
64        &self.missing_keys
65    }
66
67    /// missingKeysをクリア
68    pub fn clear_missing_keys(&mut self) {
69        self.missing_keys.clear();
70    }
71
72    /// ドット記法で値を設定(静的メソッド)
73    ///
74    /// 例: set(&mut data, "user.profile.name", Value::String("Alice".to_string()))
75    ///
76    /// 存在しないパスは自動的にObjectとして作成される
77    pub fn set(data: &mut Value, key: &str, value: Value) {
78        // ドットが無い場合は単純な設定
79        if !key.contains('.') {
80            if let Some(obj) = data.as_object_mut() {
81                obj.insert(key.to_string(), value);
82            } else {
83                // dataがObjectでない場合、新しいObjectを作成
84                let mut new_obj = serde_json::Map::new();
85                new_obj.insert(key.to_string(), value);
86                *data = Value::Object(new_obj);
87            }
88            return;
89        }
90
91        // ドット記法のパスを分解
92        let segments: Vec<&str> = key.split('.').collect();
93
94        // dataがObjectでない場合、新しいObjectを作成
95        if !data.is_object() {
96            *data = Value::Object(serde_json::Map::new());
97        }
98
99        let mut current = data;
100
101        let last_idx = segments.len() - 1;
102
103        for (i, segment) in segments.iter().enumerate() {
104            if i == last_idx {
105                // 最後のセグメント:値を設定
106                if let Some(obj) = current.as_object_mut() {
107                    obj.insert(segment.to_string(), value);
108                }
109                return;
110            }
111
112            // 中間パス:存在しないか、Objectでない場合は新規作成してから移動
113            // Borrowチェッカーを満たすため、明示的にスコープを分ける
114            {
115                let obj = current.as_object_mut().expect("current must be an object");
116                if !obj.contains_key(*segment) || !obj[*segment].is_object() {
117                    obj.insert(segment.to_string(), Value::Object(serde_json::Map::new()));
118                }
119            }
120
121            // 次の階層へ移動(新しいスコープで借用)
122            current = current.get_mut(*segment).expect("segment must exist");
123        }
124    }
125
126    /// 値をマージ(静的メソッド)
127    ///
128    /// 例: merge(&mut data, "user.profile", json!({"age": 30}))
129    ///
130    /// 注意: マージ処理の各レベルで、既存値と新しい値の少なくとも一方がスカラー(非オブジェクト)である場合、上書き処理がされる。
131    /// state object では、末尾のノードは値に null を持って、末尾の値と区別されている。
132    /// このため、scalar と list の object は、自動的に上書き処理してよい。
133    pub fn merge(data: &mut Value, key: &str, value: Value) {
134        // ドットが無い場合
135        if !key.contains('.') {
136            // data[key] と value の両方がオブジェクトの場合は再帰マージ
137            if let Some(obj) = data.as_object_mut() {
138                let should_merge = if let Some(existing) = obj.get(key) {
139                    existing.is_object() && value.is_object()
140                } else {
141                    false
142                };
143
144                if should_merge {
145                    // 両方がオブジェクト → 再帰的にマージ
146                    if let Some(value_obj) = value.as_object() {
147                        for (k, v) in value_obj {
148                            if let Some(existing) = obj.get_mut(key) {
149                                let should_recurse = if let Some(existing_obj) = existing.as_object() {
150                                    existing_obj.contains_key(k) && existing_obj[k].is_object() && v.is_object()
151                                } else {
152                                    false
153                                };
154
155                                if should_recurse {
156                                    // 既存のキーがあり、両方がオブジェクト → 再帰呼び出し
157                                    Self::merge(existing, k, v.clone());
158                                } else {
159                                    // それ以外は上書き
160                                    if let Some(existing_obj_mut) = existing.as_object_mut() {
161                                        existing_obj_mut.insert(k.clone(), v.clone());
162                                    }
163                                }
164                            }
165                        }
166                    }
167                    return;
168                }
169                // 既存値がない、またはどちらかがオブジェクトでない → 上書き
170                obj.insert(key.to_string(), value);
171            } else {
172                // data がオブジェクトでない場合、新しいオブジェクトを作成
173                let mut new_obj = serde_json::Map::new();
174                new_obj.insert(key.to_string(), value);
175                *data = Value::Object(new_obj);
176            }
177            return;
178        }
179
180        // ドット記法のパスを分解
181        let segments: Vec<&str> = key.split('.').collect();
182        let first_segment = segments[0];
183        let remaining_key = segments[1..].join(".");
184
185        // data がオブジェクトでない場合、新しいオブジェクトを作成
186        if !data.is_object() {
187            *data = Value::Object(serde_json::Map::new());
188        }
189
190        // 最初のセグメントが存在しない場合は作成
191        if let Some(obj) = data.as_object_mut() {
192            if !obj.contains_key(first_segment) {
193                obj.insert(first_segment.to_string(), Value::Object(serde_json::Map::new()));
194            }
195
196            // 再帰的にマージ
197            if let Some(next) = obj.get_mut(first_segment) {
198                Self::merge(next, &remaining_key, value);
199            }
200        }
201    }
202
203    /// キーが存在するかチェック(静的メソッド)
204    ///
205    /// 例: has(&data, "user.profile.name")
206    ///
207    /// ドット記法でのパスを辿り、最後のキーまで存在するか確認する
208    pub fn has(data: &Value, key: &str) -> bool {
209        // ドットが無い場合は単純なキー存在チェック
210        if !key.contains('.') {
211            if let Some(obj) = data.as_object() {
212                return obj.contains_key(key);
213            }
214            return false;
215        }
216
217        // ドット記法のパスを分解
218        let segments: Vec<&str> = key.split('.').collect();
219        let mut current = data;
220
221        for segment in segments {
222            match current.get(segment) {
223                Some(next) => current = next,
224                None => return false,
225            }
226        }
227
228        true
229    }
230
231    /// 値を削除(静的メソッド)
232    ///
233    /// 例: unset(&mut data, "user.profile.name")
234    pub fn unset(data: &mut Value, key: &str) {
235        // ドットが無い場合は単純な削除
236        if !key.contains('.') {
237            if let Some(obj) = data.as_object_mut() {
238                obj.remove(key);
239            }
240            return;
241        }
242
243        // ドット記法のパスを分解
244        let segments: Vec<&str> = key.split('.').collect();
245        let mut current = data;
246
247        for (i, segment) in segments.iter().enumerate() {
248            let is_last = i == segments.len() - 1;
249
250            if is_last {
251                // 最後のセグメント:削除
252                if let Some(obj) = current.as_object_mut() {
253                    obj.remove(*segment);
254                }
255                return;
256            }
257
258            // 中間パス:次の階層へ移動
259            // Borrowチェック回避のため、存在チェックと取得を分離
260            if !current.is_object() {
261                return;
262            }
263
264            let has_next = if let Some(obj) = current.as_object() {
265                obj.contains_key(*segment) && obj.get(*segment).map_or(false, |v| v.is_object())
266            } else {
267                false
268            };
269
270            if !has_next {
271                // パスが存在しない場合は何もしない
272                return;
273            }
274
275            // 次の階層へ移動
276            current = current.get_mut(*segment).unwrap();
277        }
278    }
279}
280
281impl Default for DotArrayAccessor {
282    fn default() -> Self {
283        Self::new()
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290    use serde_json::json;
291
292    #[test]
293    fn test_get_simple_key() {
294        let mut accessor = DotArrayAccessor::new();
295        let data = json!({
296            "name": "Alice"
297        });
298
299        let result = accessor.get(&data, "name");
300        assert!(result.is_some());
301        assert_eq!(result.unwrap(), &json!("Alice"));
302        assert_eq!(accessor.get_missing_keys().len(), 0);
303    }
304
305    #[test]
306    fn test_get_nested_key() {
307        let mut accessor = DotArrayAccessor::new();
308        let data = json!({
309            "user": {
310                "profile": {
311                    "name": "Alice"
312                }
313            }
314        });
315
316        let result = accessor.get(&data, "user.profile.name");
317        assert!(result.is_some());
318        assert_eq!(result.unwrap(), &json!("Alice"));
319        assert_eq!(accessor.get_missing_keys().len(), 0);
320    }
321
322    #[test]
323    fn test_get_missing_key_tracking() {
324        let mut accessor = DotArrayAccessor::new();
325        let data = json!({
326            "user": {
327                "name": "Alice"
328            }
329        });
330
331        let result = accessor.get(&data, "user.age");
332        assert!(result.is_none());
333        assert_eq!(accessor.get_missing_keys(), vec!["user.age"]);
334
335        // 2回目の失敗
336        let result2 = accessor.get(&data, "user.email");
337        assert!(result2.is_none());
338        assert_eq!(accessor.get_missing_keys(), vec!["user.age", "user.email"]);
339
340        // クリア
341        accessor.clear_missing_keys();
342        assert_eq!(accessor.get_missing_keys().len(), 0);
343    }
344
345    #[test]
346    fn test_set_simple_key() {
347        let mut data = json!({});
348        DotArrayAccessor::set(&mut data, "name", json!("Alice"));
349
350        assert_eq!(data, json!({"name": "Alice"}));
351    }
352
353    #[test]
354    fn test_set_nested_key() {
355        let mut data = json!({});
356        DotArrayAccessor::set(&mut data, "user.profile.name", json!("Alice"));
357
358        assert_eq!(data, json!({
359            "user": {
360                "profile": {
361                    "name": "Alice"
362                }
363            }
364        }));
365    }
366
367    #[test]
368    fn test_set_overwrites_existing() {
369        let mut data = json!({
370            "user": {
371                "name": "Alice"
372            }
373        });
374
375        DotArrayAccessor::set(&mut data, "user.name", json!("Bob"));
376
377        assert_eq!(data["user"]["name"], json!("Bob"));
378    }
379
380    #[test]
381    fn test_unset_simple_key() {
382        let mut data = json!({
383            "name": "Alice",
384            "age": 30
385        });
386
387        DotArrayAccessor::unset(&mut data, "name");
388
389        assert_eq!(data, json!({"age": 30}));
390    }
391
392    #[test]
393    fn test_unset_nested_key() {
394        let mut data = json!({
395            "user": {
396                "profile": {
397                    "name": "Alice",
398                    "age": 30
399                }
400            }
401        });
402
403        DotArrayAccessor::unset(&mut data, "user.profile.name");
404
405        assert_eq!(data, json!({
406            "user": {
407                "profile": {
408                    "age": 30
409                }
410            }
411        }));
412    }
413
414    #[test]
415    fn test_unset_nonexistent() {
416        let mut data = json!({
417            "user": {
418                "name": "Alice"
419            }
420        });
421
422        // 存在しないキーの削除は何もしない
423        DotArrayAccessor::unset(&mut data, "user.age");
424        DotArrayAccessor::unset(&mut data, "unknown.path");
425
426        assert_eq!(data, json!({
427            "user": {
428                "name": "Alice"
429            }
430        }));
431    }
432
433    #[test]
434    fn test_merge_simple_key() {
435        let mut data = json!({
436            "name": "Alice"
437        });
438
439        DotArrayAccessor::merge(&mut data, "age", json!(30));
440
441        assert_eq!(data, json!({
442            "name": "Alice",
443            "age": 30
444        }));
445    }
446
447    #[test]
448    fn test_merge_overwrites_scalar() {
449        let mut data = json!({
450            "name": "Alice"
451        });
452
453        DotArrayAccessor::merge(&mut data, "name", json!("Bob"));
454
455        assert_eq!(data, json!({
456            "name": "Bob"
457        }));
458    }
459
460    #[test]
461    fn test_merge_overwrites_list() {
462        let mut data = json!({
463            "tags": ["php", "web"]
464        });
465
466        DotArrayAccessor::merge(&mut data, "tags", json!(["api", "rest"]));
467
468        assert_eq!(data, json!({
469            "tags": ["api", "rest"]
470        }));
471    }
472
473    #[test]
474    fn test_merge_nested_objects() {
475        let mut data = json!({
476            "user": {
477                "name": "Alice",
478                "profile": {
479                    "age": 25
480                }
481            }
482        });
483
484        DotArrayAccessor::merge(&mut data, "user", json!({
485            "email": "alice@example.com",
486            "profile": {
487                "age": 30,
488                "city": "Tokyo"
489            }
490        }));
491
492        assert_eq!(data, json!({
493            "user": {
494                "name": "Alice",
495                "email": "alice@example.com",
496                "profile": {
497                    "age": 30,
498                    "city": "Tokyo"
499                }
500            }
501        }));
502    }
503
504    #[test]
505    fn test_merge_with_dot_notation() {
506        let mut data = json!({
507            "connection": {
508                "driver": "postgres",
509                "charset": "UTF8"
510            }
511        });
512
513        DotArrayAccessor::merge(&mut data, "connection", json!({
514            "host": "localhost",
515            "port": 5432
516        }));
517
518        assert_eq!(data, json!({
519            "connection": {
520                "driver": "postgres",
521                "charset": "UTF8",
522                "host": "localhost",
523                "port": 5432
524            }
525        }));
526    }
527
528    #[test]
529    fn test_merge_creates_path() {
530        let mut data = json!({});
531
532        DotArrayAccessor::merge(&mut data, "user.profile.name", json!("Alice"));
533
534        assert_eq!(data, json!({
535            "user": {
536                "profile": {
537                    "name": "Alice"
538                }
539            }
540        }));
541    }
542
543    #[test]
544    fn test_has_simple_key() {
545        let data = json!({
546            "name": "Alice",
547            "age": 30
548        });
549
550        assert!(DotArrayAccessor::has(&data, "name"));
551        assert!(DotArrayAccessor::has(&data, "age"));
552        assert!(!DotArrayAccessor::has(&data, "email"));
553    }
554
555    #[test]
556    fn test_has_nested_key() {
557        let data = json!({
558            "user": {
559                "profile": {
560                    "name": "Alice",
561                    "age": 30
562                }
563            }
564        });
565
566        assert!(DotArrayAccessor::has(&data, "user"));
567        assert!(DotArrayAccessor::has(&data, "user.profile"));
568        assert!(DotArrayAccessor::has(&data, "user.profile.name"));
569        assert!(DotArrayAccessor::has(&data, "user.profile.age"));
570        assert!(!DotArrayAccessor::has(&data, "user.profile.email"));
571        assert!(!DotArrayAccessor::has(&data, "user.settings"));
572        assert!(!DotArrayAccessor::has(&data, "unknown"));
573    }
574
575    #[test]
576    fn test_has_with_null_value() {
577        let data = json!({
578            "user": {
579                "name": "Alice",
580                "deleted_at": null
581            }
582        });
583
584        // null値でもキーは存在する
585        assert!(DotArrayAccessor::has(&data, "user.deleted_at"));
586        assert!(!DotArrayAccessor::has(&data, "user.created_at"));
587    }
588
589    #[test]
590    fn test_has_with_non_object_value() {
591        let data = json!({
592            "tags": ["php", "rust"],
593            "count": 42
594        });
595
596        // スカラー値や配列も存在チェック可能
597        assert!(DotArrayAccessor::has(&data, "tags"));
598        assert!(DotArrayAccessor::has(&data, "count"));
599
600        // 配列の要素にはドット記法でアクセスできない
601        assert!(!DotArrayAccessor::has(&data, "tags.0"));
602    }
603
604    #[test]
605    fn test_has_empty_key() {
606        let data = json!({
607            "user": {
608                "name": "Alice"
609            }
610        });
611
612        // 空文字列は存在しない
613        assert!(!DotArrayAccessor::has(&data, ""));
614    }
615}