Skip to main content

fionn_ops/processor/
tape_ops.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Tape-DSON Operational Processor
3//!
4//! This module provides direct conversion between SIMD-JSON tape format
5//! and DSON operations, enabling efficient tape-level processing without
6//! intermediate serde conversions.
7
8use crate::{DsonOperation, OperationValue};
9use fionn_tape::DsonTape;
10use simd_json::value::tape::Node;
11use std::collections::HashMap;
12
13/// Tape-DSON processor for direct tape-to-operation conversion
14pub struct TapeDsonProcessor {
15    /// Operations extracted from tape
16    operations: Vec<DsonOperation>,
17    /// Field values extracted during processing (path -> value)
18    field_values: HashMap<String, OperationValue>,
19}
20
21impl TapeDsonProcessor {
22    /// Create a new tape-DSON processor
23    #[must_use]
24    pub fn new() -> Self {
25        Self {
26            operations: Vec::new(),
27            field_values: HashMap::new(),
28        }
29    }
30
31    /// Extract operations from a tape
32    ///
33    /// # Errors
34    /// Returns an error if tape processing fails
35    pub fn extract_operations(&mut self, tape: &DsonTape) -> Result<&[DsonOperation], String> {
36        self.operations.clear();
37        self.field_values.clear();
38
39        let nodes = tape.nodes();
40        if nodes.is_empty() {
41            return Ok(&self.operations);
42        }
43
44        // Process the tape recursively starting from root
45        self.process_node(nodes, 0, &mut Vec::new())?;
46
47        Ok(&self.operations)
48    }
49
50    /// Process a node at the given index, building operations
51    fn process_node(
52        &mut self,
53        nodes: &[Node<'static>],
54        index: usize,
55        path_stack: &mut Vec<String>,
56    ) -> Result<usize, String> {
57        if index >= nodes.len() {
58            return Ok(index);
59        }
60
61        let node = &nodes[index];
62        let current_path = path_stack.join(".");
63
64        match node {
65            Node::String(s) => {
66                if !current_path.is_empty() {
67                    let value = OperationValue::StringRef(s.to_string());
68                    self.field_values
69                        .insert(current_path.clone(), value.clone());
70                    self.operations.push(DsonOperation::FieldAdd {
71                        path: current_path,
72                        value,
73                    });
74                }
75                Ok(index + 1)
76            }
77            Node::Static(static_val) => {
78                if !current_path.is_empty() {
79                    let value = match static_val {
80                        simd_json::StaticNode::Null => OperationValue::Null,
81                        simd_json::StaticNode::Bool(b) => OperationValue::BoolRef(*b),
82                        simd_json::StaticNode::I64(n) => OperationValue::NumberRef(n.to_string()),
83                        simd_json::StaticNode::U64(n) => OperationValue::NumberRef(n.to_string()),
84                        simd_json::StaticNode::F64(n) => OperationValue::NumberRef(n.to_string()),
85                    };
86                    self.field_values
87                        .insert(current_path.clone(), value.clone());
88                    self.operations.push(DsonOperation::FieldAdd {
89                        path: current_path,
90                        value,
91                    });
92                }
93                Ok(index + 1)
94            }
95            Node::Object { len, count: _ } => {
96                self.operations.push(DsonOperation::ObjectStart {
97                    path: current_path.clone(),
98                });
99
100                let mut current_idx = index + 1;
101
102                // Process each field in the object
103                for _ in 0..*len {
104                    if current_idx >= nodes.len() {
105                        break;
106                    }
107
108                    // Get field name (should be a String node)
109                    if let Node::String(field_name) = &nodes[current_idx] {
110                        let field_name_str = field_name.to_string();
111                        path_stack.push(field_name_str);
112                        current_idx += 1;
113
114                        // Process the field value
115                        if current_idx < nodes.len() {
116                            current_idx = self.process_node(nodes, current_idx, path_stack)?;
117                        }
118
119                        path_stack.pop();
120                    } else {
121                        current_idx += 1;
122                    }
123                }
124
125                self.operations
126                    .push(DsonOperation::ObjectEnd { path: current_path });
127
128                Ok(current_idx)
129            }
130            Node::Array { len, count: _ } => {
131                self.operations.push(DsonOperation::ArrayStart {
132                    path: current_path.clone(),
133                });
134
135                let mut current_idx = index + 1;
136
137                // Process each element in the array
138                for elem_idx in 0..*len {
139                    if current_idx >= nodes.len() {
140                        break;
141                    }
142
143                    path_stack.push(format!("[{elem_idx}]"));
144                    current_idx = self.process_node(nodes, current_idx, path_stack)?;
145                    path_stack.pop();
146                }
147
148                self.operations
149                    .push(DsonOperation::ArrayEnd { path: current_path });
150
151                Ok(current_idx)
152            }
153        }
154    }
155
156    /// Get a field value by path
157    #[must_use]
158    pub fn get_field(&self, path: &str) -> Option<&OperationValue> {
159        self.field_values.get(path)
160    }
161
162    /// Get all extracted operations
163    #[must_use]
164    pub fn operations(&self) -> &[DsonOperation] {
165        &self.operations
166    }
167
168    /// Get all field values
169    #[must_use]
170    pub const fn field_values(&self) -> &HashMap<String, OperationValue> {
171        &self.field_values
172    }
173
174    /// Serialize operations back to JSON
175    ///
176    /// # Errors
177    /// Returns an error if JSON serialization fails.
178    pub fn serialize_to_json(&self) -> Result<String, String> {
179        let mut root = serde_json::Map::new();
180
181        for op in &self.operations {
182            match op {
183                DsonOperation::FieldAdd { path, value }
184                | DsonOperation::FieldModify { path, value } => {
185                    Self::set_value_at_path(&mut root, path, value);
186                }
187                _ => {}
188            }
189        }
190
191        serde_json::to_string(&serde_json::Value::Object(root))
192            .map_err(|e| format!("Serialization error: {e}"))
193    }
194
195    /// Set a value at a given path in the JSON structure
196    fn set_value_at_path(
197        root: &mut serde_json::Map<String, serde_json::Value>,
198        path: &str,
199        value: &OperationValue,
200    ) {
201        let parts: Vec<&str> = path.split('.').collect();
202        let json_value = Self::operation_value_to_json(value);
203
204        if parts.is_empty() {
205            return;
206        }
207
208        let mut current = root;
209
210        for (i, part) in parts.iter().enumerate() {
211            if i == parts.len() - 1 {
212                // Handle array notation in path like "items[0]"
213                if let Some(bracket_pos) = part.find('[') {
214                    let field_name = &part[..bracket_pos];
215                    // For arrays, just set the field (simplified)
216                    current.insert(field_name.to_string(), json_value.clone());
217                } else {
218                    current.insert(part.to_string(), json_value.clone());
219                }
220            } else {
221                // Navigate or create intermediate objects
222                if !current.contains_key(*part) {
223                    current.insert(
224                        part.to_string(),
225                        serde_json::Value::Object(serde_json::Map::new()),
226                    );
227                }
228                if let Some(serde_json::Value::Object(obj)) = current.get_mut(*part) {
229                    current = obj;
230                } else {
231                    return;
232                }
233            }
234        }
235    }
236
237    /// Convert `OperationValue` to `serde_json::Value`
238    fn operation_value_to_json(value: &OperationValue) -> serde_json::Value {
239        match value {
240            OperationValue::StringRef(s) => serde_json::Value::String(s.clone()),
241            OperationValue::NumberRef(n) => n.parse::<i64>().map_or_else(
242                |_| {
243                    n.parse::<f64>().map_or_else(
244                        |_| serde_json::Value::String(n.clone()),
245                        |num| {
246                            serde_json::Number::from_f64(num).map_or_else(
247                                || serde_json::Value::String(n.clone()),
248                                serde_json::Value::Number,
249                            )
250                        },
251                    )
252                },
253                |num| serde_json::Value::Number(num.into()),
254            ),
255            OperationValue::BoolRef(b) => serde_json::Value::Bool(*b),
256            OperationValue::Null => serde_json::Value::Null,
257            OperationValue::ObjectRef { .. } => serde_json::Value::Object(serde_json::Map::new()),
258            OperationValue::ArrayRef { .. } => serde_json::Value::Array(Vec::new()),
259        }
260    }
261}
262
263impl Default for TapeDsonProcessor {
264    fn default() -> Self {
265        Self::new()
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_extract_operations() {
275        let mut processor = TapeDsonProcessor::new();
276        let json = r#"{"name": "Alice", "age": 30}"#;
277
278        let tape = DsonTape::parse(json).unwrap();
279        let ops = processor.extract_operations(&tape).unwrap();
280
281        // Should have extracted operations for name and age
282        assert!(!ops.is_empty());
283    }
284
285    #[test]
286    fn test_nested_object() {
287        let mut processor = TapeDsonProcessor::new();
288        let json = r#"{"user": {"name": "Bob", "active": true}}"#;
289
290        let tape = DsonTape::parse(json).unwrap();
291        processor.extract_operations(&tape).unwrap();
292
293        // Should have extracted nested field values
294        assert!(processor.get_field("user.name").is_some());
295        assert!(processor.get_field("user.active").is_some());
296    }
297
298    #[test]
299    fn test_array_processing() {
300        let mut processor = TapeDsonProcessor::new();
301        let json = r#"{"items": [1, 2, 3]}"#;
302
303        let tape = DsonTape::parse(json).unwrap();
304        let ops = processor.extract_operations(&tape).unwrap();
305
306        // Should have array start/end and element operations
307        let has_array_start = ops
308            .iter()
309            .any(|op| matches!(op, DsonOperation::ArrayStart { .. }));
310        assert!(has_array_start);
311    }
312
313    #[test]
314    fn test_serialize_to_json() {
315        let mut processor = TapeDsonProcessor::new();
316        let json = r#"{"name": "Alice", "age": 30}"#;
317
318        let tape = DsonTape::parse(json).unwrap();
319        processor.extract_operations(&tape).unwrap();
320
321        let result = processor.serialize_to_json().unwrap();
322        assert!(result.contains("Alice"));
323        assert!(result.contains("30"));
324    }
325
326    // Additional tests for coverage
327
328    #[test]
329    fn test_default() {
330        let processor = TapeDsonProcessor::default();
331        assert!(processor.operations().is_empty());
332        assert!(processor.field_values().is_empty());
333    }
334
335    #[test]
336    fn test_empty_tape() {
337        let mut processor = TapeDsonProcessor::new();
338        let json = r"{}";
339
340        let tape = DsonTape::parse(json).unwrap();
341        let ops = processor.extract_operations(&tape).unwrap();
342
343        // Empty object should still have ObjectStart and ObjectEnd
344        assert!(!ops.is_empty());
345    }
346
347    #[test]
348    fn test_operations_getter() {
349        let mut processor = TapeDsonProcessor::new();
350        let json = r#"{"x": 1}"#;
351
352        let tape = DsonTape::parse(json).unwrap();
353        processor.extract_operations(&tape).unwrap();
354
355        let ops = processor.operations();
356        assert!(!ops.is_empty());
357    }
358
359    #[test]
360    fn test_field_values_getter() {
361        let mut processor = TapeDsonProcessor::new();
362        let json = r#"{"x": 1}"#;
363
364        let tape = DsonTape::parse(json).unwrap();
365        processor.extract_operations(&tape).unwrap();
366
367        let values = processor.field_values();
368        assert!(values.contains_key("x"));
369    }
370
371    #[test]
372    fn test_get_field_nonexistent() {
373        let processor = TapeDsonProcessor::new();
374        assert!(processor.get_field("nonexistent").is_none());
375    }
376
377    #[test]
378    fn test_null_value() {
379        let mut processor = TapeDsonProcessor::new();
380        let json = r#"{"value": null}"#;
381
382        let tape = DsonTape::parse(json).unwrap();
383        processor.extract_operations(&tape).unwrap();
384
385        let value = processor.get_field("value");
386        assert!(value.is_some());
387        assert!(matches!(value.unwrap(), OperationValue::Null));
388    }
389
390    #[test]
391    fn test_bool_value() {
392        let mut processor = TapeDsonProcessor::new();
393        let json = r#"{"active": true, "deleted": false}"#;
394
395        let tape = DsonTape::parse(json).unwrap();
396        processor.extract_operations(&tape).unwrap();
397
398        let active = processor.get_field("active");
399        assert!(matches!(active.unwrap(), OperationValue::BoolRef(true)));
400
401        let deleted = processor.get_field("deleted");
402        assert!(matches!(deleted.unwrap(), OperationValue::BoolRef(false)));
403    }
404
405    #[test]
406    fn test_float_value() {
407        let mut processor = TapeDsonProcessor::new();
408        let json = r#"{"pi": 3.14159}"#;
409
410        let tape = DsonTape::parse(json).unwrap();
411        processor.extract_operations(&tape).unwrap();
412
413        let value = processor.get_field("pi");
414        assert!(value.is_some());
415    }
416
417    #[test]
418    fn test_negative_number() {
419        let mut processor = TapeDsonProcessor::new();
420        let json = r#"{"temp": -10}"#;
421
422        let tape = DsonTape::parse(json).unwrap();
423        processor.extract_operations(&tape).unwrap();
424
425        let value = processor.get_field("temp");
426        assert!(value.is_some());
427    }
428
429    #[test]
430    fn test_deeply_nested() {
431        let mut processor = TapeDsonProcessor::new();
432        let json = r#"{"a": {"b": {"c": {"d": 42}}}}"#;
433
434        let tape = DsonTape::parse(json).unwrap();
435        processor.extract_operations(&tape).unwrap();
436
437        let value = processor.get_field("a.b.c.d");
438        assert!(value.is_some());
439    }
440
441    #[test]
442    fn test_array_of_objects() {
443        let mut processor = TapeDsonProcessor::new();
444        let json = r#"{"users": [{"name": "Alice"}, {"name": "Bob"}]}"#;
445
446        let tape = DsonTape::parse(json).unwrap();
447        let ops = processor.extract_operations(&tape).unwrap();
448
449        let has_array_start = ops
450            .iter()
451            .any(|op| matches!(op, DsonOperation::ArrayStart { .. }));
452        let has_array_end = ops
453            .iter()
454            .any(|op| matches!(op, DsonOperation::ArrayEnd { .. }));
455        assert!(has_array_start);
456        assert!(has_array_end);
457    }
458
459    #[test]
460    fn test_nested_arrays() {
461        let mut processor = TapeDsonProcessor::new();
462        let json = r#"{"matrix": [[1, 2], [3, 4]]}"#;
463
464        let tape = DsonTape::parse(json).unwrap();
465        let ops = processor.extract_operations(&tape).unwrap();
466
467        assert!(!ops.is_empty());
468    }
469
470    #[test]
471    fn test_serialize_nested_object() {
472        let mut processor = TapeDsonProcessor::new();
473        let json = r#"{"user": {"name": "Alice"}}"#;
474
475        let tape = DsonTape::parse(json).unwrap();
476        processor.extract_operations(&tape).unwrap();
477
478        let result = processor.serialize_to_json().unwrap();
479        assert!(result.contains("user"));
480        assert!(result.contains("name"));
481        assert!(result.contains("Alice"));
482    }
483
484    #[test]
485    fn test_serialize_boolean() {
486        let mut processor = TapeDsonProcessor::new();
487        let json = r#"{"active": true}"#;
488
489        let tape = DsonTape::parse(json).unwrap();
490        processor.extract_operations(&tape).unwrap();
491
492        let result = processor.serialize_to_json().unwrap();
493        assert!(result.contains("true"));
494    }
495
496    #[test]
497    fn test_serialize_null() {
498        let mut processor = TapeDsonProcessor::new();
499        let json = r#"{"value": null}"#;
500
501        let tape = DsonTape::parse(json).unwrap();
502        processor.extract_operations(&tape).unwrap();
503
504        let result = processor.serialize_to_json().unwrap();
505        assert!(result.contains("null"));
506    }
507
508    #[test]
509    fn test_serialize_float() {
510        let mut processor = TapeDsonProcessor::new();
511        let json = r#"{"pi": 3.14}"#;
512
513        let tape = DsonTape::parse(json).unwrap();
514        processor.extract_operations(&tape).unwrap();
515
516        let result = processor.serialize_to_json().unwrap();
517        assert!(result.contains("3.14"));
518    }
519
520    #[test]
521    fn test_extract_operations_clears_previous() {
522        let mut processor = TapeDsonProcessor::new();
523
524        // First extraction
525        let json1 = r#"{"first": 1}"#;
526        let tape1 = DsonTape::parse(json1).unwrap();
527        processor.extract_operations(&tape1).unwrap();
528        assert!(processor.get_field("first").is_some());
529
530        // Second extraction should clear previous
531        let json2 = r#"{"second": 2}"#;
532        let tape2 = DsonTape::parse(json2).unwrap();
533        processor.extract_operations(&tape2).unwrap();
534
535        // Previous field should be gone
536        assert!(processor.get_field("first").is_none());
537        assert!(processor.get_field("second").is_some());
538    }
539
540    #[test]
541    fn test_operation_value_to_json_object_ref() {
542        let value = OperationValue::ObjectRef { start: 0, end: 10 };
543        let json = TapeDsonProcessor::operation_value_to_json(&value);
544        assert!(json.is_object());
545    }
546
547    #[test]
548    fn test_operation_value_to_json_array_ref() {
549        let value = OperationValue::ArrayRef { start: 0, end: 10 };
550        let json = TapeDsonProcessor::operation_value_to_json(&value);
551        assert!(json.is_array());
552    }
553
554    #[test]
555    fn test_operation_value_to_json_invalid_float() {
556        // NaN or infinity that can't be represented in JSON
557        let value = OperationValue::NumberRef("NaN".to_string());
558        let json = TapeDsonProcessor::operation_value_to_json(&value);
559        // Should fall back to string representation
560        assert!(json.is_string());
561    }
562
563    #[test]
564    fn test_large_integer() {
565        let mut processor = TapeDsonProcessor::new();
566        let json = r#"{"big": 9223372036854775807}"#;
567
568        let tape = DsonTape::parse(json).unwrap();
569        processor.extract_operations(&tape).unwrap();
570
571        let value = processor.get_field("big");
572        assert!(value.is_some());
573    }
574
575    #[test]
576    fn test_string_with_special_chars() {
577        let mut processor = TapeDsonProcessor::new();
578        let json = r#"{"msg": "hello\nworld"}"#;
579
580        let tape = DsonTape::parse(json).unwrap();
581        processor.extract_operations(&tape).unwrap();
582
583        let value = processor.get_field("msg");
584        assert!(value.is_some());
585    }
586
587    #[test]
588    fn test_array_with_mixed_types() {
589        let mut processor = TapeDsonProcessor::new();
590        let json = r#"{"mixed": [1, "two", true, null]}"#;
591
592        let tape = DsonTape::parse(json).unwrap();
593        let ops = processor.extract_operations(&tape).unwrap();
594
595        assert!(!ops.is_empty());
596    }
597
598    #[test]
599    fn test_serialize_with_array_notation_path() {
600        let mut processor = TapeDsonProcessor::new();
601
602        // Manually add an operation with array notation in path
603        processor.operations.push(DsonOperation::FieldAdd {
604            path: "items[0]".to_string(),
605            value: OperationValue::NumberRef("1".to_string()),
606        });
607
608        let result = processor.serialize_to_json().unwrap();
609        // Should handle the array notation
610        assert!(result.contains("items"));
611    }
612}