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