use std::collections::BTreeMap;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Default)]
pub enum ConfigValue {
#[default]
Null,
Bool(bool),
Integer(i64),
Float(f64),
String(String),
Array(Vec<ConfigValue>),
Table(BTreeMap<String, ConfigValue>),
}
impl ConfigValue {
#[must_use]
pub fn as_table(&self) -> Option<&BTreeMap<String, ConfigValue>> {
match self {
Self::Table(t) => Some(t),
_ => None,
}
}
#[must_use]
pub fn as_table_mut(&mut self) -> Option<&mut BTreeMap<String, ConfigValue>> {
match self {
Self::Table(t) => Some(t),
_ => None,
}
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
}
impl From<bool> for ConfigValue {
fn from(v: bool) -> Self {
Self::Bool(v)
}
}
impl From<i64> for ConfigValue {
fn from(v: i64) -> Self {
Self::Integer(v)
}
}
impl From<f64> for ConfigValue {
fn from(v: f64) -> Self {
Self::Float(v)
}
}
impl From<String> for ConfigValue {
fn from(v: String) -> Self {
Self::String(v)
}
}
impl From<&str> for ConfigValue {
fn from(v: &str) -> Self {
Self::String(v.to_owned())
}
}
impl FromStr for ConfigValue {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("true") {
return Ok(Self::Bool(true));
}
if s.eq_ignore_ascii_case("false") {
return Ok(Self::Bool(false));
}
if let Ok(i) = s.parse::<i64>() {
return Ok(Self::Integer(i));
}
if let Ok(f) = s.parse::<f64>() {
return Ok(Self::Float(f));
}
Ok(Self::String(s.to_owned()))
}
}
#[cfg(feature = "json")]
impl From<serde_json::Value> for ConfigValue {
fn from(v: serde_json::Value) -> Self {
match v {
serde_json::Value::Null => Self::Null,
serde_json::Value::Bool(b) => Self::Bool(b),
serde_json::Value::Number(n) => n
.as_i64()
.map(Self::Integer)
.or_else(|| n.as_f64().map(Self::Float))
.unwrap_or(Self::Null),
serde_json::Value::String(s) => Self::String(s),
serde_json::Value::Array(arr) => Self::Array(arr.into_iter().map(Self::from).collect()),
serde_json::Value::Object(map) => {
Self::Table(map.into_iter().map(|(k, v)| (k, Self::from(v))).collect())
}
}
}
}
#[cfg(feature = "json")]
impl From<ConfigValue> for serde_json::Value {
fn from(v: ConfigValue) -> Self {
match v {
ConfigValue::Null => Self::Null,
ConfigValue::Bool(b) => Self::Bool(b),
ConfigValue::Integer(i) => Self::Number(i.into()),
ConfigValue::Float(f) => {
serde_json::Number::from_f64(f).map_or(Self::Null, Self::Number)
}
ConfigValue::String(s) => Self::String(s),
ConfigValue::Array(arr) => Self::Array(arr.into_iter().map(Into::into).collect()),
ConfigValue::Table(map) => {
Self::Object(map.into_iter().map(|(k, v)| (k, v.into())).collect())
}
}
}
}
#[cfg(feature = "toml")]
impl From<toml::Value> for ConfigValue {
fn from(v: toml::Value) -> Self {
match v {
toml::Value::Boolean(b) => ConfigValue::Bool(b),
toml::Value::Integer(i) => ConfigValue::Integer(i),
toml::Value::Float(f) => ConfigValue::Float(f),
toml::Value::String(s) => ConfigValue::String(s),
toml::Value::Array(arr) => {
ConfigValue::Array(arr.into_iter().map(Self::from).collect())
}
toml::Value::Table(map) => {
ConfigValue::Table(map.into_iter().map(|(k, v)| (k, Self::from(v))).collect())
}
toml::Value::Datetime(dt) => ConfigValue::String(dt.to_string()),
}
}
}
#[cfg(feature = "toml")]
impl From<ConfigValue> for toml::Value {
fn from(v: ConfigValue) -> Self {
match v {
ConfigValue::Null => toml::Value::Table(toml::map::Map::default()),
ConfigValue::Bool(b) => toml::Value::Boolean(b),
ConfigValue::Integer(i) => toml::Value::Integer(i),
ConfigValue::Float(f) => toml::Value::Float(f),
ConfigValue::String(s) => toml::Value::String(s),
ConfigValue::Array(arr) => {
toml::Value::Array(arr.into_iter().map(Self::from).collect())
}
ConfigValue::Table(map) => {
toml::Value::Table(map.into_iter().map(|(k, v)| (k, Self::from(v))).collect())
}
}
}
}