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 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}