hedl_stream/
event.rs

1// Dweve HEDL - Hierarchical Entity Data Language
2//
3// Copyright (c) 2025 Dweve IP B.V. and individual contributors.
4//
5// SPDX-License-Identifier: Apache-2.0
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License in the LICENSE file at the
10// root of this repository or at: http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Event types for streaming parser.
19//!
20//! This module defines the events yielded by [`StreamingParser`](crate::StreamingParser)
21//! and associated metadata structures.
22//!
23//! # Event Flow
24//!
25//! A typical HEDL document produces events in this order:
26//!
27//! 1. `ListStart` or `ObjectStart` - Container begins
28//! 2. `Node` or `Scalar` - Data items
29//! 3. `ListEnd` or `ObjectEnd` - Container ends
30//! 4. (Repeat for additional containers)
31//!
32//! # Example Event Sequence
33//!
34//! For this HEDL document:
35//!
36//! ```text
37//! %VERSION: 1.0
38//! %STRUCT: User: [id, name]
39//! ---
40//! users: @User
41//!   | alice, Alice
42//!   | bob, Bob
43//! ```
44//!
45//! The parser yields:
46//!
47//! ```text
48//! ListStart { key: "users", type_name: "User", ... }
49//! Node(NodeInfo { id: "alice", ... })
50//! Node(NodeInfo { id: "bob", ... })
51//! ListEnd { key: "users", count: 2, ... }
52//! ```
53
54use hedl_core::Value;
55use std::collections::BTreeMap;
56
57/// Header information parsed from the HEDL document.
58///
59/// Contains metadata extracted from header directives:
60/// - `%VERSION`: Document format version
61/// - `%STRUCT`: Schema definitions mapping type names to column lists
62/// - `%ALIAS`: Variable substitutions
63/// - `%NEST`: Parent-child relationship rules
64///
65/// # Examples
66///
67/// ```rust
68/// use hedl_stream::{StreamingParser, HeaderInfo};
69/// use std::io::Cursor;
70///
71/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
72/// let input = r#"
73/// %VERSION: 1.0
74/// %STRUCT: User: [id, name, email]
75/// %ALIAS: admin = "Administrator"
76/// %NEST: User > Order
77/// ---
78/// "#;
79///
80/// let parser = StreamingParser::new(Cursor::new(input))?;
81/// let header = parser.header().unwrap();
82///
83/// // Access version
84/// let (major, minor) = header.version;
85/// println!("HEDL version {}.{}", major, minor);
86///
87/// // Look up schema
88/// if let Some(schema) = header.get_schema("User") {
89///     println!("User has {} fields", schema.len());
90/// }
91///
92/// // Check for alias
93/// if let Some(value) = header.aliases.get("admin") {
94///     println!("Alias 'admin' expands to '{}'", value);
95/// }
96///
97/// // Check nesting rule
98/// if let Some(child) = header.get_child_type("User") {
99///     println!("User can contain {}", child);
100/// }
101/// # Ok(())
102/// # }
103/// ```
104#[derive(Debug, Clone)]
105pub struct HeaderInfo {
106    /// HEDL version (major, minor).
107    pub version: (u32, u32),
108    /// Schema definitions: type -> columns.
109    pub structs: BTreeMap<String, Vec<String>>,
110    /// Alias definitions.
111    pub aliases: BTreeMap<String, String>,
112    /// Nest relationships: parent -> child.
113    pub nests: BTreeMap<String, String>,
114}
115
116impl HeaderInfo {
117    /// Create a new empty header.
118    pub fn new() -> Self {
119        Self {
120            version: (1, 0),
121            structs: BTreeMap::new(),
122            aliases: BTreeMap::new(),
123            nests: BTreeMap::new(),
124        }
125    }
126
127    /// Get schema columns for a type.
128    #[inline]
129    pub fn get_schema(&self, type_name: &str) -> Option<&Vec<String>> {
130        self.structs.get(type_name)
131    }
132
133    /// Get child type for a parent type (from NEST).
134    #[inline]
135    pub fn get_child_type(&self, parent_type: &str) -> Option<&String> {
136        self.nests.get(parent_type)
137    }
138}
139
140impl Default for HeaderInfo {
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146/// Information about a parsed node (entity/row).
147///
148/// Represents a single entity parsed from a HEDL matrix row. Contains the
149/// entity's type, ID, field values, and parent relationship information.
150///
151/// # Field Access
152///
153/// Fields can be accessed by index using [`get_field()`](Self::get_field).
154/// The first field (index 0) is always the ID.
155///
156/// # Examples
157///
158/// ## Accessing Fields
159///
160/// ```rust
161/// use hedl_stream::{StreamingParser, NodeEvent};
162/// use hedl_core::Value;
163/// use std::io::Cursor;
164///
165/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
166/// let input = r#"
167/// %VERSION: 1.0
168/// %STRUCT: User: [id, name, email, active]
169/// ---
170/// users: @User
171///   | alice, Alice Smith, alice@example.com, true
172/// "#;
173///
174/// let parser = StreamingParser::new(Cursor::new(input))?;
175///
176/// for event in parser {
177///     if let Ok(NodeEvent::Node(node)) = event {
178///         // Access by index
179///         assert_eq!(node.get_field(0), Some(&Value::String("alice".to_string())));
180///         assert_eq!(node.get_field(1), Some(&Value::String("Alice Smith".to_string())));
181///         assert_eq!(node.get_field(3), Some(&Value::Bool(true)));
182///
183///         // Or use the id field directly
184///         assert_eq!(node.id, "alice");
185///     }
186/// }
187/// # Ok(())
188/// # }
189/// ```
190///
191/// ## Checking Parent Relationships
192///
193/// ```rust
194/// use hedl_stream::{StreamingParser, NodeEvent};
195/// use std::io::Cursor;
196///
197/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
198/// let input = r#"
199/// %VERSION: 1.0
200/// %STRUCT: User: [id, name]
201/// %STRUCT: Order: [id, amount]
202/// %NEST: User > Order
203/// ---
204/// users: @User
205///   | alice, Alice
206///     | order1, 100.00
207/// "#;
208///
209/// let parser = StreamingParser::new(Cursor::new(input))?;
210///
211/// for event in parser.filter_map(|e| e.ok()) {
212///     if let NodeEvent::Node(node) = event {
213///         if node.is_nested() {
214///             println!("{} is a child of {:?}",
215///                 node.id, node.parent_id.as_ref().unwrap());
216///         }
217///     }
218/// }
219/// # Ok(())
220/// # }
221/// ```
222#[derive(Debug, Clone)]
223pub struct NodeInfo {
224    /// The entity type name.
225    pub type_name: String,
226    /// The entity ID.
227    pub id: String,
228    /// Field values aligned with schema.
229    pub fields: Vec<Value>,
230    /// Nesting depth (0 = top-level).
231    pub depth: usize,
232    /// Parent node ID (if nested).
233    pub parent_id: Option<String>,
234    /// Parent type name (if nested).
235    pub parent_type: Option<String>,
236    /// Line number in source.
237    pub line: usize,
238}
239
240impl NodeInfo {
241    /// Create a new node info.
242    pub fn new(
243        type_name: String,
244        id: String,
245        fields: Vec<Value>,
246        depth: usize,
247        line: usize,
248    ) -> Self {
249        Self {
250            type_name,
251            id,
252            fields,
253            depth,
254            parent_id: None,
255            parent_type: None,
256            line,
257        }
258    }
259
260    /// Set parent information.
261    pub fn with_parent(mut self, parent_type: String, parent_id: String) -> Self {
262        self.parent_type = Some(parent_type);
263        self.parent_id = Some(parent_id);
264        self
265    }
266
267    /// Get a field value by column index.
268    #[inline]
269    pub fn get_field(&self, index: usize) -> Option<&Value> {
270        self.fields.get(index)
271    }
272
273    /// Check if this is a nested (child) node.
274    #[inline]
275    pub fn is_nested(&self) -> bool {
276        self.depth > 0 || self.parent_id.is_some()
277    }
278}
279
280/// Event emitted by the streaming parser.
281#[derive(Debug, Clone)]
282pub enum NodeEvent {
283    /// Header has been parsed.
284    Header(HeaderInfo),
285
286    /// Start of a new list.
287    ListStart {
288        /// Key name for the list.
289        key: String,
290        /// Type name.
291        type_name: String,
292        /// Schema columns.
293        schema: Vec<String>,
294        /// Line number.
295        line: usize,
296    },
297
298    /// A node/row has been parsed.
299    Node(NodeInfo),
300
301    /// End of a list.
302    ListEnd {
303        /// Key name for the list.
304        key: String,
305        /// Type name.
306        type_name: String,
307        /// Number of nodes in the list.
308        count: usize,
309    },
310
311    /// A scalar key-value pair.
312    Scalar {
313        /// Key name.
314        key: String,
315        /// Value.
316        value: Value,
317        /// Line number.
318        line: usize,
319    },
320
321    /// Start of an object.
322    ObjectStart {
323        /// Key name.
324        key: String,
325        /// Line number.
326        line: usize,
327    },
328
329    /// End of an object.
330    ObjectEnd {
331        /// Key name.
332        key: String,
333    },
334
335    /// End of document.
336    EndOfDocument,
337}
338
339impl NodeEvent {
340    /// Check if this is a node event.
341    #[inline]
342    pub fn is_node(&self) -> bool {
343        matches!(self, Self::Node(_))
344    }
345
346    /// Get the node info if this is a node event.
347    #[inline]
348    pub fn as_node(&self) -> Option<&NodeInfo> {
349        match self {
350            Self::Node(info) => Some(info),
351            _ => None,
352        }
353    }
354
355    /// Get the line number for this event.
356    #[inline]
357    pub fn line(&self) -> Option<usize> {
358        match self {
359            Self::Header(_) => Some(1),
360            Self::ListStart { line, .. } => Some(*line),
361            Self::Node(info) => Some(info.line),
362            Self::Scalar { line, .. } => Some(*line),
363            Self::ObjectStart { line, .. } => Some(*line),
364            _ => None,
365        }
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    // ==================== HeaderInfo tests ====================
374
375    #[test]
376    fn test_header_info_new() {
377        let header = HeaderInfo::new();
378        assert_eq!(header.version, (1, 0));
379        assert!(header.structs.is_empty());
380        assert!(header.aliases.is_empty());
381        assert!(header.nests.is_empty());
382    }
383
384    #[test]
385    fn test_header_info_default() {
386        let header = HeaderInfo::default();
387        assert_eq!(header.version, (1, 0));
388        assert!(header.structs.is_empty());
389    }
390
391    #[test]
392    fn test_header_info_get_schema() {
393        let mut header = HeaderInfo::new();
394        header.structs.insert(
395            "User".to_string(),
396            vec!["id".to_string(), "name".to_string()],
397        );
398
399        assert_eq!(
400            header.get_schema("User"),
401            Some(&vec!["id".to_string(), "name".to_string()])
402        );
403        assert_eq!(header.get_schema("Unknown"), None);
404    }
405
406    #[test]
407    fn test_header_info_get_child_type() {
408        let mut header = HeaderInfo::new();
409        header.nests.insert("User".to_string(), "Order".to_string());
410
411        assert_eq!(header.get_child_type("User"), Some(&"Order".to_string()));
412        assert_eq!(header.get_child_type("Product"), None);
413    }
414
415    #[test]
416    fn test_header_info_multiple_structs() {
417        let mut header = HeaderInfo::new();
418        header
419            .structs
420            .insert("User".to_string(), vec!["id".to_string()]);
421        header.structs.insert(
422            "Product".to_string(),
423            vec!["id".to_string(), "price".to_string()],
424        );
425        header.structs.insert(
426            "Order".to_string(),
427            vec!["id".to_string(), "user".to_string(), "product".to_string()],
428        );
429
430        assert_eq!(header.structs.len(), 3);
431        assert_eq!(header.get_schema("User").unwrap().len(), 1);
432        assert_eq!(header.get_schema("Product").unwrap().len(), 2);
433        assert_eq!(header.get_schema("Order").unwrap().len(), 3);
434    }
435
436    #[test]
437    fn test_header_info_multiple_aliases() {
438        let mut header = HeaderInfo::new();
439        header
440            .aliases
441            .insert("active".to_string(), "Active".to_string());
442        header
443            .aliases
444            .insert("inactive".to_string(), "Inactive".to_string());
445
446        assert_eq!(header.aliases.len(), 2);
447        assert_eq!(header.aliases.get("active"), Some(&"Active".to_string()));
448    }
449
450    #[test]
451    fn test_header_info_multiple_nests() {
452        let mut header = HeaderInfo::new();
453        header.nests.insert("User".to_string(), "Order".to_string());
454        header
455            .nests
456            .insert("Order".to_string(), "LineItem".to_string());
457
458        assert_eq!(header.nests.len(), 2);
459        assert_eq!(header.get_child_type("User"), Some(&"Order".to_string()));
460        assert_eq!(
461            header.get_child_type("Order"),
462            Some(&"LineItem".to_string())
463        );
464    }
465
466    #[test]
467    fn test_header_info_clone() {
468        let mut header = HeaderInfo::new();
469        header.version = (2, 1);
470        header
471            .structs
472            .insert("Test".to_string(), vec!["col".to_string()]);
473
474        let cloned = header.clone();
475        assert_eq!(cloned.version, (2, 1));
476        assert_eq!(cloned.structs.get("Test"), Some(&vec!["col".to_string()]));
477    }
478
479    #[test]
480    fn test_header_info_debug() {
481        let header = HeaderInfo::new();
482        let debug = format!("{:?}", header);
483        assert!(debug.contains("HeaderInfo"));
484        assert!(debug.contains("version"));
485    }
486
487    // ==================== NodeInfo tests ====================
488
489    #[test]
490    fn test_node_info_new() {
491        let node = NodeInfo::new(
492            "User".to_string(),
493            "alice".to_string(),
494            vec![
495                Value::String("alice".to_string()),
496                Value::String("Alice".to_string()),
497            ],
498            0,
499            10,
500        );
501
502        assert_eq!(node.type_name, "User");
503        assert_eq!(node.id, "alice");
504        assert_eq!(node.fields.len(), 2);
505        assert_eq!(node.depth, 0);
506        assert_eq!(node.line, 10);
507        assert_eq!(node.parent_id, None);
508        assert_eq!(node.parent_type, None);
509    }
510
511    #[test]
512    fn test_node_info_with_parent() {
513        let node = NodeInfo::new(
514            "Order".to_string(),
515            "order1".to_string(),
516            vec![Value::String("order1".to_string())],
517            1,
518            15,
519        )
520        .with_parent("User".to_string(), "alice".to_string());
521
522        assert_eq!(node.parent_type, Some("User".to_string()));
523        assert_eq!(node.parent_id, Some("alice".to_string()));
524    }
525
526    #[test]
527    fn test_node_info_get_field() {
528        let node = NodeInfo::new(
529            "Data".to_string(),
530            "row1".to_string(),
531            vec![
532                Value::String("row1".to_string()),
533                Value::Int(42),
534                Value::Bool(true),
535            ],
536            0,
537            5,
538        );
539
540        assert_eq!(node.get_field(0), Some(&Value::String("row1".to_string())));
541        assert_eq!(node.get_field(1), Some(&Value::Int(42)));
542        assert_eq!(node.get_field(2), Some(&Value::Bool(true)));
543        assert_eq!(node.get_field(3), None);
544        assert_eq!(node.get_field(100), None);
545    }
546
547    #[test]
548    fn test_node_info_is_nested_by_depth() {
549        let nested = NodeInfo::new("Child".to_string(), "c1".to_string(), vec![], 1, 10);
550        assert!(nested.is_nested());
551
552        let top_level = NodeInfo::new("Parent".to_string(), "p1".to_string(), vec![], 0, 5);
553        assert!(!top_level.is_nested());
554    }
555
556    #[test]
557    fn test_node_info_is_nested_by_parent() {
558        let with_parent = NodeInfo::new("Child".to_string(), "c1".to_string(), vec![], 0, 10)
559            .with_parent("Parent".to_string(), "p1".to_string());
560        assert!(with_parent.is_nested());
561    }
562
563    #[test]
564    fn test_node_info_clone() {
565        let node = NodeInfo::new(
566            "User".to_string(),
567            "alice".to_string(),
568            vec![Value::String("alice".to_string())],
569            0,
570            1,
571        );
572        let cloned = node.clone();
573
574        assert_eq!(cloned.type_name, "User");
575        assert_eq!(cloned.id, "alice");
576    }
577
578    #[test]
579    fn test_node_info_debug() {
580        let node = NodeInfo::new("User".to_string(), "alice".to_string(), vec![], 0, 1);
581        let debug = format!("{:?}", node);
582        assert!(debug.contains("NodeInfo"));
583        assert!(debug.contains("User"));
584        assert!(debug.contains("alice"));
585    }
586
587    #[test]
588    fn test_node_info_empty_fields() {
589        let node = NodeInfo::new("Empty".to_string(), "e1".to_string(), vec![], 0, 1);
590        assert!(node.fields.is_empty());
591        assert_eq!(node.get_field(0), None);
592    }
593
594    #[test]
595    fn test_node_info_all_value_types() {
596        let node = NodeInfo::new(
597            "AllTypes".to_string(),
598            "test".to_string(),
599            vec![
600                Value::Null,
601                Value::Bool(true),
602                Value::Int(-42),
603                Value::Float(3.5),
604                Value::String("hello".to_string()),
605            ],
606            0,
607            1,
608        );
609
610        assert_eq!(node.get_field(0), Some(&Value::Null));
611        assert_eq!(node.get_field(1), Some(&Value::Bool(true)));
612        assert_eq!(node.get_field(2), Some(&Value::Int(-42)));
613        assert_eq!(node.get_field(3), Some(&Value::Float(3.5)));
614        assert_eq!(node.get_field(4), Some(&Value::String("hello".to_string())));
615    }
616
617    // ==================== NodeEvent tests ====================
618
619    #[test]
620    fn test_node_event_is_node() {
621        let node_info = NodeInfo::new("User".to_string(), "a".to_string(), vec![], 0, 1);
622        let node_event = NodeEvent::Node(node_info);
623        assert!(node_event.is_node());
624
625        let header_event = NodeEvent::Header(HeaderInfo::new());
626        assert!(!header_event.is_node());
627
628        let list_start = NodeEvent::ListStart {
629            key: "users".to_string(),
630            type_name: "User".to_string(),
631            schema: vec![],
632            line: 1,
633        };
634        assert!(!list_start.is_node());
635    }
636
637    #[test]
638    fn test_node_event_as_node() {
639        let node_info = NodeInfo::new("User".to_string(), "alice".to_string(), vec![], 0, 5);
640        let node_event = NodeEvent::Node(node_info);
641
642        let extracted = node_event.as_node().unwrap();
643        assert_eq!(extracted.id, "alice");
644
645        let header_event = NodeEvent::Header(HeaderInfo::new());
646        assert!(header_event.as_node().is_none());
647    }
648
649    #[test]
650    fn test_node_event_line_header() {
651        let event = NodeEvent::Header(HeaderInfo::new());
652        assert_eq!(event.line(), Some(1));
653    }
654
655    #[test]
656    fn test_node_event_line_list_start() {
657        let event = NodeEvent::ListStart {
658            key: "users".to_string(),
659            type_name: "User".to_string(),
660            schema: vec![],
661            line: 42,
662        };
663        assert_eq!(event.line(), Some(42));
664    }
665
666    #[test]
667    fn test_node_event_line_node() {
668        let node = NodeInfo::new("User".to_string(), "a".to_string(), vec![], 0, 100);
669        let event = NodeEvent::Node(node);
670        assert_eq!(event.line(), Some(100));
671    }
672
673    #[test]
674    fn test_node_event_line_scalar() {
675        let event = NodeEvent::Scalar {
676            key: "name".to_string(),
677            value: Value::String("test".to_string()),
678            line: 25,
679        };
680        assert_eq!(event.line(), Some(25));
681    }
682
683    #[test]
684    fn test_node_event_line_object_start() {
685        let event = NodeEvent::ObjectStart {
686            key: "config".to_string(),
687            line: 50,
688        };
689        assert_eq!(event.line(), Some(50));
690    }
691
692    #[test]
693    fn test_node_event_line_list_end() {
694        let event = NodeEvent::ListEnd {
695            key: "users".to_string(),
696            type_name: "User".to_string(),
697            count: 10,
698        };
699        assert_eq!(event.line(), None);
700    }
701
702    #[test]
703    fn test_node_event_line_object_end() {
704        let event = NodeEvent::ObjectEnd {
705            key: "config".to_string(),
706        };
707        assert_eq!(event.line(), None);
708    }
709
710    #[test]
711    fn test_node_event_line_end_of_document() {
712        let event = NodeEvent::EndOfDocument;
713        assert_eq!(event.line(), None);
714    }
715
716    #[test]
717    fn test_node_event_clone() {
718        let event = NodeEvent::Scalar {
719            key: "key".to_string(),
720            value: Value::Int(42),
721            line: 10,
722        };
723        let cloned = event.clone();
724
725        if let NodeEvent::Scalar { key, value, line } = cloned {
726            assert_eq!(key, "key");
727            assert_eq!(value, Value::Int(42));
728            assert_eq!(line, 10);
729        } else {
730            panic!("Expected Scalar");
731        }
732    }
733
734    #[test]
735    fn test_node_event_debug() {
736        let event = NodeEvent::EndOfDocument;
737        let debug = format!("{:?}", event);
738        assert!(debug.contains("EndOfDocument"));
739    }
740
741    #[test]
742    fn test_node_event_list_start_fields() {
743        let event = NodeEvent::ListStart {
744            key: "users".to_string(),
745            type_name: "User".to_string(),
746            schema: vec!["id".to_string(), "name".to_string()],
747            line: 5,
748        };
749
750        if let NodeEvent::ListStart {
751            key,
752            type_name,
753            schema,
754            line,
755        } = event
756        {
757            assert_eq!(key, "users");
758            assert_eq!(type_name, "User");
759            assert_eq!(schema, vec!["id".to_string(), "name".to_string()]);
760            assert_eq!(line, 5);
761        }
762    }
763
764    #[test]
765    fn test_node_event_list_end_fields() {
766        let event = NodeEvent::ListEnd {
767            key: "products".to_string(),
768            type_name: "Product".to_string(),
769            count: 100,
770        };
771
772        if let NodeEvent::ListEnd {
773            key,
774            type_name,
775            count,
776        } = event
777        {
778            assert_eq!(key, "products");
779            assert_eq!(type_name, "Product");
780            assert_eq!(count, 100);
781        }
782    }
783
784    #[test]
785    fn test_node_event_scalar_all_value_types() {
786        let events = [
787            NodeEvent::Scalar {
788                key: "null".to_string(),
789                value: Value::Null,
790                line: 1,
791            },
792            NodeEvent::Scalar {
793                key: "bool".to_string(),
794                value: Value::Bool(true),
795                line: 2,
796            },
797            NodeEvent::Scalar {
798                key: "int".to_string(),
799                value: Value::Int(-100),
800                line: 3,
801            },
802            NodeEvent::Scalar {
803                key: "float".to_string(),
804                value: Value::Float(2.75),
805                line: 4,
806            },
807            NodeEvent::Scalar {
808                key: "string".to_string(),
809                value: Value::String("text".to_string()),
810                line: 5,
811            },
812        ];
813
814        for (i, event) in events.iter().enumerate() {
815            assert_eq!(event.line(), Some(i + 1));
816            assert!(!event.is_node());
817        }
818    }
819}