drmem_api/types/device/
value.rs

1use crate::types::Error;
2use std::{convert::TryFrom, fmt, sync::Arc};
3
4/// Defines fundamental types that can be associated with a device.
5/// Drivers set the type for each device they manage and, for devices
6/// that can be set, only accept values of the correct type.
7
8#[derive(Clone, Debug, PartialEq)]
9pub enum Value {
10    /// For devices that return/accept a simple true/false, on/off,
11    /// etc., state.
12    Bool(bool),
13
14    /// For devices that return/accept an integer value. It is stored
15    /// as a signed, 32-bit. This type should primarily be used for
16    /// digital inputs/outputs. There is no 64-bit version because
17    /// Javascript doesn't support a 64-bit integer. For integer
18    /// values greater than 32 bits, use a `Flt` since it can
19    /// losslessly handle integers up to 52 bits.
20    Int(i32),
21
22    /// For devices that return/accept floating point numbers or
23    /// integers up to 52 bits.
24    Flt(f64),
25
26    /// For devices that return/accept text. Since strings can greatly
27    /// vary in size, care must be taken when returning this type. A
28    /// driver that returns strings rapidly should keep them short.
29    /// Longer strings should be returned at a slower rate. If the
30    /// system takes too much time serializing string data, it could
31    /// throw other portions of DrMem out of "soft real-time".
32    Str(Arc<str>),
33
34    /// For devices that render color values.
35    Color(palette::LinSrgba<u8>),
36}
37
38impl Value {
39    pub fn is_same_type(&self, o: &Value) -> bool {
40        matches!(
41            (self, o),
42            (Value::Bool(_), Value::Bool(_))
43                | (Value::Int(_), Value::Int(_))
44                | (Value::Flt(_), Value::Flt(_))
45                | (Value::Str(_), Value::Str(_))
46                | (Value::Color(_), Value::Color(_))
47        )
48    }
49}
50
51impl fmt::Display for Value {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match self {
54            Value::Bool(v) => write!(f, "{}", v),
55            Value::Int(v) => write!(f, "{}", v),
56            Value::Flt(v) => write!(f, "{}", v),
57            Value::Str(v) => write!(f, "\"{}\"", v),
58            Value::Color(v) => {
59                write!(f, "\"#{:02x}{:02x}{:02x}", v.red, v.green, v.blue)?;
60                if v.alpha < 255 {
61                    write!(f, "{:02x}", v.alpha)?;
62                }
63                write!(f, "\"")
64            }
65        }
66    }
67}
68
69impl TryFrom<Value> for bool {
70    type Error = Error;
71
72    fn try_from(value: Value) -> Result<Self, Self::Error> {
73        if let Value::Bool(v) = value {
74            Ok(v)
75        } else {
76            Err(Error::TypeError)
77        }
78    }
79}
80
81impl From<bool> for Value {
82    fn from(value: bool) -> Self {
83        Value::Bool(value)
84    }
85}
86
87impl TryFrom<Value> for i32 {
88    type Error = Error;
89
90    fn try_from(value: Value) -> Result<Self, Self::Error> {
91        if let Value::Int(v) = value {
92            return Ok(v);
93        }
94        Err(Error::TypeError)
95    }
96}
97
98impl From<i32> for Value {
99    fn from(value: i32) -> Self {
100        Value::Int(value)
101    }
102}
103
104impl TryFrom<Value> for i16 {
105    type Error = Error;
106
107    fn try_from(value: Value) -> Result<Self, Self::Error> {
108        if let Value::Int(v) = value {
109            if let Ok(v) = i16::try_from(v) {
110                return Ok(v);
111            }
112        }
113        Err(Error::TypeError)
114    }
115}
116
117impl From<i16> for Value {
118    fn from(value: i16) -> Self {
119        Value::Int(i32::from(value))
120    }
121}
122
123impl TryFrom<Value> for u16 {
124    type Error = Error;
125
126    fn try_from(value: Value) -> Result<Self, Self::Error> {
127        if let Value::Int(v) = value {
128            if let Ok(v) = u16::try_from(v) {
129                return Ok(v);
130            }
131        }
132        Err(Error::TypeError)
133    }
134}
135
136impl From<u16> for Value {
137    fn from(value: u16) -> Self {
138        Value::Int(i32::from(value))
139    }
140}
141
142impl TryFrom<Value> for f64 {
143    type Error = Error;
144
145    fn try_from(value: Value) -> Result<Self, Self::Error> {
146        if let Value::Flt(v) = value {
147            Ok(v)
148        } else {
149            Err(Error::TypeError)
150        }
151    }
152}
153
154impl From<f64> for Value {
155    fn from(value: f64) -> Self {
156        Value::Flt(value)
157    }
158}
159
160impl TryFrom<Value> for String {
161    type Error = Error;
162
163    fn try_from(value: Value) -> Result<Self, Self::Error> {
164        if let Value::Str(v) = value {
165            Ok(v.to_string())
166        } else {
167            Err(Error::TypeError)
168        }
169    }
170}
171
172impl TryFrom<Value> for Arc<str> {
173    type Error = Error;
174
175    fn try_from(value: Value) -> Result<Self, Self::Error> {
176        if let Value::Str(v) = value {
177            Ok(v)
178        } else {
179            Err(Error::TypeError)
180        }
181    }
182}
183
184impl From<String> for Value {
185    fn from(value: String) -> Self {
186        Value::Str(value.into())
187    }
188}
189
190impl From<Arc<str>> for Value {
191    fn from(value: Arc<str>) -> Self {
192        Value::Str(value)
193    }
194}
195
196impl From<&str> for Value {
197    fn from(value: &str) -> Self {
198        Value::Str(value.into())
199    }
200}
201
202impl From<palette::LinSrgba<u8>> for Value {
203    fn from(value: palette::LinSrgba<u8>) -> Self {
204        Value::Color(value)
205    }
206}
207
208impl TryFrom<Value> for palette::LinSrgba<u8> {
209    type Error = Error;
210
211    fn try_from(value: Value) -> Result<Self, Self::Error> {
212        if let Value::Color(v) = value {
213            Ok(v)
214        } else {
215            Err(Error::TypeError)
216        }
217    }
218}
219
220// Parses a color from a string. The only forms currently supported
221// are "#RRGGBB" and "#RRGGBBAA" where the red, green, blue, and alpha
222// portions are two hex digits. Even though this function takes a
223// slice, it's a private function and we know we only call it when the
224// slice has exactly 6 or 8 hex digits so we don't have to test to see
225// if the result exceeds 0xffffff.
226
227fn parse_color(s: &[u8]) -> Option<Value> {
228    let mut result = 0u32;
229
230    for ii in s {
231        if ii.is_ascii_digit() {
232            result = (result << 4) + (ii - b'0') as u32;
233        } else if (b'A'..=b'F').contains(ii) {
234            result = (result << 4) + (ii - b'A' + 10) as u32;
235        } else if (b'a'..=b'f').contains(ii) {
236            result = (result << 4) + (ii - b'a' + 10) as u32;
237        } else {
238            return None;
239        }
240    }
241
242    if s.len() == 6 {
243        result = (result << 8) + 255;
244    }
245
246    Some(Value::Color(palette::LinSrgba::new(
247        (result >> 24) as u8,
248        (result >> 16) as u8,
249        (result >> 8) as u8,
250        result as u8,
251    )))
252}
253
254impl TryFrom<&toml::value::Value> for Value {
255    type Error = Error;
256
257    fn try_from(value: &toml::value::Value) -> Result<Self, Self::Error> {
258        match value {
259            toml::value::Value::Boolean(v) => Ok(Value::Bool(*v)),
260            toml::value::Value::Integer(v) => i32::try_from(*v)
261                .map(Value::Int)
262                .map_err(|_| Error::TypeError),
263            toml::value::Value::Float(v) => Ok(Value::Flt(*v)),
264            toml::value::Value::String(v) => match v.as_bytes() {
265                tmp @ &[b'#', _, _, _, _, _, _]
266                | tmp @ &[b'#', _, _, _, _, _, _, _, _] => {
267                    if let Some(v) = parse_color(&tmp[1..]) {
268                        Ok(v)
269                    } else {
270                        Ok(Value::Str(v.to_owned().into()))
271                    }
272                }
273                _ => Ok(Value::Str(v.to_owned().into())),
274            },
275            _ => Err(Error::TypeError),
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283    use std::convert::TryFrom;
284
285    #[test]
286    fn test_device_values_to() {
287        assert_eq!("false", format!("{}", Value::Bool(false)));
288        assert_eq!("true", format!("{}", Value::Bool(true)));
289
290        assert_eq!("0", format!("{}", Value::Int(0)));
291        assert_eq!("1", format!("{}", Value::Int(1)));
292        assert_eq!("-1", format!("{}", Value::Int(-1)));
293        assert_eq!("-2147483648", format!("{}", Value::Int(-0x80000000)));
294        assert_eq!("2147483647", format!("{}", Value::Int(0x7fffffff)));
295
296        assert_eq!(
297            "\"#010203\"",
298            format!("{}", Value::Color(palette::LinSrgba::new(1, 2, 3, 255)))
299        );
300        assert_eq!(
301            "\"#01020304\"",
302            format!("{}", Value::Color(palette::LinSrgba::new(1, 2, 3, 4)))
303        );
304    }
305
306    #[test]
307    fn test_device_values_from() {
308        assert_eq!(Value::Bool(true), Value::from(true));
309        assert_eq!(Value::Bool(false), Value::from(false));
310
311        assert_eq!(Value::Int(0), Value::from(0i32));
312        assert_eq!(Value::Int(-1), Value::from(-1i32));
313        assert_eq!(Value::Int(2), Value::from(2i32));
314        assert_eq!(Value::Int(-3), Value::from(-3i16));
315        assert_eq!(Value::Int(4), Value::from(4u16));
316
317        assert_eq!(Value::Flt(5.0), Value::from(5.0f64));
318
319        assert_eq!(Value::Str("hello".into()), "hello".into());
320
321        // Cycle through 256 values.
322
323        for ii in 1..=255u8 {
324            let r: u8 = ii;
325            let g: u8 = ii ^ 0xa5u8;
326            let b: u8 = 255u8 - ii;
327            let a: u8 = ii ^ 0x81u8;
328
329            assert_eq!(
330                Value::Color(palette::LinSrgba::new(r, g, b, a)),
331                Value::from(palette::LinSrgba::new(r, g, b, a))
332            );
333        }
334    }
335
336    #[test]
337    fn test_device_values_tryfrom() {
338        // Check that we can convert bool values.
339
340        assert_eq!(bool::try_from(Value::Bool(true)), Ok(true));
341        assert!(bool::try_from(Value::Int(0)).is_err());
342        assert!(bool::try_from(Value::Flt(0.0)).is_err());
343        assert!(bool::try_from(Value::Str("hello".into())).is_err());
344
345        // Check that we can convert i32 values.
346
347        assert!(i32::try_from(Value::Bool(true)).is_err());
348        assert_eq!(i32::try_from(Value::Int(0x7fffffffi32)), Ok(0x7fffffffi32));
349        assert_eq!(
350            i32::try_from(Value::Int(-0x80000000i32)),
351            Ok(-0x80000000i32)
352        );
353        assert!(i32::try_from(Value::Flt(0.0)).is_err());
354        assert!(i32::try_from(Value::Str("hello".into())).is_err());
355
356        // Check that we can convert i16 values.
357
358        assert!(i16::try_from(Value::Bool(true)).is_err());
359        assert_eq!(i16::try_from(Value::Int(0x7fffi32)), Ok(0x7fffi16));
360        assert_eq!(i16::try_from(Value::Int(-0x8000i32)), Ok(-0x8000i16));
361        assert!(i16::try_from(Value::Int(0x8000i32)).is_err());
362        assert!(i16::try_from(Value::Int(-0x8000i32 - 1i32)).is_err());
363        assert!(i16::try_from(Value::Flt(0.0)).is_err());
364        assert!(i16::try_from(Value::Str("hello".into())).is_err());
365
366        // Check that we can convert u16 values.
367
368        assert!(u16::try_from(Value::Bool(true)).is_err());
369        assert_eq!(u16::try_from(Value::Int(0xffffi32)), Ok(0xffffu16));
370        assert_eq!(u16::try_from(Value::Int(0i32)), Ok(0u16));
371        assert!(u16::try_from(Value::Int(0x10000i32)).is_err());
372        assert!(u16::try_from(Value::Int(-1i32)).is_err());
373        assert!(u16::try_from(Value::Flt(0.0)).is_err());
374        assert!(u16::try_from(Value::Str("hello".into())).is_err());
375    }
376
377    #[test]
378    fn test_toml_value_tryfrom() {
379        assert_eq!(
380            Value::try_from(&toml::value::Value::Boolean(true)),
381            Ok(Value::Bool(true))
382        );
383        assert_eq!(
384            Value::try_from(&toml::value::Value::Boolean(false)),
385            Ok(Value::Bool(false))
386        );
387
388        assert_eq!(
389            Value::try_from(&toml::value::Value::Integer(0)),
390            Ok(Value::Int(0))
391        );
392        assert_eq!(
393            Value::try_from(&toml::value::Value::Integer(10)),
394            Ok(Value::Int(10))
395        );
396        assert_eq!(
397            Value::try_from(&toml::value::Value::Integer(-10)),
398            Ok(Value::Int(-10))
399        );
400        assert_eq!(
401            Value::try_from(&toml::value::Value::Integer(0x7fffffff)),
402            Ok(Value::Int(0x7fffffff))
403        );
404        assert_eq!(
405            Value::try_from(&toml::value::Value::Integer(-0x80000000)),
406            Ok(Value::Int(-0x80000000))
407        );
408        assert!(
409            Value::try_from(&toml::value::Value::Integer(-0x80000001)).is_err(),
410        );
411        assert!(
412            Value::try_from(&toml::value::Value::Integer(0x80000000)).is_err(),
413        );
414
415        assert_eq!(
416            Value::try_from(&toml::value::Value::Float(0.0)),
417            Ok(Value::Flt(0.0))
418        );
419        assert_eq!(
420            Value::try_from(&toml::value::Value::Float(10.0)),
421            Ok(Value::Flt(10.0))
422        );
423        assert_eq!(
424            Value::try_from(&toml::value::Value::Float(-10.0)),
425            Ok(Value::Flt(-10.0))
426        );
427
428        assert_eq!(
429            Value::try_from(&toml::value::Value::String("hello".into())),
430            Ok(Value::Str("hello".into()))
431        );
432
433        assert_eq!(
434            Value::try_from(&toml::value::Value::String("#".into())),
435            Ok(Value::Str("#".into()))
436        );
437        assert_eq!(
438            Value::try_from(&toml::value::Value::String("#1".into())),
439            Ok(Value::Str("#1".into()))
440        );
441        assert_eq!(
442            Value::try_from(&toml::value::Value::String("#12".into())),
443            Ok(Value::Str("#12".into()))
444        );
445        assert_eq!(
446            Value::try_from(&toml::value::Value::String("#123".into())),
447            Ok(Value::Str("#123".into()))
448        );
449        assert_eq!(
450            Value::try_from(&toml::value::Value::String("#1234".into())),
451            Ok(Value::Str("#1234".into()))
452        );
453        assert_eq!(
454            Value::try_from(&toml::value::Value::String("#12345".into())),
455            Ok(Value::Str("#12345".into()))
456        );
457        assert_eq!(
458            Value::try_from(&toml::value::Value::String("#1234567".into())),
459            Ok(Value::Str("#1234567".into()))
460        );
461        assert_eq!(
462            Value::try_from(&toml::value::Value::String("#123456789".into())),
463            Ok(Value::Str("#123456789".into()))
464        );
465        assert_eq!(
466            Value::try_from(&toml::value::Value::String("#1234567z".into())),
467            Ok(Value::Str("#1234567z".into()))
468        );
469
470        // Cycle through 256 semi-random colors. Make sure the parsing
471        // handles upper and lower case hex digits.
472
473        for ii in 1..=255u8 {
474            let r: u8 = ii;
475            let g: u8 = ii ^ 0xa5u8;
476            let b: u8 = 255u8 - ii;
477            let a: u8 = ii ^ 0xc3u8;
478
479            assert_eq!(
480                Value::try_from(&toml::value::Value::String(format!(
481                    "#{:02x}{:02x}{:02x}",
482                    r, g, b
483                )))
484                .unwrap(),
485                Value::Color(palette::LinSrgba::new(r, g, b, 255))
486            );
487            assert_eq!(
488                Value::try_from(&toml::value::Value::String(format!(
489                    "#{:02X}{:02X}{:02X}",
490                    r, g, b
491                )))
492                .unwrap(),
493                Value::Color(palette::LinSrgba::new(r, g, b, 255))
494            );
495            assert_eq!(
496                Value::try_from(&toml::value::Value::String(format!(
497                    "#{:02x}{:02x}{:02x}{:02x}",
498                    r, g, b, a
499                )))
500                .unwrap(),
501                Value::Color(palette::LinSrgba::new(r, g, b, a))
502            );
503            assert_eq!(
504                Value::try_from(&toml::value::Value::String(format!(
505                    "#{:02X}{:02X}{:02X}{:02X}",
506                    r, g, b, a
507                )))
508                .unwrap(),
509                Value::Color(palette::LinSrgba::new(r, g, b, a))
510            );
511        }
512
513        assert!(Value::try_from(&toml::value::Value::Datetime(
514            toml::value::Datetime {
515                date: None,
516                time: None,
517                offset: None
518            }
519        ))
520        .is_err());
521        assert!(Value::try_from(&toml::value::Value::Array(vec![])).is_err());
522        assert!(Value::try_from(&toml::value::Value::Table(
523            toml::map::Map::new()
524        ))
525        .is_err());
526    }
527}