1use crate::{
2 util::*, ConfigurationBuilder, ConfigurationPath, ConfigurationProvider, ConfigurationSource, FileSource,
3 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, String)>,
14 paths: Vec<String>,
15}
16
17impl JsonVisitor {
18 fn visit(mut self, root: &Map<String, JsonValue>) -> HashMap<String, (String, String)> {
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()));
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.insert(key.to_uppercase(), (key, value.to_string()));
59 }
60
61 fn enter_context(&mut self, context: String) {
62 if self.paths.is_empty() {
63 self.paths.push(context);
64 return;
65 }
66
67 let path = ConfigurationPath::combine(&[&self.paths[self.paths.len() - 1], &context]);
68 self.paths.push(path);
69 }
70
71 fn exit_context(&mut self) {
72 self.paths.pop();
73 }
74}
75
76struct InnerProvider {
77 file: FileSource,
78 data: RwLock<HashMap<String, (String, String)>>,
79 token: RwLock<SharedChangeToken<SingleChangeToken>>,
80}
81
82impl InnerProvider {
83 fn new(file: FileSource) -> Self {
84 Self {
85 file,
86 data: RwLock::new(HashMap::with_capacity(0)),
87 token: Default::default(),
88 }
89 }
90
91 fn load(&self, reload: bool) -> LoadResult {
92 if !self.file.path.is_file() {
93 if self.file.optional || reload {
94 let mut data = self.data.write().unwrap();
95 if !data.is_empty() {
96 *data = HashMap::with_capacity(0);
97 }
98
99 return Ok(());
100 } else {
101 return Err(LoadError::File {
102 message: format!(
103 "The configuration file '{}' was not found and is not optional.",
104 self.file.path.display()
105 ),
106 path: self.file.path.clone(),
107 });
108 }
109 }
110
111 let content = fs::read(&self.file.path).unwrap();
113 let json: JsonValue = serde_json::from_slice(&content).unwrap();
114
115 if let Some(root) = json.as_object() {
116 let visitor = JsonVisitor::default();
117 let data = visitor.visit(root);
118 *self.data.write().unwrap() = data;
119 } else if reload {
120 *self.data.write().unwrap() = HashMap::with_capacity(0);
121 } else {
122 return Err(LoadError::File {
123 message: format!(
124 "Top-level JSON element must be an object. Instead, '{}' was found.",
125 match json {
126 JsonValue::Array(_) => "array",
127 JsonValue::Bool(_) => "Boolean",
128 JsonValue::Null => "null",
129 JsonValue::Number(_) => "number",
130 JsonValue::String(_) => "string",
131 _ => unreachable!(),
132 }
133 ),
134 path: self.file.path.clone(),
135 });
136 }
137
138 let previous = std::mem::take(&mut *self.token.write().unwrap());
139
140 previous.notify();
141 Ok(())
142 }
143
144 fn get(&self, key: &str) -> Option<Value> {
145 self.data
146 .read()
147 .unwrap()
148 .get(&key.to_uppercase())
149 .map(|t| t.1.clone().into())
150 }
151
152 fn reload_token(&self) -> Box<dyn ChangeToken> {
153 Box::new(self.token.read().unwrap().clone())
154 }
155
156 fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
157 let data = self.data.read().unwrap();
158 accumulate_child_keys(&data, earlier_keys, parent_path)
159 }
160}
161
162pub struct JsonConfigurationProvider {
164 inner: Arc<InnerProvider>,
165 _subscription: Option<Box<dyn Subscription>>,
166}
167
168impl JsonConfigurationProvider {
169 pub fn new(file: FileSource) -> Self {
175 let path = file.path.clone();
176 let inner = Arc::new(InnerProvider::new(file));
177 let subscription: Option<Box<dyn Subscription>> = if inner.file.reload_on_change {
178 Some(Box::new(tokens::on_change(
179 move || FileChangeToken::new(path.clone()),
180 |state| {
181 let provider = state.unwrap();
182 std::thread::sleep(provider.file.reload_delay);
183 provider.load(true).ok();
184 },
185 Some(inner.clone()),
186 )))
187 } else {
188 None
189 };
190
191 Self {
192 inner,
193 _subscription: subscription,
194 }
195 }
196}
197
198impl ConfigurationProvider for JsonConfigurationProvider {
199 fn get(&self, key: &str) -> Option<Value> {
200 self.inner.get(key)
201 }
202
203 fn reload_token(&self) -> Box<dyn ChangeToken> {
204 self.inner.reload_token()
205 }
206
207 fn load(&mut self) -> LoadResult {
208 self.inner.load(false)
209 }
210
211 fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
212 self.inner.child_keys(earlier_keys, parent_path)
213 }
214}
215
216pub struct JsonConfigurationSource {
218 file: FileSource,
219}
220
221impl JsonConfigurationSource {
222 pub fn new(file: FileSource) -> Self {
228 Self { file }
229 }
230}
231
232impl ConfigurationSource for JsonConfigurationSource {
233 fn build(&self, _builder: &dyn ConfigurationBuilder) -> Box<dyn ConfigurationProvider> {
234 Box::new(JsonConfigurationProvider::new(self.file.clone()))
235 }
236}
237
238pub mod ext {
239
240 use super::*;
241
242 pub trait JsonConfigurationExtensions {
244 fn add_json_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self;
250 }
251
252 impl JsonConfigurationExtensions for dyn ConfigurationBuilder + '_ {
253 fn add_json_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self {
254 self.add(Box::new(JsonConfigurationSource::new(file.into())));
255 self
256 }
257 }
258
259 impl<T: ConfigurationBuilder> JsonConfigurationExtensions for T {
260 fn add_json_file<F: Into<FileSource>>(&mut self, file: F) -> &mut Self {
261 self.add(Box::new(JsonConfigurationSource::new(file.into())));
262 self
263 }
264 }
265}