dbc_rs/message/
signal_list.rs

1use crate::{MAX_SIGNALS_PER_MESSAGE, Signal, compat::Vec};
2
3/// Encapsulates the signals array for a message
4///
5/// Uses `Vec<Signal>` for dynamic sizing.
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub struct SignalList {
8    signals: Vec<Signal, { MAX_SIGNALS_PER_MESSAGE }>,
9}
10
11impl From<&[Signal]> for SignalList {
12    fn from(signals: &[Signal]) -> Self {
13        Self::from_slice(signals)
14    }
15}
16
17#[cfg(feature = "std")]
18impl From<std::vec::Vec<Signal>> for SignalList {
19    fn from(signals: std::vec::Vec<Signal>) -> Self {
20        Self::from_slice(&signals)
21    }
22}
23
24impl From<Vec<Signal, { MAX_SIGNALS_PER_MESSAGE }>> for SignalList {
25    fn from(signals: Vec<Signal, { MAX_SIGNALS_PER_MESSAGE }>) -> Self {
26        Self::from_slice(signals.as_slice())
27    }
28}
29
30impl SignalList {
31    /// Create SignalList from a slice of signals by cloning them
32    pub(crate) fn from_slice(signals: &[Signal]) -> Self {
33        let count = signals.len().min(MAX_SIGNALS_PER_MESSAGE);
34        let signals_vec: Vec<Signal, { MAX_SIGNALS_PER_MESSAGE }> =
35            signals.iter().take(count).cloned().collect();
36        Self {
37            signals: signals_vec,
38        }
39    }
40
41    /// Get an iterator over the signals
42    ///
43    /// # Examples
44    ///
45    /// ```rust,no_run
46    /// use dbc_rs::Dbc;
47    ///
48    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM\n SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\"")?;
49    /// let message = dbc.messages().at(0).unwrap();
50    /// for signal in message.signals().iter() {
51    ///     println!("Signal: {} (start: {}, length: {})", signal.name(), signal.start_bit(), signal.length());
52    /// }
53    /// # Ok::<(), dbc_rs::Error>(())
54    /// ```
55    #[inline]
56    #[must_use = "iterator is lazy and does nothing unless consumed"]
57    pub fn iter(&self) -> impl Iterator<Item = &Signal> + '_ {
58        self.signals.iter()
59    }
60
61    /// Get the number of signals
62    ///
63    /// # Examples
64    ///
65    /// ```rust,no_run
66    /// use dbc_rs::Dbc;
67    ///
68    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM\n SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\"")?;
69    /// let message = dbc.messages().at(0).unwrap();
70    /// assert_eq!(message.signals().len(), 1);
71    /// # Ok::<(), dbc_rs::Error>(())
72    /// ```
73    #[inline]
74    #[must_use]
75    pub fn len(&self) -> usize {
76        self.signals.len()
77    }
78
79    /// Returns `true` if there are no signals
80    ///
81    /// # Examples
82    ///
83    /// ```rust,no_run
84    /// use dbc_rs::Dbc;
85    ///
86    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
87    /// let message = dbc.messages().at(0).unwrap();
88    /// assert!(message.signals().is_empty());
89    /// # Ok::<(), dbc_rs::Error>(())
90    /// ```
91    #[inline]
92    #[must_use]
93    pub fn is_empty(&self) -> bool {
94        self.len() == 0
95    }
96
97    /// Get a signal by index, or None if index is out of bounds
98    ///
99    /// # Examples
100    ///
101    /// ```rust,no_run
102    /// use dbc_rs::Dbc;
103    ///
104    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM\n SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\"")?;
105    /// let message = dbc.messages().at(0).unwrap();
106    /// if let Some(signal) = message.signals().at(0) {
107    ///     assert_eq!(signal.name(), "RPM");
108    /// }
109    /// # Ok::<(), dbc_rs::Error>(())
110    /// ```
111    #[inline]
112    #[must_use]
113    pub fn at(&self, index: usize) -> Option<&Signal> {
114        self.signals.get(index)
115    }
116
117    /// Find a signal by name, or None if not found
118    ///
119    /// # Examples
120    ///
121    /// ```rust,no_run
122    /// use dbc_rs::Dbc;
123    ///
124    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM\n SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\"")?;
125    /// let message = dbc.messages().at(0).unwrap();
126    /// if let Some(signal) = message.signals().find("RPM") {
127    ///     assert_eq!(signal.name(), "RPM");
128    ///     assert_eq!(signal.factor(), 0.25);
129    /// }
130    /// # Ok::<(), dbc_rs::Error>(())
131    /// ```
132    #[must_use]
133    pub fn find(&self, name: &str) -> Option<&Signal> {
134        self.iter().find(|s| s.name() == name)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::SignalList;
141    use crate::{Parser, Signal};
142
143    // Tests that require std feature (for from_slice)
144    #[cfg(feature = "std")]
145    mod tests_with_std {
146        use super::*;
147
148        #[test]
149        fn test_signals_from_slice() {
150            let signal1 = Signal::parse(
151                &mut Parser::new(b"SG_ Signal1 : 0|8@0+ (1,0) [0|255] \"\"").unwrap(),
152            )
153            .unwrap();
154            let signal2 = Signal::parse(
155                &mut Parser::new(b"SG_ Signal2 : 8|8@0+ (1,0) [0|255] \"\"").unwrap(),
156            )
157            .unwrap();
158
159            let signals = SignalList::from_slice(&[signal1, signal2]);
160            assert_eq!(signals.len(), 2);
161            assert!(!signals.is_empty());
162            assert_eq!(signals.at(0).unwrap().name(), "Signal1");
163            assert_eq!(signals.at(1).unwrap().name(), "Signal2");
164        }
165
166        #[test]
167        fn test_signals_from_signals_slice_empty() {
168            let signals = SignalList::from_slice(&[]);
169            assert_eq!(signals.len(), 0);
170            assert!(signals.is_empty());
171            assert!(signals.at(0).is_none());
172        }
173
174        #[test]
175        fn test_signals_from_slice_multiple() {
176            // Test with multiple signals to verify capacity handling
177            let signal1 = Signal::parse(
178                &mut Parser::new(b"SG_ Signal1 : 0|8@0+ (1,0) [0|255] \"\"").unwrap(),
179            )
180            .unwrap();
181            let signal2 = Signal::parse(
182                &mut Parser::new(b"SG_ Signal2 : 8|8@0+ (1,0) [0|255] \"\"").unwrap(),
183            )
184            .unwrap();
185            let signal3 = Signal::parse(
186                &mut Parser::new(b"SG_ Signal3 : 16|8@0+ (1,0) [0|255] \"\"").unwrap(),
187            )
188            .unwrap();
189
190            let signals = SignalList::from_slice(&[signal1, signal2, signal3]);
191            assert_eq!(signals.len(), 3);
192            assert_eq!(signals.at(0).unwrap().name(), "Signal1");
193            assert_eq!(signals.at(1).unwrap().name(), "Signal2");
194            assert_eq!(signals.at(2).unwrap().name(), "Signal3");
195        }
196    }
197
198    #[test]
199    fn test_signals_find_not_found() {
200        let signal1 =
201            Signal::parse(&mut Parser::new(b"SG_ Signal1 : 0|8@0+ (1,0) [0|255] \"\"").unwrap())
202                .unwrap();
203
204        let signals = SignalList::from_slice(&[signal1]);
205        assert!(signals.find("Nonexistent").is_none());
206        assert!(signals.find("").is_none());
207        assert!(signals.find("signal1").is_none()); // Case sensitive
208    }
209
210    #[test]
211    fn test_signals_find_first_match() {
212        let signal1 =
213            Signal::parse(&mut Parser::new(b"SG_ Signal1 : 0|8@0+ (1,0) [0|255] \"\"").unwrap())
214                .unwrap();
215        let signal2 =
216            Signal::parse(&mut Parser::new(b"SG_ Signal1 : 8|8@0+ (1,0) [0|255] \"\"").unwrap())
217                .unwrap(); // Same name (shouldn't happen in practice but test the behavior)
218
219        let signals = SignalList::from_slice(&[signal1, signal2]);
220        // Should find the first match
221        let found = signals.find("Signal1");
222        assert!(found.is_some());
223        assert_eq!(found.unwrap().start_bit(), 0); // First signal
224    }
225}