dbc_rs/dbc/
value_descriptions_list.rs

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