Skip to main content

sim_config/
view.rs

1//! Typed read view over a config table.
2
3use sim_kernel::Expr;
4use sim_value::access::{as_i64, as_str, field_any};
5
6use crate::{ConfigError, ConfigResult, ConfigTable};
7
8/// Borrowed typed accessors over one config table.
9#[derive(Clone, Copy, Debug)]
10pub struct ConfigView<'a> {
11    table: &'a ConfigTable,
12}
13
14impl<'a> ConfigView<'a> {
15    /// Creates a view over `table`.
16    pub fn new(table: &'a ConfigTable) -> Self {
17        Self { table }
18    }
19
20    /// Returns the raw expression for `key`.
21    pub fn get(&self, key: &str) -> Option<&'a Expr> {
22        field_any(&self.table.table, key)
23    }
24
25    /// Reads an optional string field.
26    pub fn string(&self, key: &str) -> Option<&'a str> {
27        self.get(key).and_then(as_str)
28    }
29
30    /// Reads a required string field.
31    pub fn required_string(&self, key: &str) -> ConfigResult<&'a str> {
32        self.string(key)
33            .ok_or_else(|| self.required_error(key, "a string"))
34    }
35
36    /// Reads an optional boolean field.
37    pub fn bool(&self, key: &str) -> Option<bool> {
38        match self.get(key) {
39            Some(Expr::Bool(value)) => Some(*value),
40            _ => None,
41        }
42    }
43
44    /// Reads a required boolean field.
45    pub fn required_bool(&self, key: &str) -> ConfigResult<bool> {
46        self.bool(key)
47            .ok_or_else(|| self.required_error(key, "a bool"))
48    }
49
50    /// Reads an optional integer field.
51    pub fn i64(&self, key: &str) -> Option<i64> {
52        self.get(key).and_then(as_i64)
53    }
54
55    /// Reads a required integer field.
56    pub fn required_i64(&self, key: &str) -> ConfigResult<i64> {
57        self.i64(key)
58            .ok_or_else(|| self.required_error(key, "an integer"))
59    }
60
61    /// Borrows a list field.
62    pub fn list(&self, key: &str) -> Option<&'a [Expr]> {
63        match self.get(key) {
64            Some(Expr::List(items)) => Some(items),
65            _ => None,
66        }
67    }
68
69    /// Borrows a nested table field.
70    pub fn table(&self, key: &str) -> Option<&'a [(Expr, Expr)]> {
71        match self.get(key) {
72            Some(Expr::Map(entries)) => Some(entries),
73            _ => None,
74        }
75    }
76
77    fn required_error(&self, key: &str, expected: &'static str) -> ConfigError {
78        if self.get(key).is_some() {
79            ConfigError::TypeMismatch {
80                key: key.to_owned(),
81                expected,
82            }
83        } else {
84            ConfigError::MissingField {
85                key: key.to_owned(),
86            }
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use sim_kernel::Symbol;
94    use sim_value::build::{int, map, text};
95
96    use super::*;
97
98    #[test]
99    fn view_reads_typed_fields() {
100        let table = ConfigTable::new(
101            Symbol::qualified("model", "defaults"),
102            map(vec![
103                ("provider", text("modeled")),
104                ("enabled", Expr::Bool(true)),
105                ("limit", int(3)),
106            ]),
107        )
108        .unwrap();
109        let view = ConfigView::new(&table);
110
111        assert_eq!(view.required_string("provider").unwrap(), "modeled");
112        assert!(view.required_bool("enabled").unwrap());
113        assert_eq!(view.required_i64("limit").unwrap(), 3);
114        assert_eq!(
115            view.required_string("limit").unwrap_err(),
116            ConfigError::TypeMismatch {
117                key: "limit".to_owned(),
118                expected: "a string"
119            }
120        );
121    }
122}