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}