1 mod format;
2 pub mod source;
3 
4 use std::fmt::Debug;
5 use std::path::{Path, PathBuf};
6 
7 use crate::error::{ConfigError, Result};
8 use crate::map::Map;
9 use crate::source::Source;
10 use crate::value::Value;
11 use crate::Format;
12 
13 pub use self::format::FileFormat;
14 use self::source::FileSource;
15 
16 pub use self::source::file::FileSourceFile;
17 pub use self::source::string::FileSourceString;
18 
19 /// A configuration source backed up by a file.
20 ///
21 /// It supports optional automatic file format discovery.
22 #[derive(Clone, Debug)]
23 pub struct File<T, F> {
24     source: T,
25 
26     /// Format of file (which dictates what driver to use).
27     format: Option<F>,
28 
29     /// A required File will error if it cannot be found
30     required: bool,
31 }
32 
33 /// An extension of [`Format`](crate::Format) trait.
34 ///
35 /// Associates format with file extensions, therefore linking storage-agnostic notion of format to a file system.
36 pub trait FileStoredFormat: Format {
37     /// Returns a vector of file extensions, for instance `[yml, yaml]`.
file_extensions(&self) -> &'static [&'static str]38     fn file_extensions(&self) -> &'static [&'static str];
39 }
40 
41 impl<F> File<source::string::FileSourceString, F>
42 where
43     F: FileStoredFormat + 'static,
44 {
from_str(s: &str, format: F) -> Self45     pub fn from_str(s: &str, format: F) -> Self {
46         Self {
47             format: Some(format),
48             required: true,
49             source: s.into(),
50         }
51     }
52 }
53 
54 impl<F> File<source::file::FileSourceFile, F>
55 where
56     F: FileStoredFormat + 'static,
57 {
new(name: &str, format: F) -> Self58     pub fn new(name: &str, format: F) -> Self {
59         Self {
60             format: Some(format),
61             required: true,
62             source: source::file::FileSourceFile::new(name.into()),
63         }
64     }
65 }
66 
67 impl File<source::file::FileSourceFile, FileFormat> {
68     /// Given the basename of a file, will attempt to locate a file by setting its
69     /// extension to a registered format.
with_name(name: &str) -> Self70     pub fn with_name(name: &str) -> Self {
71         Self {
72             format: None,
73             required: true,
74             source: source::file::FileSourceFile::new(name.into()),
75         }
76     }
77 }
78 
79 impl<'a> From<&'a Path> for File<source::file::FileSourceFile, FileFormat> {
from(path: &'a Path) -> Self80     fn from(path: &'a Path) -> Self {
81         Self {
82             format: None,
83             required: true,
84             source: source::file::FileSourceFile::new(path.to_path_buf()),
85         }
86     }
87 }
88 
89 impl From<PathBuf> for File<source::file::FileSourceFile, FileFormat> {
from(path: PathBuf) -> Self90     fn from(path: PathBuf) -> Self {
91         Self {
92             format: None,
93             required: true,
94             source: source::file::FileSourceFile::new(path),
95         }
96     }
97 }
98 
99 impl<T, F> File<T, F>
100 where
101     F: FileStoredFormat + 'static,
102     T: FileSource<F>,
103 {
104     #[must_use]
format(mut self, format: F) -> Self105     pub fn format(mut self, format: F) -> Self {
106         self.format = Some(format);
107         self
108     }
109 
110     #[must_use]
required(mut self, required: bool) -> Self111     pub fn required(mut self, required: bool) -> Self {
112         self.required = required;
113         self
114     }
115 }
116 
117 impl<T, F> Source for File<T, F>
118 where
119     F: FileStoredFormat + Debug + Clone + Send + Sync + 'static,
120     T: Sync + Send + FileSource<F> + 'static,
121 {
clone_into_box(&self) -> Box<dyn Source + Send + Sync>122     fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
123         Box::new((*self).clone())
124     }
125 
collect(&self) -> Result<Map<String, Value>>126     fn collect(&self) -> Result<Map<String, Value>> {
127         // Coerce the file contents to a string
128         let (uri, contents, format) = match self
129             .source
130             .resolve(self.format.clone())
131             .map_err(|err| ConfigError::Foreign(err))
132         {
133             Ok(result) => (result.uri, result.content, result.format),
134 
135             Err(error) => {
136                 if !self.required {
137                     return Ok(Map::new());
138                 }
139 
140                 return Err(error);
141             }
142         };
143 
144         // Parse the string using the given format
145         format
146             .parse(uri.as_ref(), &contents)
147             .map_err(|cause| ConfigError::FileParse { uri, cause })
148     }
149 }
150