use crate::configuration_provider::ConfigurationProvider;
use crate::value::number::Number;
use crate::value::Value;
use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug)]
pub struct YamlProvider(Value);
#[derive(Error, Debug)]
pub enum CreateYamlProviderFromFileError {
#[error("Failed to read configuration file [file_path = `{file_path}`]")]
FailedToReadFile {
file_path: PathBuf,
#[source]
error: io::Error,
},
#[error("Failed to parse configuration file [file_path = `{file_path}`]")]
FailedToDeserialize {
file_path: PathBuf,
#[source]
error: serde_yaml::Error,
},
#[error("Invalid configuration [file_path = `{file_path}`]")]
InvalidConfiguration {
file_path: PathBuf,
#[source]
error: InvalidConfigurationError,
},
}
#[derive(Error, Debug)]
pub enum CreateYamlProviderFromStringError {
#[error("Failed to parse config")]
FailedToDeserialize(#[from] serde_yaml::Error),
#[error("Invalid configuration")]
InvalidConfiguration(#[from] InvalidConfigurationError),
}
#[derive(Error, Debug)]
pub enum InvalidConfigurationError {
#[error("Key is not a string")]
KeyIsNotAString,
#[error("Tags not supported")]
TagsNotSupported,
#[error("Internal error")]
InternalError,
}
impl YamlProvider {
pub fn from_str(config: &str) -> Result<Self, CreateYamlProviderFromStringError> {
let value = serde_yaml::from_str::<serde_yaml::value::Value>(config)?;
Ok(YamlProvider(map_yaml_value_into_value(value)?))
}
pub fn from_path(file_path: impl AsRef<Path>) -> Result<Self, CreateYamlProviderFromFileError> {
let configuration = match fs::read_to_string(file_path.as_ref()) {
Ok(v) => v,
Err(error) => {
let fp = file_path.as_ref().to_path_buf();
return Err(CreateYamlProviderFromFileError::FailedToReadFile {
file_path: fp.canonicalize().unwrap_or(fp),
error,
});
}
};
Self::from_str(&configuration).map_err(|err| {
let file_path = file_path.as_ref().to_path_buf();
match err {
CreateYamlProviderFromStringError::FailedToDeserialize(error) => {
CreateYamlProviderFromFileError::FailedToDeserialize { file_path, error }
}
CreateYamlProviderFromStringError::InvalidConfiguration(error) => {
CreateYamlProviderFromFileError::InvalidConfiguration { file_path, error }
}
}
})
}
}
impl ConfigurationProvider for YamlProvider {
fn provide(&self) -> Value {
self.0.clone()
}
}
fn map_yaml_value_into_value(
value: serde_yaml::value::Value,
) -> Result<Value, InvalidConfigurationError> {
match value {
serde_yaml::Value::Null => Ok(Value::None),
serde_yaml::Value::Bool(bool) => Ok(Value::Bool(bool)),
serde_yaml::Value::Number(number) => {
if let Some(number) = number.as_i64() {
return Ok(Value::Number(Number::Integer(number)));
}
if let Some(number) = number.as_u64() {
return Ok(Value::Number(Number::UInteger(number)));
}
if let Some(number) = number.as_f64() {
return Ok(Value::Number(Number::Float(number)));
}
Err(InvalidConfigurationError::InternalError)
}
serde_yaml::Value::String(string) => Ok(Value::String(string)),
serde_yaml::Value::Sequence(seq) => Ok(Value::Array(
seq.into_iter()
.map(map_yaml_value_into_value)
.collect::<Result<Vec<_>, InvalidConfigurationError>>()?,
)),
serde_yaml::Value::Mapping(map) => map
.into_iter()
.map(|(k, v)| {
let key = match k {
serde_yaml::Value::String(string) => string,
_ => return Err(InvalidConfigurationError::KeyIsNotAString),
};
Ok((key, map_yaml_value_into_value(v)?))
})
.collect::<Result<HashMap<String, Value>, InvalidConfigurationError>>()
.map(Value::Map),
serde_yaml::Value::Tagged(_) => {
return Err(InvalidConfigurationError::TagsNotSupported);
}
}
}