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 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
166pub struct JsonConfigurationProvider {
168 inner: Arc<InnerProvider>,
169 _subscription: Option<Box<dyn Subscription>>,
170}
171
172impl JsonConfigurationProvider {
173 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
220pub struct JsonConfigurationSource {
222 file: FileSource,
223}
224
225impl JsonConfigurationSource {
226 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 pub trait JsonConfigurationExtensions {
248 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}