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
78pub struct Provider(FileSource);
80
81impl Provider {
82 #[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 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}