use crate::error::{Error, Result};
use crate::value::ConfigValue;
#[cfg(not(feature = "std"))]
use alloc::{
collections::BTreeMap as HashMap,
string::{String, ToString},
};
#[cfg(feature = "std")]
use std::collections::HashMap;
pub trait ValueVisitor {
type Output;
fn expecting(&self) -> &'static str {
"any value"
}
fn visit_null(&mut self) -> Result<Self::Output> {
Err(Error::ConversionError {
key: String::new(),
type_name: self.expecting().into(),
source: "unexpected null".into(),
})
}
fn visit_bool(&mut self, _v: bool) -> Result<Self::Output> {
Err(Error::ConversionError {
key: String::new(),
type_name: self.expecting().into(),
source: "unexpected boolean".into(),
})
}
fn visit_i64(&mut self, _v: i64) -> Result<Self::Output> {
Err(Error::ConversionError {
key: String::new(),
type_name: self.expecting().into(),
source: "unexpected integer".into(),
})
}
fn visit_f64(&mut self, _v: f64) -> Result<Self::Output> {
Err(Error::ConversionError {
key: String::new(),
type_name: self.expecting().into(),
source: "unexpected float".into(),
})
}
fn visit_str(&mut self, _v: &str) -> Result<Self::Output> {
Err(Error::ConversionError {
key: String::new(),
type_name: self.expecting().into(),
source: "unexpected string".into(),
})
}
fn visit_array(&mut self, _arr: &[ConfigValue]) -> Result<Self::Output> {
Err(Error::ConversionError {
key: String::new(),
type_name: self.expecting().into(),
source: "unexpected array".into(),
})
}
fn visit_seq(&mut self, seq: SeqAccess<'_>) -> Result<Self::Output> {
self.visit_array(seq.as_slice())
}
fn visit_map(&mut self, _map: MapAccess<'_>) -> Result<Self::Output> {
Err(Error::ConversionError {
key: String::new(),
type_name: self.expecting().into(),
source: "unexpected object".into(),
})
}
fn visit_enum(&mut self, _variant: &str, _value: &ConfigValue) -> Result<Self::Output> {
Err(Error::ConversionError {
key: String::new(),
type_name: self.expecting().into(),
source: "unexpected enum variant".into(),
})
}
fn visit_unknown(&mut self, _key: &str, _value: &ConfigValue) -> Result<()> {
Ok(())
}
fn finish(&mut self, output: Self::Output) -> Result<Self::Output> {
Ok(output)
}
}
pub struct MapAccess<'a> {
map: &'a HashMap<String, ConfigValue>,
}
impl<'a> MapAccess<'a> {
fn new(map: &'a HashMap<String, ConfigValue>) -> Self {
Self { map }
}
pub fn get(&self, key: &str) -> Option<&ConfigValue> {
self.map.get(key)
}
pub fn contains_key(&self, key: &str) -> bool {
self.map.contains_key(key)
}
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.map.keys().map(|s| s.as_str())
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &ConfigValue)> {
self.map.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn as_map(&self) -> &HashMap<String, ConfigValue> {
self.map
}
}
pub struct SeqAccess<'a> {
arr: &'a [ConfigValue],
index: usize,
}
impl<'a> SeqAccess<'a> {
pub fn new(arr: &'a [ConfigValue]) -> Self {
Self { arr, index: 0 }
}
pub fn next_element<T: crate::FromValue>(&mut self) -> Result<Option<T>> {
if self.index >= self.arr.len() {
return Ok(None);
}
let element = &self.arr[self.index];
self.index += 1;
T::from_value(element).map(Some)
}
pub fn as_slice(&self) -> &[ConfigValue] {
self.arr
}
pub fn len(&self) -> usize {
self.arr.len()
}
pub fn is_empty(&self) -> bool {
self.arr.is_empty()
}
pub fn position(&self) -> usize {
self.index
}
}
pub fn visit<V: ValueVisitor>(value: &ConfigValue, visitor: &mut V) -> Result<V::Output> {
match value {
ConfigValue::Null => visitor.visit_null(),
ConfigValue::Bool(b) => visitor.visit_bool(*b),
ConfigValue::Integer(n) => visitor.visit_i64(*n),
ConfigValue::Float(f) => visitor.visit_f64(*f),
ConfigValue::String(s) => visitor.visit_str(s),
ConfigValue::Array(arr) => visitor.visit_array(arr),
ConfigValue::Object(map) => visitor.visit_map(MapAccess::new(map)),
}
}
pub struct FromValueVisitor<T> {
_marker: core::marker::PhantomData<T>,
}
impl<T> FromValueVisitor<T> {
pub fn new() -> Self {
Self {
_marker: core::marker::PhantomData,
}
}
}
impl<T> Default for FromValueVisitor<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: crate::FromValue> ValueVisitor for FromValueVisitor<T> {
type Output = T;
fn expecting(&self) -> &'static str {
core::any::type_name::<T>()
}
fn visit_null(&mut self) -> Result<Self::Output> {
T::from_value(&ConfigValue::Null)
}
fn visit_bool(&mut self, v: bool) -> Result<Self::Output> {
T::from_value(&ConfigValue::Bool(v))
}
fn visit_i64(&mut self, v: i64) -> Result<Self::Output> {
T::from_value(&ConfigValue::Integer(v))
}
fn visit_f64(&mut self, v: f64) -> Result<Self::Output> {
T::from_value(&ConfigValue::Float(v))
}
fn visit_str(&mut self, v: &str) -> Result<Self::Output> {
T::from_value(&ConfigValue::String(v.to_string()))
}
fn visit_array(&mut self, arr: &[ConfigValue]) -> Result<Self::Output> {
T::from_value(&ConfigValue::Array(arr.to_vec()))
}
fn visit_map(&mut self, map: MapAccess<'_>) -> Result<Self::Output> {
T::from_value(&ConfigValue::Object(map.as_map().clone()))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct SumVisitor {
sum: i64,
}
impl ValueVisitor for SumVisitor {
type Output = i64;
fn expecting(&self) -> &'static str {
"a number or array of numbers"
}
fn visit_i64(&mut self, v: i64) -> Result<Self::Output> {
self.sum += v;
Ok(self.sum)
}
fn visit_array(&mut self, arr: &[ConfigValue]) -> Result<Self::Output> {
for item in arr {
if let Some(n) = item.as_i64() {
self.sum += n;
}
}
Ok(self.sum)
}
}
#[test]
fn test_visit_integer() {
let mut visitor = SumVisitor { sum: 0 };
let result = visit(&ConfigValue::Integer(42), &mut visitor).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_visit_array() {
let mut visitor = SumVisitor { sum: 0 };
let arr = ConfigValue::Array(vec![
ConfigValue::Integer(1),
ConfigValue::Integer(2),
ConfigValue::Integer(3),
ConfigValue::Integer(4),
ConfigValue::Integer(5),
]);
let result = visit(&arr, &mut visitor).unwrap();
assert_eq!(result, 15);
}
#[test]
fn test_visit_string_error() {
let mut visitor = SumVisitor { sum: 0 };
let result = visit(&ConfigValue::String("hello".to_string()), &mut visitor);
assert!(result.is_err());
}
struct KeyCollector {
keys: Vec<String>,
}
impl ValueVisitor for KeyCollector {
type Output = Vec<String>;
fn expecting(&self) -> &'static str {
"an object"
}
fn visit_map(&mut self, map: MapAccess<'_>) -> Result<Self::Output> {
self.keys = map.keys().map(String::from).collect();
Ok(self.keys.clone())
}
}
#[test]
fn test_visit_map() {
let mut visitor = KeyCollector { keys: vec![] };
let mut map = HashMap::new();
map.insert("a".to_string(), ConfigValue::Integer(1));
map.insert("b".to_string(), ConfigValue::Integer(2));
map.insert("c".to_string(), ConfigValue::Integer(3));
let result = visit(&ConfigValue::Object(map), &mut visitor).unwrap();
assert_eq!(result.len(), 3);
assert!(result.contains(&"a".to_string()));
assert!(result.contains(&"b".to_string()));
assert!(result.contains(&"c".to_string()));
}
#[test]
fn test_map_access_methods() {
let mut map = HashMap::new();
map.insert(
"host".to_string(),
ConfigValue::String("localhost".to_string()),
);
map.insert("port".to_string(), ConfigValue::Integer(8080));
let access = MapAccess::new(&map);
assert!(access.contains_key("host"));
assert!(!access.contains_key("nonexistent"));
assert_eq!(access.get("host").unwrap().as_str(), Some("localhost"));
assert_eq!(access.len(), 2);
assert!(!access.is_empty());
}
struct SeqSumVisitor {
sum: i64,
}
impl ValueVisitor for SeqSumVisitor {
type Output = i64;
fn expecting(&self) -> &'static str {
"an array of numbers"
}
fn visit_seq(&mut self, mut seq: super::SeqAccess<'_>) -> Result<Self::Output> {
while let Some(value) = seq.next_element::<i64>()? {
self.sum += value;
}
Ok(self.sum)
}
}
#[test]
fn test_visit_seq() {
use super::SeqAccess;
let mut visitor = SeqSumVisitor { sum: 0 };
let arr = vec![
ConfigValue::Integer(10),
ConfigValue::Integer(20),
ConfigValue::Integer(30),
];
let seq = SeqAccess::new(&arr);
let result = visitor.visit_seq(seq).unwrap();
assert_eq!(result, 60);
}
struct EnumVisitor;
impl ValueVisitor for EnumVisitor {
type Output = String;
fn expecting(&self) -> &'static str {
"an enum variant"
}
fn visit_enum(&mut self, variant: &str, value: &ConfigValue) -> Result<Self::Output> {
Ok(format!("{}:{}", variant, value.as_i64().unwrap_or(0)))
}
}
#[test]
fn test_visit_enum() {
let mut visitor = EnumVisitor;
let value = ConfigValue::Integer(42);
let result = visitor.visit_enum("Answer", &value).unwrap();
assert_eq!(result, "Answer:42");
}
struct StrictMapVisitor {
allowed_keys: Vec<String>,
}
impl ValueVisitor for StrictMapVisitor {
type Output = HashMap<String, ConfigValue>;
fn expecting(&self) -> &'static str {
"a map with only allowed keys"
}
fn visit_map(&mut self, map: MapAccess<'_>) -> Result<Self::Output> {
for key in map.keys() {
if !self.allowed_keys.contains(&key.to_string()) {
self.visit_unknown(key, map.get(key).unwrap())?;
}
}
Ok(map.as_map().clone())
}
fn visit_unknown(&mut self, key: &str, _value: &ConfigValue) -> Result<()> {
Err(Error::ConversionError {
key: key.to_string(),
type_name: "strict map".into(),
source: format!("unknown field: {}", key).into(),
})
}
}
#[test]
fn test_visit_unknown() {
let mut map = HashMap::new();
map.insert("allowed".to_string(), ConfigValue::Integer(1));
map.insert("forbidden".to_string(), ConfigValue::Integer(2));
let mut visitor = StrictMapVisitor {
allowed_keys: vec!["allowed".to_string()],
};
let result = visit(&ConfigValue::Object(map), &mut visitor);
assert!(result.is_err());
match result.unwrap_err() {
Error::ConversionError { key, .. } => assert_eq!(key, "forbidden"),
_ => panic!("Expected ConversionError"),
}
}
}