dbc_rs/dbc/value_descriptions_list.rs
1#[cfg(any(feature = "alloc", feature = "kernel"))]
2use crate::value_descriptions::ValueDescriptions;
3
4/// Iterator over value descriptions in a ValueDescriptionsList
5#[cfg(any(feature = "alloc", feature = "kernel"))]
6struct ValueDescriptionsListIter<'a, 'b> {
7 entries: alloc::collections::btree_map::Iter<'b, (Option<u32>, &'a str), ValueDescriptions<'a>>,
8}
9
10#[cfg(any(feature = "alloc", feature = "kernel"))]
11impl<'a, 'b> Iterator for ValueDescriptionsListIter<'a, 'b> {
12 type Item = ((Option<u32>, &'a str), &'b ValueDescriptions<'a>);
13
14 #[inline]
15 fn next(&mut self) -> Option<Self::Item> {
16 self.entries.next().map(|(k, v)| (*k, v))
17 }
18}
19
20/// Encapsulates the value descriptions map for a DBC
21///
22/// Value descriptions map signal values to human-readable text descriptions.
23/// They can be message-specific (keyed by message_id and signal_name) or global
24/// (keyed by None and signal_name, applying to all signals with that name).
25#[cfg(any(feature = "alloc", feature = "kernel"))]
26#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
27pub struct ValueDescriptionsList<'a> {
28 value_descriptions: alloc::collections::BTreeMap<(Option<u32>, &'a str), ValueDescriptions<'a>>,
29}
30
31#[cfg(any(feature = "alloc", feature = "kernel"))]
32impl<'a> ValueDescriptionsList<'a> {
33 /// Create ValueDescriptionsList from a BTreeMap
34 pub(crate) fn from_map(
35 value_descriptions: alloc::collections::BTreeMap<
36 (Option<u32>, &'a str),
37 ValueDescriptions<'a>,
38 >,
39 ) -> Self {
40 Self { value_descriptions }
41 }
42
43 /// Create an empty ValueDescriptionsList
44 pub(crate) fn new() -> Self {
45 Self {
46 value_descriptions: alloc::collections::BTreeMap::new(),
47 }
48 }
49
50 /// Insert a value description entry
51 pub(crate) fn insert(&mut self, key: (Option<u32>, &'a str), value: ValueDescriptions<'a>) {
52 self.value_descriptions.insert(key, value);
53 }
54
55 /// Get an iterator over all value descriptions
56 ///
57 /// # Examples
58 ///
59 /// ```rust,no_run
60 /// use dbc_rs::Dbc;
61 ///
62 /// let dbc = Dbc::parse(r#"VERSION "1.0"
63 ///
64 /// BU_: ECM
65 ///
66 /// BO_ 100 Engine : 8 ECM
67 /// SG_ Gear : 0|8@1+ (1,0) [0|5] "" *
68 ///
69 /// VAL_ 100 Gear 0 "Park" 1 "Drive" ;"#)?;
70 /// for ((message_id, signal_name), value_descriptions) in dbc.value_descriptions().iter() {
71 /// println!("Message {:?}, Signal {}: {} entries", message_id, signal_name, value_descriptions.len());
72 /// }
73 /// # Ok::<(), dbc_rs::Error>(())
74 /// ```
75 #[inline]
76 #[must_use = "iterator is lazy and does nothing unless consumed"]
77 pub fn iter(
78 &self,
79 ) -> impl Iterator<Item = ((Option<u32>, &'a str), &ValueDescriptions<'a>)> + '_ {
80 ValueDescriptionsListIter {
81 entries: self.value_descriptions.iter(),
82 }
83 }
84
85 /// Get the number of value description entries
86 ///
87 /// # Examples
88 ///
89 /// ```rust,no_run
90 /// use dbc_rs::Dbc;
91 ///
92 /// let dbc = Dbc::parse(r#"VERSION "1.0"
93 ///
94 /// BU_: ECM
95 ///
96 /// BO_ 100 Engine : 8 ECM
97 /// SG_ Gear : 0|8@1+ (1,0) [0|5] "" *
98 ///
99 /// VAL_ 100 Gear 0 "Park" 1 "Drive" ;"#)?;
100 /// assert_eq!(dbc.value_descriptions().len(), 1);
101 /// # Ok::<(), dbc_rs::Error>(())
102 /// ```
103 #[inline]
104 #[must_use]
105 pub fn len(&self) -> usize {
106 self.value_descriptions.len()
107 }
108
109 /// Returns `true` if there are no value descriptions
110 ///
111 /// # Examples
112 ///
113 /// ```rust,no_run
114 /// use dbc_rs::Dbc;
115 ///
116 /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM")?;
117 /// assert!(dbc.value_descriptions().is_empty());
118 /// # Ok::<(), dbc_rs::Error>(())
119 /// ```
120 #[inline]
121 #[must_use]
122 pub fn is_empty(&self) -> bool {
123 self.value_descriptions.is_empty()
124 }
125
126 /// Get value descriptions for a specific signal
127 ///
128 /// This method first tries to find a message-specific value description,
129 /// then falls back to a global value description (if message_id is None in the map).
130 ///
131 /// # Arguments
132 ///
133 /// * `message_id` - The message ID
134 /// * `signal_name` - The signal name
135 ///
136 /// # Examples
137 ///
138 /// ```rust,no_run
139 /// use dbc_rs::Dbc;
140 ///
141 /// let dbc = Dbc::parse(r#"VERSION "1.0"
142 ///
143 /// BU_: ECM
144 ///
145 /// BO_ 100 Engine : 8 ECM
146 /// SG_ Gear : 0|8@1+ (1,0) [0|5] "" *
147 ///
148 /// VAL_ 100 Gear 0 "Park" 1 "Drive" ;"#)?;
149 /// if let Some(value_descriptions) = dbc.value_descriptions().for_signal(100, "Gear") {
150 /// assert_eq!(value_descriptions.get(0), Some("Park"));
151 /// }
152 /// # Ok::<(), dbc_rs::Error>(())
153 /// ```
154 #[inline]
155 #[must_use]
156 pub fn for_signal(&self, message_id: u32, signal_name: &str) -> Option<&ValueDescriptions<'a>> {
157 // First try to find a specific entry for this message_id
158 // Then fall back to a global entry (None message_id) that applies to all messages
159 // Priority: message-specific > global
160 // Note: We can't use get() directly because signal_name is &str but key uses &'a str
161 // So we iterate and match by string content
162 self.value_descriptions
163 .iter()
164 .find(|((id, name), _)| {
165 *name == signal_name
166 && match id {
167 Some(specific_id) => *specific_id == message_id,
168 None => false, // Check global entries separately
169 }
170 })
171 .map(|(_, v)| v)
172 .or_else(|| {
173 // Fall back to global entry (None message_id)
174 self.value_descriptions
175 .iter()
176 .find(|((id, name), _)| id.is_none() && *name == signal_name)
177 .map(|(_, v)| v)
178 })
179 }
180}