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}