use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::error::{ConfigError, ConfigResult};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(untagged)]
pub enum ConfigValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
Array(Vec<ConfigValue>),
Map(HashMap<String, ConfigValue>),
#[default]
Null,
}
impl ConfigValue {
#[must_use]
pub fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
#[must_use]
pub fn is_integer(&self) -> bool {
matches!(self, Self::Integer(_))
}
#[must_use]
pub fn is_float(&self) -> bool {
matches!(self, Self::Float(_))
}
#[must_use]
pub fn is_boolean(&self) -> bool {
matches!(self, Self::Boolean(_))
}
#[must_use]
pub fn is_array(&self) -> bool {
matches!(self, Self::Array(_))
}
#[must_use]
pub fn is_map(&self) -> bool {
matches!(self, Self::Map(_))
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[must_use]
pub fn as_string(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_integer(&self) -> Option<i64> {
match self {
Self::Integer(i) => Some(*i),
_ => None,
}
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn as_float(&self) -> Option<f64> {
match self {
Self::Float(f) => Some(*f),
Self::Integer(i) => Some(*i as f64),
_ => None,
}
}
#[must_use]
pub fn as_boolean(&self) -> Option<bool> {
match self {
Self::Boolean(b) => Some(*b),
_ => None,
}
}
#[must_use]
pub fn as_array(&self) -> Option<&[ConfigValue]> {
match self {
Self::Array(arr) => Some(arr),
_ => None,
}
}
#[must_use]
pub fn as_map(&self) -> Option<&HashMap<String, ConfigValue>> {
match self {
Self::Map(map) => Some(map),
_ => None,
}
}
#[must_use]
pub fn get(&self, key: &str) -> Option<&ConfigValue> {
match self {
Self::Map(map) => map.get(key),
_ => None,
}
}
pub fn get_mut(&mut self, key: &str) -> Option<&mut ConfigValue> {
match self {
Self::Map(map) => map.get_mut(key),
_ => None,
}
}
pub fn insert(
&mut self,
key: impl Into<String>,
value: ConfigValue,
) -> ConfigResult<Option<ConfigValue>> {
match self {
Self::Map(map) => Ok(map.insert(key.into(), value)),
_ => Err(ConfigError::type_error("map", self.type_name())),
}
}
#[must_use]
pub fn type_name(&self) -> &'static str {
match self {
Self::String(_) => "string",
Self::Integer(_) => "integer",
Self::Float(_) => "float",
Self::Boolean(_) => "boolean",
Self::Array(_) => "array",
Self::Map(_) => "map",
Self::Null => "null",
}
}
pub fn merge(&mut self, other: ConfigValue) {
match (self, other) {
(Self::Map(base), Self::Map(other)) => {
for (key, value) in other {
match base.get_mut(&key) {
Some(base_value) => base_value.merge(value),
None => {
base.insert(key, value);
}
}
}
}
(base, other) => *base = other,
}
}
}
impl From<String> for ConfigValue {
fn from(s: String) -> Self {
Self::String(s)
}
}
impl From<&str> for ConfigValue {
fn from(s: &str) -> Self {
Self::String(s.to_string())
}
}
impl From<i64> for ConfigValue {
fn from(i: i64) -> Self {
Self::Integer(i)
}
}
impl From<i32> for ConfigValue {
fn from(i: i32) -> Self {
Self::Integer(i64::from(i))
}
}
impl From<f64> for ConfigValue {
fn from(f: f64) -> Self {
Self::Float(f)
}
}
impl From<f32> for ConfigValue {
fn from(f: f32) -> Self {
Self::Float(f64::from(f))
}
}
impl From<bool> for ConfigValue {
fn from(b: bool) -> Self {
Self::Boolean(b)
}
}
impl<T: Into<ConfigValue>> From<Vec<T>> for ConfigValue {
fn from(vec: Vec<T>) -> Self {
Self::Array(vec.into_iter().map(Into::into).collect())
}
}
impl<T: Into<ConfigValue>> From<HashMap<String, T>> for ConfigValue {
fn from(map: HashMap<String, T>) -> Self {
Self::Map(map.into_iter().map(|(k, v)| (k, v.into())).collect())
}
}