1use std::fmt::Debug;
2use std::path::{Path, PathBuf};
3
4use crate::Format;
5use crate::errors::{ConfigError, Result};
6use crate::map::Map;
7use crate::source::Source;
8use crate::value::Value;
9
10mod format;
11pub mod source;
12
13pub use self::format::FileFormat;
14use self::source::FileSource;
15pub use self::source::file::FileSourceFile;
16pub use self::source::string::FileSourceString;
17
18#[derive(Clone, Debug)]
22#[must_use]
23pub struct File<T, F> {
24 source: T,
25
26 format: Option<F>,
28
29 required: bool,
31}
32
33pub trait FileStoredFormat: Format {
37 fn file_extensions(&self) -> &'static [&'static str];
39}
40
41impl<F> File<source::string::FileSourceString, F>
42where
43 F: FileStoredFormat + 'static,
44{
45 pub fn from_str(s: &str, format: F) -> Self {
46 Self { format: Some(format), required: true, source: s.into() }
47 }
48}
49
50impl<F> File<source::file::FileSourceFile, F>
51where
52 F: FileStoredFormat + 'static,
53{
54 pub fn new(name: &str, format: F) -> Self {
55 Self {
56 format: Some(format),
57 required: true,
58 source: source::file::FileSourceFile::new(name.into()),
59 }
60 }
61}
62
63impl File<source::file::FileSourceFile, FileFormat> {
64 pub fn with_name(name: &str) -> Self {
67 Self { format: None, required: true, source: source::file::FileSourceFile::new(name.into()) }
68 }
69}
70
71impl<'a> From<&'a Path> for File<source::file::FileSourceFile, FileFormat> {
72 fn from(path: &'a Path) -> Self {
73 Self {
74 format: None,
75 required: true,
76 source: source::file::FileSourceFile::new(path.to_path_buf()),
77 }
78 }
79}
80
81impl From<PathBuf> for File<source::file::FileSourceFile, FileFormat> {
82 fn from(path: PathBuf) -> Self {
83 Self { format: None, required: true, source: source::file::FileSourceFile::new(path) }
84 }
85}
86
87impl<T, F> File<T, F>
88where
89 F: FileStoredFormat + 'static,
90 T: FileSource<F>,
91{
92 pub fn format(mut self, format: F) -> Self {
93 self.format = Some(format);
94 self
95 }
96
97 pub fn required(mut self, required: bool) -> Self {
99 self.required = required;
100 self
101 }
102}
103
104impl<T, F> Source for File<T, F>
105where
106 F: FileStoredFormat + Debug + Clone + Send + Sync + 'static,
107 T: Sync + Send + FileSource<F> + 'static,
108{
109 fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
110 Box::new((*self).clone())
111 }
112
113 fn collect(&self) -> Result<Map<String, Value>> {
114 let (uri, contents, format) =
116 match self.source.resolve(self.format.clone()).map_err(ConfigError::Foreign) {
117 Ok(result) => (result.uri, result.content, result.format),
118
119 Err(error) => {
120 if !self.required {
121 return Ok(Map::new());
122 }
123
124 return Err(error);
125 }
126 };
127
128 format.parse(uri.as_ref(), &contents).map_err(|cause| ConfigError::FileParse { uri, cause })
130 }
131}