dbc_rs/nodes/core.rs
1use super::Nodes;
2use crate::{
3 Error, MAX_NAME_SIZE, MAX_NODES, Result,
4 compat::{String, Vec},
5};
6
7impl Nodes {
8 // Shared validation function
9 pub(crate) fn validate(nodes: &[impl AsRef<str>]) -> Result<()> {
10 // Check for too many nodes (DoS protection)
11 if let Some(err) = crate::check_max_limit(
12 nodes.len(),
13 MAX_NODES,
14 Error::Validation(Error::NODES_TOO_MANY),
15 ) {
16 return Err(err);
17 }
18
19 // Check for duplicate node names (case-sensitive)
20 for (i, node1) in nodes.iter().enumerate() {
21 for node2 in nodes.iter().skip(i + 1) {
22 if node1.as_ref() == node2.as_ref() {
23 return Err(Error::Validation(Error::NODES_DUPLICATE_NAME));
24 }
25 }
26 }
27 Ok(())
28 }
29
30 pub(crate) fn new(nodes: Vec<String<{ MAX_NAME_SIZE }>, { MAX_NODES }>) -> Self {
31 // Validation should have been done prior (by builder)
32 Self { nodes }
33 }
34
35 /// Returns an iterator over the node names.
36 ///
37 /// # Examples
38 ///
39 /// ```rust,no_run
40 /// use dbc_rs::Dbc;
41 ///
42 /// let dbc = Dbc::parse(r#"VERSION "1.0"
43 ///
44 /// BU_: ECM TCM BCM
45 /// "#)?;
46 ///
47 /// // Iterate over nodes
48 /// let mut iter = dbc.nodes().iter();
49 /// assert_eq!(iter.next(), Some("ECM"));
50 /// assert_eq!(iter.next(), Some("TCM"));
51 /// assert_eq!(iter.next(), Some("BCM"));
52 /// assert_eq!(iter.next(), None);
53 ///
54 /// // Or use in a loop
55 /// for node in dbc.nodes().iter() {
56 /// println!("Node: {}", node);
57 /// }
58 /// # Ok::<(), dbc_rs::Error>(())
59 /// ```
60 #[inline]
61 #[must_use = "iterator is lazy and does nothing unless consumed"]
62 pub fn iter(&self) -> impl Iterator<Item = &str> + '_ {
63 self.nodes.iter().map(|s| s.as_str())
64 }
65
66 /// Checks if a node name is in the list.
67 ///
68 /// The check is case-sensitive.
69 ///
70 /// # Arguments
71 ///
72 /// * `node` - The node name to check
73 ///
74 /// # Examples
75 ///
76 /// ```rust,no_run
77 /// use dbc_rs::Dbc;
78 ///
79 /// let dbc = Dbc::parse(r#"VERSION "1.0"
80 ///
81 /// BU_: ECM TCM
82 /// "#)?;
83 ///
84 /// assert!(dbc.nodes().contains("ECM"));
85 /// assert!(dbc.nodes().contains("TCM"));
86 /// assert!(!dbc.nodes().contains("BCM"));
87 /// assert!(!dbc.nodes().contains("ecm")); // Case-sensitive
88 /// # Ok::<(), dbc_rs::Error>(())
89 /// ```
90 #[inline]
91 #[must_use]
92 pub fn contains(&self, node: &str) -> bool {
93 self.iter().any(|n| n == node)
94 }
95
96 /// Returns the number of nodes in the collection.
97 ///
98 /// # Examples
99 ///
100 /// ```rust,no_run
101 /// use dbc_rs::Dbc;
102 ///
103 /// let dbc = Dbc::parse(r#"VERSION "1.0"
104 ///
105 /// BU_: ECM TCM BCM
106 /// "#)?;
107 ///
108 /// assert_eq!(dbc.nodes().len(), 3);
109 /// # Ok::<(), dbc_rs::Error>(())
110 /// ```
111 #[inline]
112 #[must_use]
113 pub fn len(&self) -> usize {
114 self.nodes.len()
115 }
116
117 /// Returns `true` if there are no nodes in the collection.
118 ///
119 /// # Examples
120 ///
121 /// ```rust,no_run
122 /// use dbc_rs::Dbc;
123 ///
124 /// // Empty node list
125 /// let dbc = Dbc::parse(r#"VERSION "1.0"
126 ///
127 /// BU_:
128 /// "#)?;
129 /// assert!(dbc.nodes().is_empty());
130 ///
131 /// // With nodes
132 /// let dbc2 = Dbc::parse(r#"VERSION "1.0"
133 ///
134 /// BU_: ECM
135 /// "#)?;
136 /// assert!(!dbc2.nodes().is_empty());
137 /// # Ok::<(), dbc_rs::Error>(())
138 /// ```
139 pub fn is_empty(&self) -> bool {
140 self.nodes.is_empty()
141 }
142
143 /// Gets a node by index.
144 ///
145 /// Returns `None` if the index is out of bounds.
146 ///
147 /// # Arguments
148 ///
149 /// * `index` - The zero-based index of the node
150 ///
151 /// # Examples
152 ///
153 /// ```rust,no_run
154 /// use dbc_rs::Dbc;
155 ///
156 /// let dbc = Dbc::parse(r#"VERSION "1.0"
157 ///
158 /// BU_: ECM TCM BCM
159 /// "#)?;
160 ///
161 /// assert_eq!(dbc.nodes().at(0), Some("ECM"));
162 /// assert_eq!(dbc.nodes().at(1), Some("TCM"));
163 /// assert_eq!(dbc.nodes().at(2), Some("BCM"));
164 /// assert_eq!(dbc.nodes().at(3), None); // Out of bounds
165 /// # Ok::<(), dbc_rs::Error>(())
166 /// ```
167 #[inline]
168 #[must_use]
169 pub fn at(&self, index: usize) -> Option<&str> {
170 self.nodes.get(index).map(|s| s.as_str())
171 }
172}