use std::marker::PhantomData;
use std::str::FromStr;
use crate::error::ConfigulatorError;
use crate::field_info::FieldType;
use crate::value_map::{ConfigValue, ValueMap};
use crate::{ConfigFields, FromValueMap};
pub fn parse_scalar<T: FromStr + Default>(
map: &ValueMap,
key: &str,
) -> Result<T, ConfigulatorError>
where
T::Err: std::fmt::Display,
{
match map.get(key) {
Some(ConfigValue::Scalar(s)) => {
if s.is_empty() {
return Ok(T::default());
}
s.parse::<T>().map_err(|e| ConfigulatorError::ParseError {
field: key.to_string(),
value: s.clone(),
message: e.to_string(),
})
}
None => Ok(T::default()),
Some(other) => Err(ConfigulatorError::ParseError {
field: key.to_string(),
value: format!("{other:?}"),
message: "expected scalar value".to_string(),
}),
}
}
pub fn parse_list<T: FromStr + Default>(
map: &ValueMap,
key: &str,
) -> Result<Vec<T>, ConfigulatorError>
where
T::Err: std::fmt::Display,
{
match map.get(key) {
Some(ConfigValue::List(items)) => {
let mut result = Vec::with_capacity(items.len());
for (i, s) in items.iter().enumerate() {
let val = s.parse::<T>().map_err(|e| ConfigulatorError::ParseError {
field: format!("{key}[{i}]"),
value: s.clone(),
message: e.to_string(),
})?;
result.push(val);
}
Ok(result)
}
Some(ConfigValue::Scalar(s)) => {
if s.is_empty() {
return Ok(Vec::new());
}
let val = s.parse::<T>().map_err(|e| ConfigulatorError::ParseError {
field: key.to_string(),
value: s.clone(),
message: e.to_string(),
})?;
Ok(vec![val])
}
None => Ok(Vec::new()),
Some(other) => Err(ConfigulatorError::ParseError {
field: key.to_string(),
value: format!("{other:?}"),
message: "expected list value".to_string(),
}),
}
}
pub fn parse_nested<T: FromValueMap + Default>(
map: &ValueMap,
key: &str,
) -> Result<T, ConfigulatorError> {
match map.get(key) {
Some(ConfigValue::Nested(nested_map)) => T::from_value_map(nested_map),
None => Ok(T::default()),
Some(other) => Err(ConfigulatorError::ParseError {
field: key.to_string(),
value: format!("{other:?}"),
message: "expected nested struct value".to_string(),
}),
}
}
pub struct ConfigDetect<T>(pub PhantomData<T>);
impl<T: FromValueMap + ConfigFields + Default> ConfigDetect<T> {
pub fn __configulator_parse(&self, map: &ValueMap, key: &str) -> Result<T, ConfigulatorError> {
parse_nested::<T>(map, key)
}
pub fn __configulator_field_type(&self) -> FieldType {
FieldType::Struct(T::configulator_fields())
}
}
pub trait ConfiguratorScalar {
type Output;
fn __configulator_parse(&self, map: &ValueMap, key: &str) -> Result<Self::Output, ConfigulatorError>;
fn __configulator_field_type(&self) -> FieldType;
}
impl<T> ConfiguratorScalar for ConfigDetect<T>
where
T: FromStr + Default,
T::Err: std::fmt::Display,
{
type Output = T;
fn __configulator_parse(&self, map: &ValueMap, key: &str) -> Result<T, ConfigulatorError> {
parse_scalar::<T>(map, key)
}
fn __configulator_field_type(&self) -> FieldType {
FieldType::Scalar
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default, Debug, PartialEq)]
struct TestNested {
name: String,
}
impl FromValueMap for TestNested {
fn from_value_map(map: &ValueMap) -> Result<Self, ConfigulatorError> {
let name = parse_scalar::<String>(map, "name")?;
Ok(TestNested { name })
}
}
impl ConfigFields for TestNested {
fn configulator_fields() -> Vec<crate::field_info::FieldInfo> {
vec![]
}
}
#[test]
fn test_parse_nested_happy_path() {
let mut inner = ValueMap::new();
inner.insert("name".into(), ConfigValue::Scalar("hello".into()));
let mut map = ValueMap::new();
map.insert("nested".into(), ConfigValue::Nested(inner));
let result = parse_nested::<TestNested>(&map, "nested").unwrap();
assert_eq!(result.name, "hello");
}
}