#![crate_name = "amethyst_config"]
#![warn(
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
rust_2018_compatibility
)]
#![warn(clippy::all)]
#![allow(clippy::new_without_default)]
use std::{
error::Error,
fmt, io,
path::{Path, PathBuf},
};
use ron::{self, de::Error as DeError, ser::Error as SerError};
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub enum ConfigError {
File(io::Error),
Parser(DeError),
Serializer(SerError),
Extension(PathBuf),
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ConfigError::File(ref err) => write!(f, "{}", err),
ConfigError::Parser(ref msg) => write!(f, "{}", msg),
ConfigError::Serializer(ref msg) => write!(f, "{}", msg),
ConfigError::Extension(ref path) => {
let found = match path.extension() {
Some(extension) => format!("{:?}", extension),
None => "a directory.".to_string(),
};
write!(
f,
"{}: Invalid path extension, expected \"ron\", got {}.",
path.display().to_string(),
found,
)
}
}
}
}
impl From<io::Error> for ConfigError {
fn from(e: io::Error) -> ConfigError {
ConfigError::File(e)
}
}
impl From<DeError> for ConfigError {
fn from(e: DeError) -> Self {
ConfigError::Parser(e)
}
}
impl From<SerError> for ConfigError {
fn from(e: SerError) -> Self {
ConfigError::Serializer(e)
}
}
impl Error for ConfigError {
fn description(&self) -> &str {
match *self {
ConfigError::File(_) => "Project file error",
ConfigError::Parser(_) => "Project parser error",
ConfigError::Serializer(_) => "Project serializer error",
ConfigError::Extension(_) => "Invalid extension or directory for a file",
}
}
fn cause(&self) -> Option<&dyn Error> {
match *self {
ConfigError::File(ref err) => Some(err),
_ => None,
}
}
}
pub trait Config
where
Self: Sized,
{
fn load<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError>;
#[deprecated(note = "use `load` instead")]
fn load_no_fallback<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
Self::load(path)
}
fn load_bytes(bytes: &[u8]) -> Result<Self, ConfigError>;
fn write<P: AsRef<Path>>(&self, path: P) -> Result<(), ConfigError>;
}
impl<T> Config for T
where
T: for<'a> Deserialize<'a> + Serialize,
{
fn load<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
use std::{fs::File, io::Read};
let path = path.as_ref();
let content = {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
buffer
};
if path.extension().and_then(std::ffi::OsStr::to_str) == Some("ron") {
Self::load_bytes(&content)
} else {
Err(ConfigError::Extension(path.to_path_buf()))
}
}
fn load_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
let mut de = ron::de::Deserializer::from_bytes(bytes)?;
let val = T::deserialize(&mut de)?;
de.end()?;
Ok(val)
}
fn write<P: AsRef<Path>>(&self, path: P) -> Result<(), ConfigError> {
use ron::ser::to_string_pretty;
use std::{fs::File, io::Write};
let s = to_string_pretty(self, Default::default())?;
File::create(path)?.write_all(s.as_bytes())?;
Ok(())
}
}