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 { source: T, /// Format of file (which dictates what driver to use). format: Option, /// 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 File where F: FileStoredFormat + 'static, { pub fn from_str(s: &str, format: F) -> Self { Self { format: Some(format), required: true, source: s.into(), } } } impl File 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 { /// 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 { fn from(path: &'a Path) -> Self { Self { format: None, required: true, source: source::file::FileSourceFile::new(path.to_path_buf()), } } } impl From for File { fn from(path: PathBuf) -> Self { Self { format: None, required: true, source: source::file::FileSourceFile::new(path), } } } impl File where F: FileStoredFormat + 'static, T: FileSource, { #[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 Source for File where F: FileStoredFormat + Debug + Clone + Send + Sync + 'static, T: Sync + Send + FileSource + 'static, { fn clone_into_box(&self) -> Box { Box::new((*self).clone()) } fn collect(&self) -> Result> { // 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 }) } }