1#[derive(Debug, Clone, PartialEq)]
3pub enum NDAttrSource {
4 Driver,
5 Param { port_name: String, param_name: String },
6 Constant,
7}
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum NDAttrDataType {
12 Int8,
13 UInt8,
14 Int16,
15 UInt16,
16 Int32,
17 UInt32,
18 Int64,
19 UInt64,
20 Float32,
21 Float64,
22 String,
23}
24
25#[derive(Debug, Clone, PartialEq)]
27pub enum NDAttrValue {
28 Int8(i8),
29 UInt8(u8),
30 Int16(i16),
31 UInt16(u16),
32 Int32(i32),
33 UInt32(u32),
34 Int64(i64),
35 UInt64(u64),
36 Float32(f32),
37 Float64(f64),
38 String(String),
39}
40
41impl NDAttrValue {
42 pub fn data_type(&self) -> NDAttrDataType {
43 match self {
44 Self::Int8(_) => NDAttrDataType::Int8,
45 Self::UInt8(_) => NDAttrDataType::UInt8,
46 Self::Int16(_) => NDAttrDataType::Int16,
47 Self::UInt16(_) => NDAttrDataType::UInt16,
48 Self::Int32(_) => NDAttrDataType::Int32,
49 Self::UInt32(_) => NDAttrDataType::UInt32,
50 Self::Int64(_) => NDAttrDataType::Int64,
51 Self::UInt64(_) => NDAttrDataType::UInt64,
52 Self::Float32(_) => NDAttrDataType::Float32,
53 Self::Float64(_) => NDAttrDataType::Float64,
54 Self::String(_) => NDAttrDataType::String,
55 }
56 }
57
58 pub fn as_f64(&self) -> Option<f64> {
59 match self {
60 Self::Int8(v) => Some(*v as f64),
61 Self::UInt8(v) => Some(*v as f64),
62 Self::Int16(v) => Some(*v as f64),
63 Self::UInt16(v) => Some(*v as f64),
64 Self::Int32(v) => Some(*v as f64),
65 Self::UInt32(v) => Some(*v as f64),
66 Self::Int64(v) => Some(*v as f64),
67 Self::UInt64(v) => Some(*v as f64),
68 Self::Float32(v) => Some(*v as f64),
69 Self::Float64(v) => Some(*v),
70 Self::String(_) => None,
71 }
72 }
73
74 pub fn as_i64(&self) -> Option<i64> {
75 match self {
76 Self::Int8(v) => Some(*v as i64),
77 Self::UInt8(v) => Some(*v as i64),
78 Self::Int16(v) => Some(*v as i64),
79 Self::UInt16(v) => Some(*v as i64),
80 Self::Int32(v) => Some(*v as i64),
81 Self::UInt32(v) => Some(*v as i64),
82 Self::Int64(v) => Some(*v),
83 Self::UInt64(v) => Some(*v as i64),
84 Self::Float32(v) => Some(*v as i64),
85 Self::Float64(v) => Some(*v as i64),
86 Self::String(_) => None,
87 }
88 }
89
90 pub fn as_string(&self) -> String {
91 match self {
92 Self::Int8(v) => v.to_string(),
93 Self::UInt8(v) => v.to_string(),
94 Self::Int16(v) => v.to_string(),
95 Self::UInt16(v) => v.to_string(),
96 Self::Int32(v) => v.to_string(),
97 Self::UInt32(v) => v.to_string(),
98 Self::Int64(v) => v.to_string(),
99 Self::UInt64(v) => v.to_string(),
100 Self::Float32(v) => v.to_string(),
101 Self::Float64(v) => v.to_string(),
102 Self::String(v) => v.clone(),
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct NDAttribute {
110 pub name: String,
111 pub description: String,
112 pub source: NDAttrSource,
113 pub value: NDAttrValue,
114}
115
116#[derive(Debug, Clone, Default)]
118pub struct NDAttributeList {
119 attrs: Vec<NDAttribute>,
120}
121
122impl NDAttributeList {
123 pub fn new() -> Self {
124 Self::default()
125 }
126
127 pub fn add(&mut self, attr: NDAttribute) {
128 if let Some(existing) = self.attrs.iter_mut().find(|a| a.name == attr.name) {
129 *existing = attr;
130 } else {
131 self.attrs.push(attr);
132 }
133 }
134
135 pub fn get(&self, name: &str) -> Option<&NDAttribute> {
136 self.attrs.iter().find(|a| a.name == name)
137 }
138
139 pub fn remove(&mut self, name: &str) -> bool {
140 let len_before = self.attrs.len();
141 self.attrs.retain(|a| a.name != name);
142 self.attrs.len() < len_before
143 }
144
145 pub fn clear(&mut self) {
146 self.attrs.clear();
147 }
148
149 pub fn iter(&self) -> impl Iterator<Item = &NDAttribute> {
150 self.attrs.iter()
151 }
152
153 pub fn len(&self) -> usize {
154 self.attrs.len()
155 }
156
157 pub fn is_empty(&self) -> bool {
158 self.attrs.is_empty()
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_add_get() {
168 let mut list = NDAttributeList::new();
169 list.add(NDAttribute {
170 name: "ColorMode".into(),
171 description: "Color mode".into(),
172 source: NDAttrSource::Driver,
173 value: NDAttrValue::Int32(0),
174 });
175 let attr = list.get("ColorMode").unwrap();
176 assert_eq!(attr.value, NDAttrValue::Int32(0));
177 }
178
179 #[test]
180 fn test_replace_existing() {
181 let mut list = NDAttributeList::new();
182 list.add(NDAttribute {
183 name: "Gain".into(),
184 description: "".into(),
185 source: NDAttrSource::Driver,
186 value: NDAttrValue::Float64(1.0),
187 });
188 list.add(NDAttribute {
189 name: "Gain".into(),
190 description: "".into(),
191 source: NDAttrSource::Driver,
192 value: NDAttrValue::Float64(2.5),
193 });
194 assert_eq!(list.len(), 1);
195 assert_eq!(list.get("Gain").unwrap().value, NDAttrValue::Float64(2.5));
196 }
197
198 #[test]
199 fn test_iter() {
200 let mut list = NDAttributeList::new();
201 list.add(NDAttribute {
202 name: "A".into(),
203 description: "".into(),
204 source: NDAttrSource::Constant,
205 value: NDAttrValue::Int32(1),
206 });
207 list.add(NDAttribute {
208 name: "B".into(),
209 description: "".into(),
210 source: NDAttrSource::Constant,
211 value: NDAttrValue::String("hello".into()),
212 });
213 let names: Vec<_> = list.iter().map(|a| a.name.as_str()).collect();
214 assert_eq!(names, vec!["A", "B"]);
215 }
216
217 #[test]
218 fn test_get_missing() {
219 let list = NDAttributeList::new();
220 assert!(list.get("nope").is_none());
221 }
222
223 #[test]
224 fn test_empty() {
225 let list = NDAttributeList::new();
226 assert!(list.is_empty());
227 assert_eq!(list.len(), 0);
228 }
229
230 #[test]
231 fn test_all_data_types() {
232 let values = vec![
233 NDAttrValue::Int8(-1),
234 NDAttrValue::UInt8(255),
235 NDAttrValue::Int16(-100),
236 NDAttrValue::UInt16(1000),
237 NDAttrValue::Int32(-50000),
238 NDAttrValue::UInt32(50000),
239 NDAttrValue::Int64(-1_000_000),
240 NDAttrValue::UInt64(1_000_000),
241 NDAttrValue::Float32(3.14),
242 NDAttrValue::Float64(2.718),
243 NDAttrValue::String("test".into()),
244 ];
245 for v in &values {
246 assert_eq!(v.data_type(), v.data_type());
247 }
248 }
249
250 #[test]
251 fn test_source_tracking() {
252 let attr = NDAttribute {
253 name: "temp".into(),
254 description: "temperature".into(),
255 source: NDAttrSource::Param {
256 port_name: "SIM1".into(),
257 param_name: "TEMPERATURE".into(),
258 },
259 value: NDAttrValue::Float64(25.0),
260 };
261 match &attr.source {
262 NDAttrSource::Param { port_name, param_name } => {
263 assert_eq!(port_name, "SIM1");
264 assert_eq!(param_name, "TEMPERATURE");
265 }
266 _ => panic!("wrong source type"),
267 }
268 }
269
270 #[test]
271 fn test_value_conversions() {
272 let v = NDAttrValue::Int32(42);
273 assert_eq!(v.as_f64(), Some(42.0));
274 assert_eq!(v.as_i64(), Some(42));
275 assert_eq!(v.as_string(), "42");
276
277 let s = NDAttrValue::String("hello".into());
278 assert_eq!(s.as_f64(), None);
279 assert_eq!(s.as_i64(), None);
280 assert_eq!(s.as_string(), "hello");
281 }
282
283 #[test]
284 fn test_remove() {
285 let mut list = NDAttributeList::new();
286 list.add(NDAttribute {
287 name: "A".into(),
288 description: "".into(),
289 source: NDAttrSource::Driver,
290 value: NDAttrValue::Int32(1),
291 });
292 assert!(list.remove("A"));
293 assert!(list.is_empty());
294 assert!(!list.remove("A"));
295 }
296}