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