Skip to main content

sofie_flags/
setting.rs

1use std::fmt;
2
3use serde_json::{Value, json};
4use sofie_core::{Error, Result};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum SettingValue {
8    Switch(bool),
9    Number(i64),
10    Text(String),
11}
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct DeclaredSetting {
15    /// `<module>.<setting>`, unique in the app.
16    pub name: String,
17    pub default: SettingValue,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct HeldSetting {
22    pub name: String,
23    pub value: SettingValue,
24    pub default: SettingValue,
25}
26
27impl SettingValue {
28    #[must_use]
29    pub fn same_kind(&self, other: &Self) -> bool {
30        std::mem::discriminant(self) == std::mem::discriminant(other)
31    }
32
33    pub fn parse_like(&self, text: &str) -> Result<Self> {
34        let trimmed = text.trim();
35        match self {
36            Self::Switch(_) => match trimmed {
37                "true" | "on" => Ok(Self::Switch(true)),
38                "false" | "off" => Ok(Self::Switch(false)),
39                _ => Err(Error::invalid("value", "is `true` or `false`")),
40            },
41            Self::Number(_) => trimmed
42                .parse()
43                .map(Self::Number)
44                .map_err(|_| Error::invalid("value", "is a whole number")),
45            Self::Text(_) => Ok(Self::Text(text.to_owned())),
46        }
47    }
48
49    pub(crate) fn to_json(&self) -> String {
50        match self {
51            Self::Switch(on) => json!({ "switch": on }),
52            Self::Number(number) => json!({ "number": number }),
53            Self::Text(text) => json!({ "text": text }),
54        }
55        .to_string()
56    }
57
58    pub(crate) fn from_json(name: &str, text: &str) -> Result<Self> {
59        let value: Value = serde_json::from_str(text).map_err(|error| {
60            Error::internal(
61                format!("reading the stored value of setting `{name}`"),
62                error,
63            )
64        })?;
65        let read = value.as_object().and_then(|object| {
66            let (kind, value) = object.iter().next()?;
67            match kind.as_str() {
68                "switch" => value.as_bool().map(Self::Switch),
69                "number" => value.as_i64().map(Self::Number),
70                "text" => value.as_str().map(|text| Self::Text(text.to_owned())),
71                _ => None,
72            }
73        });
74        read.ok_or_else(|| {
75            Error::internal_message(format!("the stored value of setting `{name}` is malformed"))
76        })
77    }
78}
79
80impl fmt::Display for SettingValue {
81    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82        match self {
83            Self::Switch(on) => write!(formatter, "{on}"),
84            Self::Number(number) => write!(formatter, "{number}"),
85            Self::Text(text) => formatter.write_str(text),
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::SettingValue;
93
94    #[test]
95    fn a_posted_value_is_read_as_its_declared_kind() {
96        let number = SettingValue::Number(5000);
97        assert_eq!(
98            number.parse_like(" 7500 ").unwrap(),
99            SettingValue::Number(7500)
100        );
101        assert!(number.parse_like("lots").is_err());
102        let switch = SettingValue::Switch(false);
103        assert_eq!(switch.parse_like("on").unwrap(), SettingValue::Switch(true));
104        assert!(switch.parse_like("1").is_err());
105        let text = SettingValue::Text(String::new());
106        assert_eq!(
107            text.parse_like(" a ").unwrap(),
108            SettingValue::Text(" a ".to_owned())
109        );
110    }
111
112    #[test]
113    fn a_value_survives_its_stored_form() {
114        for value in [
115            SettingValue::Switch(true),
116            SettingValue::Number(-3),
117            SettingValue::Text("hi \"there\"".to_owned()),
118        ] {
119            assert_eq!(
120                SettingValue::from_json("a.b", &value.to_json()).unwrap(),
121                value
122            );
123        }
124        assert!(SettingValue::from_json("a.b", "{\"number\":\"x\"}").is_err());
125    }
126}