config/
json.rs

1use crate::{
2    util::*, ConfigurationBuilder, ConfigurationPath, ConfigurationProvider, ConfigurationSource,
3    FileSource, LoadError, LoadResult, Value,
4};
5use serde_json::{map::Map, Value as JsonValue};
6use std::collections::HashMap;
7use std::fs;
8use std::sync::{Arc, RwLock};
9use tokens::{ChangeToken, FileChangeToken, SharedChangeToken, SingleChangeToken, Subscription};
10
11#[derive(Default)]
12struct JsonVisitor {
13    data: HashMap<String, (String, Value)>,
14    paths: Vec<String>,
15}
16
17impl JsonVisitor {
18    fn visit(mut self, root: &Map<String, JsonValue>) -> HashMap<String, (String, Value)> {
19        self.visit_element(root);
20        self.data.shrink_to_fit();
21        self.data
22    }
23
24    fn visit_element(&mut self, element: &Map<String, JsonValue>) {
25        if element.is_empty() {
26            if let Some(key) = self.paths.last() {
27                self.data
28                    .insert(key.to_uppercase(), (to_pascal_case(key), String::new().into()));
29            }
30        } else {
31            for (name, value) in element {
32                self.enter_context(to_pascal_case(name));
33                self.visit_value(value);
34                self.exit_context();
35            }
36        }
37    }
38
39    fn visit_value(&mut self, value: &JsonValue) {
40        match value {
41            JsonValue::Object(ref element) => self.visit_element(element),
42            JsonValue::Array(array) => {
43                for (index, element) in array.iter().enumerate() {
44                    self.enter_context(index.to_string());
45                    self.visit_value(element);
46                    self.exit_context();
47                }
48            }
49            JsonValue::Bool(value) => self.add_value(value),
50            JsonValue::Null => self.add_value(String::new()),
51            JsonValue::Number(value) => self.add_value(value),
52            JsonValue::String(value) => self.add_value(value),
53        }
54    }
55
56    fn add_value<T: ToString>(&mut self, value: T) {
57        let key = self.paths.last().unwrap().to_string();
58        self.data
59            .insert(key.to_uppercase(), (key, value.to_string().into()));
60    }
61
62    fn enter_context(&mut self, context: String) {
63        if self.paths.is_empty() {
64            self.paths.push(context);
65            return;
66        }
67
68        let path = ConfigurationPath::combine(&[&self.paths[self.paths.len() - 1], &context]);
69        self.paths.push(path);
70    }
71
72    fn exit_context(&mut self) {
73        self.paths.pop();
74    }
75}
76
77struct InnerProvider {
78    file: FileSource,
79    data: RwLock<HashMap<String, (String, Value)>>,
80    token: RwLock<SharedChangeToken<SingleChangeToken>>,
81}
82
83impl InnerProvider {
84    fn new(file: FileSource) -> Self {
85        Self {
86            file,
87            data: RwLock::new(HashMap::with_capacity(0)),
88            token: Default::default(),
89        }
90    }
91
92    fn load(&self, reload: bool) -> LoadResult {
93        if !self.file.path.is_file() {
94            if self.file.optional || reload {
95                let mut data = self.data.write().unwrap();
96                if !data.is_empty() {
97                    *data = HashMap::with_capacity(0);
98                }
99
100                return Ok(());
101            } else {
102                return Err(LoadError::File {
103                    message: format!(
104                        "The configuration file '{}' was not found and is not optional.",
105                        self.file.path.display()
106                    ),
107                    path: self.file.path.clone(),
108                });
109            }
110        }
111
112        // REF: https://docs.serde.rs/serde_json/de/fn.from_reader.html
113        let content = fs::read(&self.file.path).unwrap();
114        let json: JsonValue = serde_json::from_slice(&content).unwrap();
115
116        if let Some(root) = json.as_object() {
117            let visitor = JsonVisitor::default();
118            let data = visitor.visit(root);
119            *self.data.write().unwrap() = data;
120        } else if reload {
121            *self.data.write().unwrap() = HashMap::with_capacity(0);
122        } else {
123            return Err(LoadError::File {
124                message: format!(
125                    "Top-level JSON element must be an object. Instead, '{}' was found.",
126                    match json {
127                        JsonValue::Array(_) => "array",
128                        JsonValue::Bool(_) => "Boolean",
129                        JsonValue::Null => "null",
130                        JsonValue::Number(_) => "number",
131                        JsonValue::String(_) => "string",
132                        _ => unreachable!(),
133                    }
134                ),
135                path: self.file.path.clone(),
136            });
137        }
138
139        let previous = std::mem::replace(
140            &mut *self.token.write().unwrap(),
141            SharedChangeToken::default(),
142        );
143
144        previous.notify();
145        Ok(())
146    }
147
148    fn get(&self, key: &str) -> Option<Value> {
149        self.data
150            .read()
151            .unwrap()
152            .get(&key.to_uppercase())
153            .map(|t| t.1.clone())
154    }
155
156    fn reload_token(&self) -> Box<dyn ChangeToken> {
157        Box::new(self.token.read().unwrap().clone())
158    }
159
160    fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
161        let data = self.data.read().unwrap();
162        accumulate_child_keys(&data, earlier_keys, parent_path)
163    }
164}
165
166/// Represents a [`ConfigurationProvider`](crate::ConfigurationProvider) for `*.json` files.
167pub struct JsonConfigurationProvider {
168    inner: Arc<InnerProvider>,
169    _subscription: Option<Box<dyn Subscription>>,
170}
171
172impl JsonConfigurationProvider {
173    /// Initializes a new `*.json` file configuration provider.
174    ///
175    /// # Arguments
176    ///
177    /// * `file` - The `*.json` [`FileSource`](crate::FileSource) information
178    pub fn new(file: FileSource) -> Self {
179        let path = file.path.clone();
180        let inner = Arc::new(InnerProvider::new(file));
181        let subscription: Option<Box<dyn Subscription>> = if inner.file.reload_on_change {
182            Some(Box::new(tokens::on_change(
183                move || FileChangeToken::new(path.clone()),
184                |state| {
185                    let provider = state.unwrap();
186                    std::thread::sleep(provider.file.reload_delay);
187                    provider.load(true).ok();
188                },
189                Some(inner.clone())
190            )))
191        } else {
192            None
193        };
194
195        Self {
196            inner,
197            _subscription: subscription,
198        }
199    }
200}
201
202impl ConfigurationProvider for JsonConfigurationProvider {
203    fn get(&self, key: &str) -> Option<Value> {
204        self.inner.get(key)
205    }
206
207    fn reload_token(&self) -> Box<dyn ChangeToken> {
208        self.inner.reload_token()
209    }
210
211    fn load(&mut self) -> LoadResult {
212        self.inner.load(false)
213    }
214
215    fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
216        self.inner.child_keys(earlier_keys, parent_path)
217    }
218}
219
220/// Represents a [`ConfigurationSource`](crate::ConfigurationSource) for `*.json` files.
221pub struct JsonConfigurationSource {
222    file: FileSource,
223}
224
225impl JsonConfigurationSource {
226    /// Initializes a new `*.json` file configuration source.
227    ///
228    /// # Arguments
229    ///
230    /// * `file` - The `*.json` [`FileSource`](crate::FileSource) information
231    pub fn new(file: FileSource) -> Self {
232        Self { file }
233    }
234}
235
236impl ConfigurationSource for JsonConfigurationSource {
237    fn build(&self, _builder: &dyn ConfigurationBuilder) -> Box<dyn ConfigurationProvider> {
238        Box::new(JsonConfigurationProvider::new(self.file.clone()))
239    }
240}
241
242pub mod ext {
243
244    use super::*;
245
246    /// Defines extension methods for [`ConfigurationBuilder`](crate::ConfigurationBuilder).
247    pub trait JsonConfigurationExtensions {
248        /// Adds a `*.json` file as a configuration source.
249        ///
250        /// # Arguments
251        ///
252        /// * `file` - The `*.json` [`FileSource`](crate::FileSource) information
253        fn add_json_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self;
254    }
255
256    impl JsonConfigurationExtensions for dyn ConfigurationBuilder + '_ {
257        fn add_json_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self {
258            self.add(Box::new(JsonConfigurationSource::new(file.into())));
259            self
260        }
261    }
262
263    impl<T: ConfigurationBuilder> JsonConfigurationExtensions for T {
264        fn add_json_file<F: Into<FileSource>>(&mut self, file: F) -> &mut Self {
265            self.add(Box::new(JsonConfigurationSource::new(file.into())));
266            self
267        }
268    }
269}