Skip to main content

config/
json.rs

1use crate::{pascal_case, path, Error, FileSource, Result, Settings};
2use serde_json::{map::Map, Value as JsonValue};
3use std::fs;
4use tokens::{ChangeToken, FileChangeToken, NeverChangeToken};
5
6struct JsonVisitor<'a> {
7    settings: &'a mut Settings,
8    paths: Vec<String>,
9}
10
11impl<'a> JsonVisitor<'a> {
12    #[inline]
13    fn new(settings: &'a mut Settings) -> Self {
14        Self {
15            settings,
16            paths: Vec::new(),
17        }
18    }
19}
20
21impl JsonVisitor<'_> {
22    #[inline]
23    fn visit(mut self, root: &Map<String, JsonValue>) {
24        self.visit_element(root)
25    }
26
27    fn visit_element(&mut self, element: &Map<String, JsonValue>) {
28        if element.is_empty() {
29            if let Some(key) = self.paths.last() {
30                self.settings.insert(pascal_case(key), String::new());
31            }
32        } else {
33            for (name, value) in element {
34                self.enter_context(pascal_case(name));
35                self.visit_value(value);
36                self.exit_context();
37            }
38        }
39    }
40
41    fn visit_value(&mut self, value: &JsonValue) {
42        match value {
43            JsonValue::Object(ref element) => self.visit_element(element),
44            JsonValue::Array(array) => {
45                for (index, element) in array.iter().enumerate() {
46                    self.enter_context(index.to_string());
47                    self.visit_value(element);
48                    self.exit_context();
49                }
50            }
51            JsonValue::Bool(value) => self.add_value(value),
52            JsonValue::Null => self.add_value(String::new()),
53            JsonValue::Number(value) => self.add_value(value),
54            JsonValue::String(value) => self.add_value(value),
55        }
56    }
57
58    fn add_value<T: ToString>(&mut self, value: T) {
59        let key = self.paths.last().expect("no paths");
60        self.settings.insert(pascal_case(key), value.to_string());
61    }
62
63    fn enter_context(&mut self, context: String) {
64        if self.paths.is_empty() {
65            self.paths.push(context);
66        } else {
67            self.paths
68                .push(path::combine(&[&self.paths[self.paths.len() - 1], &context]));
69        }
70    }
71
72    #[inline]
73    fn exit_context(&mut self) {
74        self.paths.pop();
75    }
76}
77
78/// Represents a [configuration provider](crate::Provider) for `*.json` files.
79pub struct Provider(FileSource);
80
81impl Provider {
82    /// Initializes a new `*.json` file configuration provider.
83    ///
84    /// # Arguments
85    ///
86    /// * `file` - The `*.json` [file source](FileSource) information
87    #[inline]
88    pub fn new(file: FileSource) -> Self {
89        Self(file)
90    }
91}
92
93impl crate::Provider for Provider {
94    #[inline]
95    fn name(&self) -> &str {
96        path::provider(&self.0.path, "Json")
97    }
98
99    fn reload_token(&self) -> Box<dyn ChangeToken> {
100        if self.0.reload_on_change {
101            Box::new(FileChangeToken::new(self.0.path.clone()))
102        } else {
103            Box::new(NeverChangeToken)
104        }
105    }
106
107    fn load(&self, settings: &mut Settings) -> Result {
108        if !self.0.path.is_file() {
109            if self.0.optional {
110                return Ok(());
111            } else {
112                return Err(Error::MissingFile(self.0.path.clone()));
113            }
114        }
115
116        // REF: https://docs.serde.rs/serde_json/de/fn.from_reader.html
117        let content = fs::read(&self.0.path).map_err(Error::unknown)?;
118        let json: JsonValue = serde_json::from_slice(&content).map_err(Error::unknown)?;
119        let Some(root) = json.as_object() else {
120            return Err(Error::InvalidFile {
121                message: format!(
122                    "Top-level JSON element must be an object. Instead, '{}' was found.",
123                    match json {
124                        JsonValue::Array(_) => "array",
125                        JsonValue::Bool(_) => "Boolean",
126                        JsonValue::Null => "null",
127                        JsonValue::Number(_) => "number",
128                        JsonValue::String(_) => "string",
129                        _ => unreachable!(),
130                    }
131                ),
132                path: self.0.path.clone(),
133            });
134        };
135
136        JsonVisitor::new(settings).visit(root);
137        Ok(())
138    }
139}