Skip to main content

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(children) = header.get_child_types("User") {
99///     println!("User can contain {:?}", children);
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 -> children.
113    /// A parent type can have multiple child types.
114    pub nests: BTreeMap<String, Vec<String>>,
115    /// Null literal character (v2.0+). Default: '~'.
116    pub null_char: char,
117    /// Quote character (v2.0+). Default: '"'.
118    pub quote_char: char,
119    /// Count hints: Type.total -> count (v2.0+).
120    pub count_totals: BTreeMap<String, usize>,
121    /// Count hints: Type.field:value -> count (v2.0+).
122    pub count_fields: BTreeMap<String, BTreeMap<String, usize>>,
123}
124
125impl HeaderInfo {
126    /// Create a new empty header.
127    #[must_use]
128    pub fn new() -> Self {
129        Self {
130            version: (1, 0),
131            structs: BTreeMap::new(),
132            aliases: BTreeMap::new(),
133            nests: BTreeMap::new(),
134            null_char: '~',
135            quote_char: '"',
136            count_totals: BTreeMap::new(),
137            count_fields: BTreeMap::new(),
138        }
139    }
140
141    /// Get schema columns for a type.
142    #[inline]
143    #[must_use]
144    pub fn get_schema(&self, type_name: &str) -> Option<&Vec<String>> {
145        self.structs.get(type_name)
146    }
147
148    /// Get child types for a parent type (from NEST).
149    #[inline]
150    #[must_use]
151    pub fn get_child_types(&self, parent_type: &str) -> Option<&Vec<String>> {
152        self.nests.get(parent_type)
153    }
154}
155
156impl Default for HeaderInfo {
157    fn default() -> Self {
158        Self::new()
159    }
160}
161
162/// Information about a parsed node (entity/row).
163///
164/// Represents a single entity parsed from a HEDL matrix row. Contains the
165/// entity's type, ID, field values, and parent relationship information.
166///
167/// # Field Access
168///
169/// Fields can be accessed by index using [`get_field()`](Self::get_field).
170/// The first field (index 0) is always the ID.
171///
172/// # Examples
173///
174/// ## Accessing Fields
175///
176/// ```rust
177/// use hedl_stream::{StreamingParser, NodeEvent};
178/// use hedl_core::Value;
179/// use std::io::Cursor;
180///
181/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
182/// let input = r#"
183/// %VERSION: 1.0
184/// %STRUCT: User: [id, name, email, active]
185/// ---
186/// users:@User
187///   | alice, Alice Smith, alice@example.com, true
188/// "#;
189///
190/// let parser = StreamingParser::new(Cursor::new(input))?;
191///
192/// for event in parser {
193///     if let Ok(NodeEvent::Node(node)) = event {
194///         // Access by index
195///         assert_eq!(node.get_field(0), Some(&Value::String("alice".to_string().into())));
196///         assert_eq!(node.get_field(1), Some(&Value::String("Alice Smith".to_string().into())));
197///         assert_eq!(node.get_field(3), Some(&Value::Bool(true)));
198///
199///         // Or use the id field directly
200///         assert_eq!(node.id, "alice");
201///     }
202/// }
203/// # Ok(())
204/// # }
205/// ```
206///
207/// ## Checking Parent Relationships
208///
209/// ```rust
210/// use hedl_stream::{StreamingParser, NodeEvent};
211/// use std::io::Cursor;
212///
213/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
214/// let input = r#"
215/// %VERSION: 1.0
216/// %STRUCT: User: [id, name]
217/// %STRUCT: Order: [id, amount]
218/// %NEST: User > Order
219/// ---
220/// users:@User
221///   | alice, Alice
222///     | order1, 100.00
223/// "#;
224///
225/// let parser = StreamingParser::new(Cursor::new(input))?;
226///
227/// for event in parser.filter_map(|e| e.ok()) {
228///     if let NodeEvent::Node(node) = event {
229///         if node.is_nested() {
230///             println!("{} is a child of {:?}",
231///                 node.id, node.parent_id.as_ref().unwrap());
232///         }
233///     }
234/// }
235/// # Ok(())
236/// # }
237/// ```
238#[derive(Debug, Clone)]
239pub struct NodeInfo {
240    /// The entity type name.
241    pub type_name: String,
242    /// The entity ID.
243    pub id: String,
244    /// Field values aligned with schema.
245    pub fields: Vec<Value>,
246    /// Nesting depth (0 = top-level).
247    pub depth: usize,
248    /// Parent node ID (if nested).
249    pub parent_id: Option<String>,
250    /// Parent type name (if nested).
251    pub parent_type: Option<String>,
252    /// Line number in source.
253    pub line: usize,
254    /// Expected child count from `|[N]` syntax.
255    pub child_count: Option<usize>,
256}
257
258impl NodeInfo {
259    /// Create a new node info.
260    #[must_use]
261    pub fn new(
262        type_name: String,
263        id: String,
264        fields: Vec<Value>,
265        depth: usize,
266        line: usize,
267    ) -> Self {
268        Self {
269            type_name,
270            id,
271            fields,
272            depth,
273            parent_id: None,
274            parent_type: None,
275            line,
276            child_count: None,
277        }
278    }
279
280    /// Set parent information.
281    #[must_use]
282    pub fn with_parent(mut self, parent_type: String, parent_id: String) -> Self {
283        self.parent_type = Some(parent_type);
284        self.parent_id = Some(parent_id);
285        self
286    }
287
288    /// Set expected child count from `|[N]` syntax.
289    #[must_use]
290    pub fn with_child_count(mut self, count: usize) -> Self {
291        self.child_count = Some(count);
292        self
293    }
294
295    /// Get a field value by column index.
296    #[inline]
297    #[must_use]
298    pub fn get_field(&self, index: usize) -> Option<&Value> {
299        self.fields.get(index)
300    }
301
302    /// Check if this is a nested (child) node.
303    #[inline]
304    #[must_use]
305    pub fn is_nested(&self) -> bool {
306        self.depth > 0 || self.parent_id.is_some()
307    }
308}
309
310/// Event emitted by the streaming parser.
311#[derive(Debug, Clone)]
312pub enum NodeEvent {
313    /// Header has been parsed.
314    Header(HeaderInfo),
315
316    /// Start of a new list.
317    ListStart {
318        /// Key name for the list.
319        key: String,
320        /// Type name.
321        type_name: String,
322        /// Schema columns.
323        schema: Vec<String>,
324        /// Line number.
325        line: usize,
326    },
327
328    /// A node/row has been parsed.
329    Node(NodeInfo),
330
331    /// End of a list.
332    ListEnd {
333        /// Key name for the list.
334        key: String,
335        /// Type name.
336        type_name: String,
337        /// Number of nodes in the list.
338        count: usize,
339    },
340
341    /// A scalar key-value pair.
342    Scalar {
343        /// Key name.
344        key: String,
345        /// Value.
346        value: Value,
347        /// Line number.
348        line: usize,
349    },
350
351    /// Start of an object.
352    ObjectStart {
353        /// Key name.
354        key: String,
355        /// Line number.
356        line: usize,
357    },
358
359    /// End of an object.
360    ObjectEnd {
361        /// Key name.
362        key: String,
363    },
364
365    /// End of document.
366    EndOfDocument,
367}
368
369impl NodeEvent {
370    /// Check if this is a node event.
371    #[inline]
372    #[must_use]
373    pub fn is_node(&self) -> bool {
374        matches!(self, Self::Node(_))
375    }
376
377    /// Get the node info if this is a node event.
378    #[inline]
379    #[must_use]
380    pub fn as_node(&self) -> Option<&NodeInfo> {
381        match self {
382            Self::Node(info) => Some(info),
383            _ => None,
384        }
385    }
386
387    /// Get the line number for this event.
388    #[inline]
389    #[must_use]
390    pub fn line(&self) -> Option<usize> {
391        match self {
392            Self::Header(_) => Some(1),
393            Self::ListStart { line, .. } => Some(*line),
394            Self::Node(info) => Some(info.line),
395            Self::Scalar { line, .. } => Some(*line),
396            Self::ObjectStart { line, .. } => Some(*line),
397            _ => None,
398        }
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    // ==================== HeaderInfo tests ====================
407
408    #[test]
409    fn test_header_info_new() {
410        let header = HeaderInfo::new();
411        assert_eq!(header.version, (1, 0));
412        assert!(header.structs.is_empty());
413        assert!(header.aliases.is_empty());
414        assert!(header.nests.is_empty());
415    }
416
417    #[test]
418    fn test_header_info_default() {
419        let header = HeaderInfo::default();
420        assert_eq!(header.version, (1, 0));
421        assert!(header.structs.is_empty());
422    }
423
424    #[test]
425    fn test_header_info_get_schema() {
426        let mut header = HeaderInfo::new();
427        header.structs.insert(
428            "User".to_string(),
429            vec!["id".to_string(), "name".to_string()],
430        );
431
432        assert_eq!(
433            header.get_schema("User"),
434            Some(&vec!["id".to_string(), "name".to_string()])
435        );
436        assert_eq!(header.get_schema("Unknown"), None);
437    }
438
439    #[test]
440    fn test_header_info_get_child_types() {
441        let mut header = HeaderInfo::new();
442        header
443            .nests
444            .entry("User".to_string())
445            .or_default()
446            .push("Order".to_string());
447
448        assert_eq!(
449            header.get_child_types("User"),
450            Some(&vec!["Order".to_string()])
451        );
452        assert_eq!(header.get_child_types("Product"), None);
453    }
454
455    #[test]
456    fn test_header_info_multiple_structs() {
457        let mut header = HeaderInfo::new();
458        header
459            .structs
460            .insert("User".to_string(), vec!["id".to_string()]);
461        header.structs.insert(
462            "Product".to_string(),
463            vec!["id".to_string(), "price".to_string()],
464        );
465        header.structs.insert(
466            "Order".to_string(),
467            vec!["id".to_string(), "user".to_string(), "product".to_string()],
468        );
469
470        assert_eq!(header.structs.len(), 3);
471        assert_eq!(header.get_schema("User").unwrap().len(), 1);
472        assert_eq!(header.get_schema("Product").unwrap().len(), 2);
473        assert_eq!(header.get_schema("Order").unwrap().len(), 3);
474    }
475
476    #[test]
477    fn test_header_info_multiple_aliases() {
478        let mut header = HeaderInfo::new();
479        header
480            .aliases
481            .insert("active".to_string(), "Active".to_string());
482        header
483            .aliases
484            .insert("inactive".to_string(), "Inactive".to_string());
485
486        assert_eq!(header.aliases.len(), 2);
487        assert_eq!(header.aliases.get("active"), Some(&"Active".to_string()));
488    }
489
490    #[test]
491    fn test_header_info_multiple_nests() {
492        let mut header = HeaderInfo::new();
493        header
494            .nests
495            .entry("User".to_string())
496            .or_default()
497            .push("Order".to_string());
498        header
499            .nests
500            .entry("Order".to_string())
501            .or_default()
502            .push("LineItem".to_string());
503
504        assert_eq!(header.nests.len(), 2);
505        assert_eq!(
506            header.get_child_types("User"),
507            Some(&vec!["Order".to_string()])
508        );
509        assert_eq!(
510            header.get_child_types("Order"),
511            Some(&vec!["LineItem".to_string()])
512        );
513    }
514
515    #[test]
516    fn test_header_info_clone() {
517        let mut header = HeaderInfo::new();
518        header.version = (2, 1);
519        header
520            .structs
521            .insert("Test".to_string(), vec!["col".to_string()]);
522
523        let cloned = header.clone();
524        assert_eq!(cloned.version, (2, 1));
525        assert_eq!(cloned.structs.get("Test"), Some(&vec!["col".to_string()]));
526    }
527
528    #[test]
529    fn test_header_info_debug() {
530        let header = HeaderInfo::new();
531        let debug = format!("{header:?}");
532        assert!(debug.contains("HeaderInfo"));
533        assert!(debug.contains("version"));
534    }
535
536    // ==================== NodeInfo tests ====================
537
538    #[test]
539    fn test_node_info_new() {
540        let node = NodeInfo::new(
541            "User".to_string(),
542            "alice".to_string(),
543            vec![
544                Value::String("alice".to_string().into()),
545                Value::String("Alice".to_string().into()),
546            ],
547            0,
548            10,
549        );
550
551        assert_eq!(node.type_name, "User");
552        assert_eq!(node.id, "alice");
553        assert_eq!(node.fields.len(), 2);
554        assert_eq!(node.depth, 0);
555        assert_eq!(node.line, 10);
556        assert_eq!(node.parent_id, None);
557        assert_eq!(node.parent_type, None);
558    }
559
560    #[test]
561    fn test_node_info_with_parent() {
562        let node = NodeInfo::new(
563            "Order".to_string(),
564            "order1".to_string(),
565            vec![Value::String("order1".to_string().into())],
566            1,
567            15,
568        )
569        .with_parent("User".to_string(), "alice".to_string());
570
571        assert_eq!(node.parent_type, Some("User".to_string()));
572        assert_eq!(node.parent_id, Some("alice".to_string()));
573    }
574
575    #[test]
576    fn test_node_info_get_field() {
577        let node = NodeInfo::new(
578            "Data".to_string(),
579            "row1".to_string(),
580            vec![
581                Value::String("row1".to_string().into()),
582                Value::Int(42),
583                Value::Bool(true),
584            ],
585            0,
586            5,
587        );
588
589        assert_eq!(
590            node.get_field(0),
591            Some(&Value::String("row1".to_string().into()))
592        );
593        assert_eq!(node.get_field(1), Some(&Value::Int(42)));
594        assert_eq!(node.get_field(2), Some(&Value::Bool(true)));
595        assert_eq!(node.get_field(3), None);
596        assert_eq!(node.get_field(100), None);
597    }
598
599    #[test]
600    fn test_node_info_is_nested_by_depth() {
601        let nested = NodeInfo::new("Child".to_string(), "c1".to_string(), vec![], 1, 10);
602        assert!(nested.is_nested());
603
604        let top_level = NodeInfo::new("Parent".to_string(), "p1".to_string(), vec![], 0, 5);
605        assert!(!top_level.is_nested());
606    }
607
608    #[test]
609    fn test_node_info_is_nested_by_parent() {
610        let with_parent = NodeInfo::new("Child".to_string(), "c1".to_string(), vec![], 0, 10)
611            .with_parent("Parent".to_string(), "p1".to_string());
612        assert!(with_parent.is_nested());
613    }
614
615    #[test]
616    fn test_node_info_clone() {
617        let node = NodeInfo::new(
618            "User".to_string(),
619            "alice".to_string(),
620            vec![Value::String("alice".to_string().into())],
621            0,
622            1,
623        );
624        let cloned = node.clone();
625
626        assert_eq!(cloned.type_name, "User");
627        assert_eq!(cloned.id, "alice");
628    }
629
630    #[test]
631    fn test_node_info_debug() {
632        let node = NodeInfo::new("User".to_string(), "alice".to_string(), vec![], 0, 1);
633        let debug = format!("{node:?}");
634        assert!(debug.contains("NodeInfo"));
635        assert!(debug.contains("User"));
636        assert!(debug.contains("alice"));
637    }
638
639    #[test]
640    fn test_node_info_empty_fields() {
641        let node = NodeInfo::new("Empty".to_string(), "e1".to_string(), vec![], 0, 1);
642        assert!(node.fields.is_empty());
643        assert_eq!(node.get_field(0), None);
644    }
645
646    #[test]
647    fn test_node_info_all_value_types() {
648        let node = NodeInfo::new(
649            "AllTypes".to_string(),
650            "test".to_string(),
651            vec![
652                Value::Null,
653                Value::Bool(true),
654                Value::Int(-42),
655                Value::Float(3.5),
656                Value::String("hello".to_string().into()),
657            ],
658            0,
659            1,
660        );
661
662        assert_eq!(node.get_field(0), Some(&Value::Null));
663        assert_eq!(node.get_field(1), Some(&Value::Bool(true)));
664        assert_eq!(node.get_field(2), Some(&Value::Int(-42)));
665        assert_eq!(node.get_field(3), Some(&Value::Float(3.5)));
666        assert_eq!(
667            node.get_field(4),
668            Some(&Value::String("hello".to_string().into()))
669        );
670    }
671
672    // ==================== NodeEvent tests ====================
673
674    #[test]
675    fn test_node_event_is_node() {
676        let node_info = NodeInfo::new("User".to_string(), "a".to_string(), vec![], 0, 1);
677        let node_event = NodeEvent::Node(node_info);
678        assert!(node_event.is_node());
679
680        let header_event = NodeEvent::Header(HeaderInfo::new());
681        assert!(!header_event.is_node());
682
683        let list_start = NodeEvent::ListStart {
684            key: "users".to_string(),
685            type_name: "User".to_string(),
686            schema: vec![],
687            line: 1,
688        };
689        assert!(!list_start.is_node());
690    }
691
692    #[test]
693    fn test_node_event_as_node() {
694        let node_info = NodeInfo::new("User".to_string(), "alice".to_string(), vec![], 0, 5);
695        let node_event = NodeEvent::Node(node_info);
696
697        let extracted = node_event.as_node().unwrap();
698        assert_eq!(extracted.id, "alice");
699
700        let header_event = NodeEvent::Header(HeaderInfo::new());
701        assert!(header_event.as_node().is_none());
702    }
703
704    #[test]
705    fn test_node_event_line_header() {
706        let event = NodeEvent::Header(HeaderInfo::new());
707        assert_eq!(event.line(), Some(1));
708    }
709
710    #[test]
711    fn test_node_event_line_list_start() {
712        let event = NodeEvent::ListStart {
713            key: "users".to_string(),
714            type_name: "User".to_string(),
715            schema: vec![],
716            line: 42,
717        };
718        assert_eq!(event.line(), Some(42));
719    }
720
721    #[test]
722    fn test_node_event_line_node() {
723        let node = NodeInfo::new("User".to_string(), "a".to_string(), vec![], 0, 100);
724        let event = NodeEvent::Node(node);
725        assert_eq!(event.line(), Some(100));
726    }
727
728    #[test]
729    fn test_node_event_line_scalar() {
730        let event = NodeEvent::Scalar {
731            key: "name".to_string(),
732            value: Value::String("test".to_string().into()),
733            line: 25,
734        };
735        assert_eq!(event.line(), Some(25));
736    }
737
738    #[test]
739    fn test_node_event_line_object_start() {
740        let event = NodeEvent::ObjectStart {
741            key: "config".to_string(),
742            line: 50,
743        };
744        assert_eq!(event.line(), Some(50));
745    }
746
747    #[test]
748    fn test_node_event_line_list_end() {
749        let event = NodeEvent::ListEnd {
750            key: "users".to_string(),
751            type_name: "User".to_string(),
752            count: 10,
753        };
754        assert_eq!(event.line(), None);
755    }
756
757    #[test]
758    fn test_node_event_line_object_end() {
759        let event = NodeEvent::ObjectEnd {
760            key: "config".to_string(),
761        };
762        assert_eq!(event.line(), None);
763    }
764
765    #[test]
766    fn test_node_event_line_end_of_document() {
767        let event = NodeEvent::EndOfDocument;
768        assert_eq!(event.line(), None);
769    }
770
771    #[test]
772    fn test_node_event_clone() {
773        let event = NodeEvent::Scalar {
774            key: "key".to_string(),
775            value: Value::Int(42),
776            line: 10,
777        };
778        let cloned = event.clone();
779
780        if let NodeEvent::Scalar { key, value, line } = cloned {
781            assert_eq!(key, "key");
782            assert_eq!(value, Value::Int(42));
783            assert_eq!(line, 10);
784        } else {
785            panic!("Expected Scalar");
786        }
787    }
788
789    #[test]
790    fn test_node_event_debug() {
791        let event = NodeEvent::EndOfDocument;
792        let debug = format!("{event:?}");
793        assert!(debug.contains("EndOfDocument"));
794    }
795
796    #[test]
797    fn test_node_event_list_start_fields() {
798        let event = NodeEvent::ListStart {
799            key: "users".to_string(),
800            type_name: "User".to_string(),
801            schema: vec!["id".to_string(), "name".to_string()],
802            line: 5,
803        };
804
805        if let NodeEvent::ListStart {
806            key,
807            type_name,
808            schema,
809            line,
810        } = event
811        {
812            assert_eq!(key, "users");
813            assert_eq!(type_name, "User");
814            assert_eq!(schema, vec!["id".to_string(), "name".to_string()]);
815            assert_eq!(line, 5);
816        }
817    }
818
819    #[test]
820    fn test_node_event_list_end_fields() {
821        let event = NodeEvent::ListEnd {
822            key: "products".to_string(),
823            type_name: "Product".to_string(),
824            count: 100,
825        };
826
827        if let NodeEvent::ListEnd {
828            key,
829            type_name,
830            count,
831        } = event
832        {
833            assert_eq!(key, "products");
834            assert_eq!(type_name, "Product");
835            assert_eq!(count, 100);
836        }
837    }
838
839    #[test]
840    fn test_node_event_scalar_all_value_types() {
841        let events = [
842            NodeEvent::Scalar {
843                key: "null".to_string(),
844                value: Value::Null,
845                line: 1,
846            },
847            NodeEvent::Scalar {
848                key: "bool".to_string(),
849                value: Value::Bool(true),
850                line: 2,
851            },
852            NodeEvent::Scalar {
853                key: "int".to_string(),
854                value: Value::Int(-100),
855                line: 3,
856            },
857            NodeEvent::Scalar {
858                key: "float".to_string(),
859                value: Value::Float(2.75),
860                line: 4,
861            },
862            NodeEvent::Scalar {
863                key: "string".to_string(),
864                value: Value::String("text".to_string().into()),
865                line: 5,
866            },
867        ];
868
869        for (i, event) in events.iter().enumerate() {
870            assert_eq!(event.line(), Some(i + 1));
871            assert!(!event.is_node());
872        }
873    }
874}