sbp_settings/
setting.rs

1use std::{borrow::Cow, fmt, fs, io, path::Path};
2
3use log::warn;
4use once_cell::sync::OnceCell;
5use serde::{
6    de::{self, Unexpected},
7    Deserialize, Deserializer,
8};
9
10static SETTINGS: OnceCell<Vec<Setting>> = OnceCell::new();
11
12fn bundled_settings() -> Vec<Setting> {
13    let settings_yaml = include_str!(concat!(env!("OUT_DIR"), "/settings.yaml"));
14    serde_yaml::from_str(settings_yaml).expect("could not parse settings.yaml")
15}
16
17pub fn load_from_path(path: impl AsRef<Path>) -> Result<(), LoadFromPathError> {
18    let file = fs::File::open(path).map_err(LoadFromPathError::Io)?;
19    let settings: Vec<serde_yaml::Mapping> =
20        serde_yaml::from_reader(file).map_err(LoadFromPathError::Serde)?;
21    let type_key = serde_yaml::Value::String("type".into());
22    let settings = settings
23        .into_iter()
24        .map(|mut map| {
25            if map.contains_key(&type_key) {
26                serde_yaml::from_value(map.into())
27            } else {
28                map.insert(type_key.clone(), serde_yaml::Value::String("string".into()));
29                let setting: Setting = serde_yaml::from_value(map.into())?;
30                warn!(
31                    "Missing `type` field for {} -> {}",
32                    setting.group, setting.name
33                );
34                Ok(setting)
35            }
36        })
37        .collect::<Result<Vec<_>, _>>()
38        .map_err(LoadFromPathError::Serde)?;
39    load(settings).map_err(|_| LoadFromPathError::AlreadySet)
40}
41
42pub fn load(settings: Vec<Setting>) -> Result<(), Vec<Setting>> {
43    SETTINGS.set(settings)
44}
45
46#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize)]
47pub struct Setting {
48    pub name: String,
49
50    pub group: String,
51
52    #[serde(rename = "type")]
53    pub kind: SettingKind,
54
55    #[serde(deserialize_with = "deserialize_bool", default)]
56    pub expert: bool,
57
58    #[serde(deserialize_with = "deserialize_bool", default)]
59    pub readonly: bool,
60
61    #[serde(rename = "Description")]
62    pub description: Option<String>,
63
64    #[serde(
65        alias = "default value",
66        deserialize_with = "deserialize_string",
67        default
68    )]
69    pub default_value: Option<String>,
70
71    #[serde(rename = "Notes")]
72    pub notes: Option<String>,
73
74    #[serde(deserialize_with = "deserialize_string", default)]
75    pub units: Option<String>,
76
77    #[serde(
78        rename = "enumerated possible values",
79        deserialize_with = "deserialize_string",
80        default
81    )]
82    pub enumerated_possible_values: Option<String>,
83
84    pub digits: Option<String>,
85}
86
87impl Setting {
88    pub fn all() -> &'static [Setting] {
89        let settings = SETTINGS.get_or_init(bundled_settings);
90        settings.as_slice()
91    }
92
93    pub fn find(group: impl AsRef<str>, name: impl AsRef<str>) -> Option<&'static Setting> {
94        let group = group.as_ref();
95        let name = name.as_ref();
96        Setting::all()
97            .iter()
98            .find(|s| s.group == group && s.name == name)
99    }
100
101    pub(crate) fn new(group: impl AsRef<str>, name: impl AsRef<str>) -> Cow<'static, Setting> {
102        Setting::find(&group, &name).map_or_else(
103            || {
104                let group = group.as_ref().to_owned();
105                let name = name.as_ref().to_owned();
106                warn!("No documentation entry setting {} -> {}", group, name);
107                Cow::Owned(Setting {
108                    group,
109                    name,
110                    ..Default::default()
111                })
112            },
113            Cow::Borrowed,
114        )
115    }
116
117    pub(crate) fn with_fmt_type(
118        group: impl AsRef<str>,
119        name: impl AsRef<str>,
120        fmt_type: impl AsRef<str>,
121    ) -> Cow<'static, Setting> {
122        let mut setting = Setting::new(group, name);
123        if setting.kind == SettingKind::Enum {
124            let mut parts = fmt_type.as_ref().splitn(2, ':');
125            let possible_values = parts.nth(1);
126            if let Some(p) = possible_values {
127                setting.to_mut().enumerated_possible_values = Some(p.to_owned());
128            }
129        }
130        setting
131    }
132}
133
134#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, Default)]
135pub enum SettingKind {
136    #[serde(rename = "integer", alias = "int")]
137    Integer,
138
139    #[serde(rename = "boolean", alias = "bool")]
140    Boolean,
141
142    #[serde(rename = "float")]
143    Float,
144
145    #[serde(rename = "double", alias = "Double")]
146    Double,
147
148    #[serde(rename = "string")]
149    #[default]
150    String,
151
152    #[serde(rename = "enum")]
153    Enum,
154
155    #[serde(rename = "packed bitfield")]
156    PackedBitfield,
157}
158
159impl SettingKind {
160    pub fn to_str(&self) -> &'static str {
161        match self {
162            SettingKind::Integer => "integer",
163            SettingKind::Boolean => "boolean",
164            SettingKind::Float => "float",
165            SettingKind::Double => "double",
166            SettingKind::String => "string",
167            SettingKind::Enum => "enum",
168            SettingKind::PackedBitfield => "packed bitfield",
169        }
170    }
171}
172
173#[derive(Debug, Clone, PartialEq)]
174pub enum SettingValue {
175    Integer(i64),
176    Boolean(bool),
177    Float(f32),
178    Double(f64),
179    String(String),
180}
181
182impl SettingValue {
183    pub fn parse(v: &str, kind: SettingKind) -> Option<Self> {
184        if v.is_empty() {
185            return None;
186        }
187        match kind {
188            SettingKind::Integer => v.parse().ok().map(SettingValue::Integer),
189            SettingKind::Boolean if v == "True" => Some(SettingValue::Boolean(true)),
190            SettingKind::Boolean if v == "False" => Some(SettingValue::Boolean(false)),
191            SettingKind::Float => v.parse().ok().map(SettingValue::Float),
192            SettingKind::Double => v.parse().ok().map(SettingValue::Double),
193            SettingKind::String | SettingKind::Enum | SettingKind::PackedBitfield => {
194                Some(SettingValue::String(v.to_owned()))
195            }
196            _ => None,
197        }
198    }
199}
200
201impl fmt::Display for SettingValue {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            SettingValue::Integer(s) => s.fmt(f),
205            // For consistency with the C version of libsettings, use the
206            // same boolean literals
207            SettingValue::Boolean(true) => write!(f, "True"),
208            SettingValue::Boolean(false) => write!(f, "False"),
209            SettingValue::Float(s) => s.fmt(f),
210            SettingValue::Double(s) => s.fmt(f),
211            SettingValue::String(s) => s.fmt(f),
212        }
213    }
214}
215
216fn deserialize_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
217where
218    D: Deserializer<'de>,
219{
220    struct BoolVisitor;
221
222    impl<'de> de::Visitor<'de> for BoolVisitor {
223        type Value = bool;
224
225        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
226            formatter.write_str("a bool or a string containing a bool")
227        }
228
229        fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
230        where
231            E: de::Error,
232        {
233            Ok(v)
234        }
235
236        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
237        where
238            E: de::Error,
239        {
240            match v {
241                "True" | "true" => Ok(true),
242                "False" | "false" => Ok(false),
243                other => Err(de::Error::invalid_value(
244                    Unexpected::Str(other),
245                    &"True or False",
246                )),
247            }
248        }
249    }
250
251    deserializer.deserialize_any(BoolVisitor)
252}
253
254fn deserialize_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
255where
256    D: Deserializer<'de>,
257{
258    struct StringVisitor;
259
260    impl<'de> de::Visitor<'de> for StringVisitor {
261        type Value = Option<String>;
262
263        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
264            formatter.write_str("an optional string")
265        }
266
267        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
268        where
269            E: de::Error,
270        {
271            match v {
272                "N/A" | "" => Ok(None),
273                _ => Ok(Some(v.to_owned())),
274            }
275        }
276
277        fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
278        where
279            E: de::Error,
280        {
281            Ok(Some(v.to_string()))
282        }
283
284        fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
285        where
286            E: de::Error,
287        {
288            Ok(Some(v.to_string()))
289        }
290
291        fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
292        where
293            E: de::Error,
294        {
295            Ok(Some(v.to_string()))
296        }
297
298        fn visit_unit<E>(self) -> Result<Self::Value, E>
299        where
300            E: de::Error,
301        {
302            Ok(None)
303        }
304    }
305
306    deserializer.deserialize_any(StringVisitor)
307}
308
309#[derive(Debug)]
310pub enum LoadFromPathError {
311    AlreadySet,
312    Io(io::Error),
313    Serde(serde_yaml::Error),
314}
315
316impl fmt::Display for LoadFromPathError {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        match self {
319            LoadFromPathError::AlreadySet => write!(f, "settings have already been loaded"),
320            LoadFromPathError::Io(e) => write!(f, "{}", e),
321            LoadFromPathError::Serde(e) => write!(f, "{}", e),
322        }
323    }
324}
325
326impl std::error::Error for LoadFromPathError {}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    #[test]
333    fn test_find_setting() {
334        assert_eq!(
335            Setting::find("solution", "soln_freq"),
336            Some(&Setting {
337                name: "soln_freq".to_string(),
338                group: "solution".to_string(),
339                kind: SettingKind::Integer,
340                expert: false,
341                readonly: false,
342                description: Some("The frequency at which GNSS navigation solution is computed.\n".to_string()),
343                default_value: Some("10".to_string()),
344                notes: Some("Minimum is 1 Hz. Maximum is 10 Hz for RTK positioning with a maximum of 15 used satellites.\nAt 5 Hz and lower the maximum number of used satellites is 22. 20 Hz is an absolute maximum with\na limit of 5 used satellites.\n\nSystem with inertial fusion (Duro Inertial, Piksi Multi Inertial) can output position at a higher rate\nthan the GNSS-only solution. See fused_soln_freq in the INS group.\n".to_string()),
345                units: Some("Hz".to_string()),
346                enumerated_possible_values: None,
347                digits: None
348            })
349        );
350
351        assert_eq!(Setting::find("solution", "froo_froo"), None);
352    }
353
354    #[test]
355    fn test_na_is_none() {
356        let setting = Setting::find("tcp_server0", "enabled_sbp_messages").unwrap();
357        assert_eq!(setting.units, None);
358    }
359
360    #[test]
361    fn test_bool_display() {
362        assert_eq!(SettingValue::Boolean(true).to_string(), "True");
363        assert_eq!(SettingValue::Boolean(false).to_string(), "False");
364    }
365}