1use crate::{
2 util::*, ConfigurationBuilder, ConfigurationPath, ConfigurationProvider, ConfigurationSource,
3 FileSource, LoadError, LoadResult, Value,
4};
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::fs::File;
8use std::io::BufReader;
9use std::ops::Deref;
10use std::rc::Rc;
11use std::sync::{Arc, RwLock};
12use tokens::{ChangeToken, FileChangeToken, SharedChangeToken, SingleChangeToken, Subscription};
13use xml_rs::attribute::OwnedAttribute;
14use xml_rs::name::OwnedName;
15use xml_rs::reader::{EventReader, XmlEvent};
16
17trait LocalNameResolver {
18 fn local_name_or_error(&self, element: &OwnedName, line: usize) -> Result<String, String>;
19}
20
21impl LocalNameResolver for OwnedName {
22 fn local_name_or_error(&self, element: &OwnedName, line: usize) -> Result<String, String> {
23 if self.namespace.is_none() {
24 Ok(self.local_name.clone())
25 } else {
26 Err(format!(
27 "XML namespaces are not supported. ({}, Line: {})",
28 &element.local_name, line
29 ))
30 }
31 }
32}
33
34trait VecExtensions<TKey: PartialEq, TValue> {
35 fn get_or_add(&mut self, key: TKey) -> &mut TValue;
36}
37
38impl VecExtensions<String, Vec<Rc<RefCell<Element>>>> for Vec<(String, Vec<Rc<RefCell<Element>>>)> {
39 fn get_or_add(&mut self, key: String) -> &mut Vec<Rc<RefCell<Element>>> {
40 let index = self
41 .iter_mut()
42 .position(|i| &i.0 == &key)
43 .unwrap_or(self.len());
44
45 if index == self.len() {
46 self.push((key, Vec::new()));
47 }
48
49 &mut self[index].1
50 }
51}
52
53struct Attribute(String, String);
54
55struct Element {
56 line: usize,
57 element_name: String,
58 name: Option<String>,
59 sibling_name: String,
60 children: Vec<(String, Vec<Rc<RefCell<Element>>>)>,
61 text: Option<String>,
62 attributes: Vec<Attribute>,
63}
64
65impl Element {
66 fn new(
67 element_name: OwnedName,
68 attributes: Vec<OwnedAttribute>,
69 line: usize,
70 ) -> Result<Self, String> {
71 let name = get_name(&element_name, &attributes, line)?;
72 let local_name = element_name.local_name_or_error(&element_name, line)?;
73 let sibling_name = name
74 .as_ref()
75 .and_then(|n| {
76 Some(ConfigurationPath::combine(&[
77 &local_name.to_uppercase(),
78 &n.to_uppercase(),
79 ]))
80 })
81 .unwrap_or(local_name.to_uppercase());
82
83 Ok(Self {
84 line,
85 element_name: local_name,
86 name,
87 sibling_name,
88 children: Default::default(),
89 text: None,
90 attributes: attributes
91 .into_iter()
92 .map(|a| {
93 Ok(Attribute(
94 a.name.local_name_or_error(&element_name, line)?,
95 a.value,
96 ))
97 })
98 .collect::<Result<Vec<Attribute>, String>>()?,
99 })
100 }
101}
102
103#[derive(Default)]
104struct Prefix {
105 text: String,
106 lengths: Vec<usize>,
107}
108
109impl Prefix {
110 fn push<S: AsRef<str>>(&mut self, value: S) {
111 if self.text.is_empty() {
112 self.text.push_str(&value.as_ref());
113 self.lengths.push(value.as_ref().len());
114 } else {
115 self.text.push_str(ConfigurationPath::key_delimiter());
116 self.text.push_str(&value.as_ref());
117 self.lengths
118 .push(value.as_ref().len() + ConfigurationPath::key_delimiter().len());
119 }
120 }
121
122 fn pop(&mut self) {
123 if let Some(length) = self.lengths.pop() {
124 let idx = self.text.len() - length;
125 for _ in 0..length {
126 let _ = self.text.remove(idx);
127 }
128 }
129 }
130}
131
132impl ToString for Prefix {
133 fn to_string(&self) -> String {
134 self.text.clone()
135 }
136}
137
138fn get_name(
139 element: &OwnedName,
140 attributes: &Vec<OwnedAttribute>,
141 line: usize,
142) -> Result<Option<String>, String> {
143 for attribute in attributes {
144 let local_name = attribute.name.local_name_or_error(element, line)?;
145
146 match local_name.as_str() {
147 "name" | "Name" | "NAME" => {
148 return Ok(Some(attribute.value.clone()));
149 }
150 _ => {}
151 }
152 }
153
154 Ok(None)
155}
156
157fn process_element(
158 prefix: &mut Prefix,
159 element: &Element,
160 config: &mut HashMap<String, (String, Value)>,
161) -> Result<(), String> {
162 process_attributes(prefix, element, config)?;
163 process_element_content(prefix, element, config)?;
164 process_children(prefix, element, config)
165}
166
167fn process_element_content(
168 prefix: &mut Prefix,
169 element: &Element,
170 config: &mut HashMap<String, (String, Value)>,
171) -> Result<(), String> {
172 if let Some(ref value) = element.text {
173 add_to_config(prefix.to_string(), value.clone(), element, config)
174 } else {
175 Ok(())
176 }
177}
178
179fn process_element_child(
180 prefix: &mut Prefix,
181 child: &Element,
182 index: Option<usize>,
183 config: &mut HashMap<String, (String, Value)>,
184) -> Result<(), String> {
185 prefix.push(&child.element_name);
186
187 if let Some(ref name) = child.name {
188 prefix.push(name);
189 }
190
191 if let Some(i) = index {
192 prefix.push(i.to_string());
193 }
194
195 process_element(prefix, child, config)?;
196
197 if index.is_some() {
198 prefix.pop();
199 }
200
201 if child.name.is_some() {
202 prefix.pop();
203 }
204
205 prefix.pop();
206 Ok(())
207}
208
209fn process_attributes(
210 prefix: &mut Prefix,
211 element: &Element,
212 config: &mut HashMap<String, (String, Value)>,
213) -> Result<(), String> {
214 for attribute in &element.attributes {
215 prefix.push(&attribute.0);
216 add_to_config(prefix.to_string(), attribute.1.clone(), element, config)?;
217 prefix.pop();
218 }
219
220 Ok(())
221}
222
223fn process_children(
224 prefix: &mut Prefix,
225 element: &Element,
226 config: &mut HashMap<String, (String, Value)>,
227) -> Result<(), String> {
228 for children in element.children.iter().map(|i| &i.1) {
229 if children.len() == 1 {
230 process_element_child(prefix, &children[0].deref().borrow(), None, config)?;
231 } else {
232 for (i, child) in children.iter().enumerate() {
233 process_element_child(prefix, &child.deref().borrow(), Some(i), config)?;
234 }
235 }
236 }
237
238 Ok(())
239}
240
241fn add_to_config(
242 key: String,
243 value: String,
244 element: &Element,
245 config: &mut HashMap<String, (String, Value)>,
246) -> Result<(), String> {
247 if let Some((dup_key, _)) = config.insert(key.to_uppercase(), (key, value.into())) {
248 Err(format!(
249 "A duplicate key '{}' was found. ({}, Line: {})",
250 &dup_key, &element.element_name, element.line
251 ))
252 } else {
253 Ok(())
254 }
255}
256
257fn to_config(
258 mut root: Option<Rc<RefCell<Element>>>,
259) -> Result<HashMap<String, (String, Value)>, String> {
260 if let Some(cell) = root.take() {
261 let element = &cell.deref().borrow();
262 let mut data = HashMap::new();
263 let mut prefix = Prefix::default();
264
265 if let Some(ref name) = element.name {
266 prefix.push(name);
267 }
268
269 process_element(&mut prefix, &element, &mut data)?;
270 data.shrink_to_fit();
271 Ok(data)
272 } else {
273 Ok(HashMap::with_capacity(0))
274 }
275}
276
277fn visit(file: File) -> Result<HashMap<String, (String, Value)>, String> {
278 let content = BufReader::new(file);
279 let events = EventReader::new(content);
280 let mut has_content = false;
281 let mut last_name = None;
282 let mut line = 0;
283 let mut root = None;
284 let mut current = Vec::<(OwnedName, Rc<RefCell<Element>>)>::new();
285
286 for event in events.into_iter() {
287 match event {
288 Ok(XmlEvent::StartElement {
289 name, attributes, ..
290 }) => {
291 line += 1;
292 has_content = false;
293 last_name = Some(name.clone());
294 let element = Element::new(name.clone(), attributes, line)?;
295 let key = element.sibling_name.clone();
296 let child = Rc::new(RefCell::new(element));
297
298 if let Some(parent) = current.last() {
299 parent
300 .1
301 .borrow_mut()
302 .children
303 .get_or_add(key)
304 .push(child.clone());
305 } else {
306 root = Some(child.clone());
307 }
308
309 current.push((name, child));
310 }
311 Ok(XmlEvent::EndElement { name }) => {
312 if has_content {
313 if let Some(ref last) = last_name {
314 if last != &name {
315 line += 1;
316 }
317 }
318 }
319
320 if let Some((current_name, _)) = current.pop() {
321 last_name = Some(current_name);
322 }
323 }
324 Ok(XmlEvent::CData(text)) | Ok(XmlEvent::Characters(text)) => {
325 has_content = true;
326 if let Some(parent) = current.last() {
327 parent.1.borrow_mut().text = Some(text);
328 }
329 }
330 _ => {}
331 };
332 }
333
334 to_config(root)
335}
336
337struct InnerProvider {
338 file: FileSource,
339 data: RwLock<HashMap<String, (String, Value)>>,
340 token: RwLock<SharedChangeToken<SingleChangeToken>>,
341}
342
343impl InnerProvider {
344 fn new(file: FileSource) -> Self {
345 Self {
346 file,
347 data: RwLock::new(HashMap::with_capacity(0)),
348 token: Default::default(),
349 }
350 }
351
352 fn load(&self, reload: bool) -> LoadResult {
353 if !self.file.path.is_file() {
354 if self.file.optional || reload {
355 let mut data = self.data.write().unwrap();
356 if !data.is_empty() {
357 *data = HashMap::with_capacity(0);
358 }
359
360 return Ok(());
361 } else {
362 return Err(LoadError::File {
363 message: format!(
364 "The configuration file '{}' was not found and is not optional.",
365 self.file.path.display()
366 ),
367 path: self.file.path.clone(),
368 });
369 }
370 }
371
372 if let Ok(file) = File::open(&self.file.path) {
373 let data = visit(file).map_err(|e| LoadError::File {
374 message: e,
375 path: self.file.path.clone(),
376 })?;
377 *self.data.write().unwrap() = data;
378 } else {
379 *self.data.write().unwrap() = HashMap::with_capacity(0);
380 }
381
382 let previous = std::mem::replace(
383 &mut *self.token.write().unwrap(),
384 SharedChangeToken::default(),
385 );
386
387 previous.notify();
388 Ok(())
389 }
390
391 fn get(&self, key: &str) -> Option<Value> {
392 self.data
393 .read()
394 .unwrap()
395 .get(&key.to_uppercase())
396 .map(|t| t.1.clone())
397 }
398
399 fn reload_token(&self) -> Box<dyn ChangeToken> {
400 Box::new(self.token.read().unwrap().clone())
401 }
402
403 fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
404 let data = self.data.read().unwrap();
405 accumulate_child_keys(&data, earlier_keys, parent_path)
406 }
407}
408
409pub struct XmlConfigurationProvider {
411 inner: Arc<InnerProvider>,
412 _subscription: Option<Box<dyn Subscription>>,
413}
414
415impl XmlConfigurationProvider {
416 pub fn new(file: FileSource) -> Self {
422 let path = file.path.clone();
423 let inner = Arc::new(InnerProvider::new(file));
424 let subscription: Option<Box<dyn Subscription>> = if inner.file.reload_on_change {
425 Some(Box::new(tokens::on_change(
426 move || FileChangeToken::new(path.clone()),
427 |state| {
428 let provider = state.unwrap();
429 std::thread::sleep(provider.file.reload_delay);
430 provider.load(true).ok();
431 },
432 Some(inner.clone()),
433 )))
434 } else {
435 None
436 };
437
438 Self {
439 inner,
440 _subscription: subscription,
441 }
442 }
443}
444
445impl ConfigurationProvider for XmlConfigurationProvider {
446 fn get(&self, key: &str) -> Option<Value> {
447 self.inner.get(key)
448 }
449
450 fn reload_token(&self) -> Box<dyn ChangeToken> {
451 self.inner.reload_token()
452 }
453
454 fn load(&mut self) -> LoadResult {
455 self.inner.load(false)
456 }
457
458 fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
459 self.inner.child_keys(earlier_keys, parent_path)
460 }
461}
462
463pub struct XmlConfigurationSource {
465 file: FileSource,
466}
467
468impl XmlConfigurationSource {
469 pub fn new(file: FileSource) -> Self {
475 Self { file }
476 }
477}
478
479impl ConfigurationSource for XmlConfigurationSource {
480 fn build(&self, _builder: &dyn ConfigurationBuilder) -> Box<dyn ConfigurationProvider> {
481 Box::new(XmlConfigurationProvider::new(self.file.clone()))
482 }
483}
484
485pub mod ext {
486
487 use super::*;
488
489 pub trait XmlConfigurationExtensions {
491 fn add_xml_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self;
497 }
498
499 impl XmlConfigurationExtensions for dyn ConfigurationBuilder + '_ {
500 fn add_xml_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self {
501 self.add(Box::new(XmlConfigurationSource::new(file.into())));
502 self
503 }
504 }
505
506 impl<T: ConfigurationBuilder> XmlConfigurationExtensions for T {
507 fn add_xml_file<F: Into<FileSource>>(&mut self, file: F) -> &mut Self {
508 self.add(Box::new(XmlConfigurationSource::new(file.into())));
509 self
510 }
511 }
512}