use std::{ffi::OsStr, fs, io, path::PathBuf};
use crate::{error::ErrorInner, Error, Layer};
pub struct File {
path: PathBuf,
format: FileFormat,
required: bool,
}
impl File {
pub fn new(path: impl Into<PathBuf>) -> Result<Self, Error> {
let path = path.into();
let ext = path
.extension()
.ok_or_else(|| ErrorInner::MissingFileExtension { path: path.clone() })?;
let format = FileFormat::from_extension(ext)
.ok_or_else(|| ErrorInner::UnsupportedFileFormat { path: path.clone() })?;
Ok(Self::with_format(path, format))
}
pub fn with_format(path: impl Into<PathBuf>, format: FileFormat) -> Self {
Self {
path: path.into(),
format,
required: false,
}
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn load<L: Layer>(&self) -> Result<L, Error> {
let file_content = match fs::read(&self.path) {
Ok(v) => v,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
if self.required {
return Err(ErrorInner::MissingRequiredFile { path: self.path.clone() }.into());
} else {
return Ok(L::empty());
}
}
Err(e) => {
return Err(ErrorInner::Io {
path: Some(self.path.clone()),
err: e,
}.into());
}
};
let error = |err| {
Error::from(ErrorInner::Deserialization {
err,
source: Some(format!("file '{}'", self.path.display())),
})
};
match self.format {
#[cfg(feature = "toml")]
FileFormat::Toml => {
let s = std::str::from_utf8(&file_content).map_err(|e| error(Box::new(e)))?;
toml::from_str(s).map_err(|e| error(Box::new(e)))
}
#[cfg(feature = "yaml")]
FileFormat::Yaml => serde_yaml::from_slice(&file_content)
.map_err(|e| error(Box::new(e))),
#[cfg(feature = "json5")]
FileFormat::Json5 => {
let s = std::str::from_utf8(&file_content).map_err(|e| error(Box::new(e)))?;
json5::from_str(s).map_err(|e| error(Box::new(e)))
}
}
}
}
pub enum FileFormat {
#[cfg(feature = "toml")]
Toml,
#[cfg(feature = "yaml")]
Yaml,
#[cfg(feature = "json5")]
Json5,
}
impl FileFormat {
pub fn from_extension(ext: impl AsRef<OsStr>) -> Option<Self> {
match ext.as_ref().to_str()? {
#[cfg(feature = "toml")]
"toml" => Some(Self::Toml),
#[cfg(feature = "yaml")]
"yaml" | "yml" => Some(Self::Yaml),
#[cfg(feature = "json5")]
"json5" | "json" => Some(Self::Json5),
_ => None,
}
}
}