| mod format; |
| pub mod source; |
| |
| use std::fmt::Debug; |
| use std::path::{Path, PathBuf}; |
| |
| use crate::error::{ConfigError, Result}; |
| use crate::map::Map; |
| use crate::source::Source; |
| use crate::value::Value; |
| use crate::Format; |
| |
| pub use self::format::FileFormat; |
| use self::source::FileSource; |
| |
| pub use self::source::file::FileSourceFile; |
| pub use self::source::string::FileSourceString; |
| |
| /// A configuration source backed up by a file. |
| /// |
| /// It supports optional automatic file format discovery. |
| #[derive(Clone, Debug)] |
| pub struct File<T, F> { |
| source: T, |
| |
| /// Format of file (which dictates what driver to use). |
| format: Option<F>, |
| |
| /// A required File will error if it cannot be found |
| required: bool, |
| } |
| |
| /// An extension of [`Format`](crate::Format) trait. |
| /// |
| /// Associates format with file extensions, therefore linking storage-agnostic notion of format to a file system. |
| pub trait FileStoredFormat: Format { |
| /// Returns a vector of file extensions, for instance `[yml, yaml]`. |
| fn file_extensions(&self) -> &'static [&'static str]; |
| } |
| |
| impl<F> File<source::string::FileSourceString, F> |
| where |
| F: FileStoredFormat + 'static, |
| { |
| pub fn from_str(s: &str, format: F) -> Self { |
| Self { |
| format: Some(format), |
| required: true, |
| source: s.into(), |
| } |
| } |
| } |
| |
| impl<F> File<source::file::FileSourceFile, F> |
| where |
| F: FileStoredFormat + 'static, |
| { |
| pub fn new(name: &str, format: F) -> Self { |
| Self { |
| format: Some(format), |
| required: true, |
| source: source::file::FileSourceFile::new(name.into()), |
| } |
| } |
| } |
| |
| impl File<source::file::FileSourceFile, FileFormat> { |
| /// Given the basename of a file, will attempt to locate a file by setting its |
| /// extension to a registered format. |
| pub fn with_name(name: &str) -> Self { |
| Self { |
| format: None, |
| required: true, |
| source: source::file::FileSourceFile::new(name.into()), |
| } |
| } |
| } |
| |
| impl<'a> From<&'a Path> for File<source::file::FileSourceFile, FileFormat> { |
| fn from(path: &'a Path) -> Self { |
| Self { |
| format: None, |
| required: true, |
| source: source::file::FileSourceFile::new(path.to_path_buf()), |
| } |
| } |
| } |
| |
| impl From<PathBuf> for File<source::file::FileSourceFile, FileFormat> { |
| fn from(path: PathBuf) -> Self { |
| Self { |
| format: None, |
| required: true, |
| source: source::file::FileSourceFile::new(path), |
| } |
| } |
| } |
| |
| impl<T, F> File<T, F> |
| where |
| F: FileStoredFormat + 'static, |
| T: FileSource<F>, |
| { |
| #[must_use] |
| pub fn format(mut self, format: F) -> Self { |
| self.format = Some(format); |
| self |
| } |
| |
| #[must_use] |
| pub fn required(mut self, required: bool) -> Self { |
| self.required = required; |
| self |
| } |
| } |
| |
| impl<T, F> Source for File<T, F> |
| where |
| F: FileStoredFormat + Debug + Clone + Send + Sync + 'static, |
| T: Sync + Send + FileSource<F> + 'static, |
| { |
| fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> { |
| Box::new((*self).clone()) |
| } |
| |
| fn collect(&self) -> Result<Map<String, Value>> { |
| // Coerce the file contents to a string |
| let (uri, contents, format) = match self |
| .source |
| .resolve(self.format.clone()) |
| .map_err(|err| ConfigError::Foreign(err)) |
| { |
| Ok(result) => (result.uri, result.content, result.format), |
| |
| Err(error) => { |
| if !self.required { |
| return Ok(Map::new()); |
| } |
| |
| return Err(error); |
| } |
| }; |
| |
| // Parse the string using the given format |
| format |
| .parse(uri.as_ref(), &contents) |
| .map_err(|cause| ConfigError::FileParse { uri, cause }) |
| } |
| } |