1use sim_kernel::Expr;
4use sim_value::access::{as_i64, as_str, field_any};
5
6use crate::{ConfigError, ConfigResult, ConfigTable};
7
8#[derive(Clone, Copy, Debug)]
10pub struct ConfigView<'a> {
11 table: &'a ConfigTable,
12}
13
14impl<'a> ConfigView<'a> {
15 pub fn new(table: &'a ConfigTable) -> Self {
17 Self { table }
18 }
19
20 pub fn get(&self, key: &str) -> Option<&'a Expr> {
22 field_any(&self.table.table, key)
23 }
24
25 pub fn string(&self, key: &str) -> Option<&'a str> {
27 self.get(key).and_then(as_str)
28 }
29
30 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 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 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 pub fn i64(&self, key: &str) -> Option<i64> {
52 self.get(key).and_then(as_i64)
53 }
54
55 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 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 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}