use crate::error::{Error, Result};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Integer(i64),
Float(f64),
String(String),
Array(Vec<Value>),
Table(BTreeMap<String, Value>),
#[cfg(feature = "chrono")]
DateTime(chrono::DateTime<chrono::Utc>),
}
impl Value {
pub fn null() -> Self {
Value::Null
}
pub fn bool(value: bool) -> Self {
Value::Bool(value)
}
pub fn integer(value: i64) -> Self {
Value::Integer(value)
}
pub fn float(value: f64) -> Self {
Value::Float(value)
}
pub fn string<S: Into<String>>(value: S) -> Self {
Value::String(value.into())
}
pub fn array(values: Vec<Value>) -> Self {
Value::Array(values)
}
pub fn table(table: BTreeMap<String, Value>) -> Self {
Value::Table(table)
}
#[cfg(feature = "chrono")]
pub fn datetime(dt: chrono::DateTime<chrono::Utc>) -> Self {
Value::DateTime(dt)
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Integer(_) => "integer",
Value::Float(_) => "float",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Table(_) => "table",
#[cfg(feature = "chrono")]
Value::DateTime(_) => "datetime",
}
}
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn is_bool(&self) -> bool {
matches!(self, Value::Bool(_))
}
pub fn is_integer(&self) -> bool {
matches!(self, Value::Integer(_))
}
pub fn is_float(&self) -> bool {
matches!(self, Value::Float(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
pub fn is_array(&self) -> bool {
matches!(self, Value::Array(_))
}
pub fn is_table(&self) -> bool {
matches!(self, Value::Table(_))
}
pub fn as_bool(&self) -> Result<bool> {
match self {
Value::Bool(b) => Ok(*b),
Value::String(s) => match s.to_lowercase().as_str() {
"true" | "yes" | "1" | "on" => Ok(true),
"false" | "no" | "0" | "off" => Ok(false),
_ => Err(Error::type_error(
"Cannot convert to boolean",
"bool",
self.type_name(),
)),
},
_ => Err(Error::type_error(
"Cannot convert to boolean",
"bool",
self.type_name(),
)),
}
}
pub fn as_integer(&self) -> Result<i64> {
match self {
Value::Integer(i) => Ok(*i),
Value::Float(f) => Ok(*f as i64),
Value::String(s) => s.parse::<i64>().map_err(|_| {
Error::type_error("Cannot convert to integer", "integer", self.type_name())
}),
_ => Err(Error::type_error(
"Cannot convert to integer",
"integer",
self.type_name(),
)),
}
}
pub fn as_float(&self) -> Result<f64> {
match self {
Value::Float(f) => Ok(*f),
Value::Integer(i) => Ok(*i as f64),
Value::String(s) => s.parse::<f64>().map_err(|_| {
Error::type_error("Cannot convert to float", "float", self.type_name())
}),
_ => Err(Error::type_error(
"Cannot convert to float",
"float",
self.type_name(),
)),
}
}
pub fn as_string(&self) -> Result<&str> {
match self {
Value::String(s) => Ok(s.as_str()),
_ => Err(Error::type_error(
"Cannot convert to string",
"string",
self.type_name(),
)),
}
}
pub fn to_string_representation(&self) -> Result<String> {
match self {
Value::String(s) => Ok(s.clone()),
Value::Integer(i) => Ok(i.to_string()),
Value::Float(f) => Ok(f.to_string()),
Value::Bool(b) => Ok(b.to_string()),
_ => Err(Error::type_error(
"Cannot convert to string representation",
"string",
self.type_name(),
)),
}
}
pub fn as_array(&self) -> Result<&Vec<Value>> {
match self {
Value::Array(arr) => Ok(arr),
_ => Err(Error::type_error(
"Cannot convert to array",
"array",
self.type_name(),
)),
}
}
pub fn as_array_mut(&mut self) -> Result<&mut Vec<Value>> {
match self {
Value::Array(arr) => Ok(arr),
_ => Err(Error::type_error(
"Cannot convert to array",
"array",
self.type_name(),
)),
}
}
pub fn as_table(&self) -> Result<&BTreeMap<String, Value>> {
match self {
Value::Table(table) => Ok(table),
_ => Err(Error::type_error(
"Cannot convert to table",
"table",
self.type_name(),
)),
}
}
pub fn as_table_mut(&mut self) -> Result<&mut BTreeMap<String, Value>> {
match self {
Value::Table(table) => Ok(table),
_ => Err(Error::type_error(
"Cannot convert to table",
"table",
self.type_name(),
)),
}
}
pub fn get(&self, path: &str) -> Option<&Value> {
if path.is_empty() {
return Some(self);
}
let parts: Vec<&str> = path.split('.').collect();
let mut current = self;
let mut found_nested = true;
for part in parts {
match current {
Value::Table(table) => {
if let Some(next) = table.get(part) {
current = next;
} else {
found_nested = false;
break;
}
}
_ => {
found_nested = false;
break;
}
}
}
if found_nested {
return Some(current);
}
if let Value::Table(table) = self {
table.get(path)
} else {
None
}
}
pub fn get_mut_nested(&mut self, path: &str) -> Result<&mut Value> {
if path.is_empty() {
return Ok(self);
}
let parts: Vec<&str> = path.split('.').collect();
if parts.is_empty() {
return Err(Error::key_not_found(path));
}
let (last_key, parent_path) = parts
.split_last()
.ok_or_else(|| Error::key_not_found(path))?;
let mut current = self;
for part in parent_path {
match current {
Value::Table(table) => {
current = table
.get_mut(*part)
.ok_or_else(|| Error::key_not_found(*part))?;
}
_ => {
return Err(Error::type_error(
format!(
"Cannot navigate into {} when looking for key '{}'",
current.type_name(),
part
),
"table",
current.type_name(),
))
}
}
}
match current {
Value::Table(table) => table
.get_mut(*last_key)
.ok_or_else(|| Error::key_not_found(*last_key)),
_ => Err(Error::type_error(
format!("Cannot get key '{}' from {}", last_key, current.type_name()),
"table",
current.type_name(),
)),
}
}
pub fn set_nested(&mut self, path: &str, value: Value) -> Result<()> {
if path.is_empty() {
return Err(Error::key_not_found(""));
}
let parts: Vec<&str> = path.split('.').collect();
if parts.is_empty() {
return Err(Error::key_not_found(path));
}
let (last_key, parent_path) = parts
.split_last()
.ok_or_else(|| Error::key_not_found(path))?;
let mut current = self;
for part in parent_path {
if let Value::Table(table) = current {
let entry = table
.entry((*part).to_string())
.or_insert_with(|| Value::table(BTreeMap::new()));
current = entry;
} else {
return Err(Error::type_error(
format!("Cannot navigate into {}", current.type_name()),
"table",
current.type_name(),
));
}
}
if let Value::Table(table) = current {
table.insert((*last_key).to_string(), value);
Ok(())
} else {
Err(Error::type_error(
format!("Cannot set key in {}", current.type_name()),
"table",
current.type_name(),
))
}
}
pub fn remove(&mut self, path: &str) -> Result<Option<Value>> {
if path.is_empty() {
let old = std::mem::replace(self, Value::Null);
return Ok(Some(old));
}
let parts: Vec<&str> = path.split('.').collect();
if parts.is_empty() {
return Err(Error::key_not_found(path));
}
let (last_key, parent_path) = parts
.split_last()
.ok_or_else(|| Error::key_not_found(path))?;
let mut current = self;
for part in parent_path {
match current {
Value::Table(table) => {
current = table
.get_mut(*part)
.ok_or_else(|| Error::key_not_found(*part))?;
}
_ => {
return Err(Error::type_error(
format!(
"Cannot navigate into {} when removing key '{}'",
current.type_name(),
part
),
"table",
current.type_name(),
))
}
}
}
if let Value::Table(table) = current {
Ok(table.remove(*last_key))
} else {
Err(Error::type_error(
format!(
"Cannot remove key '{}' from {}",
last_key,
current.type_name()
),
"table",
current.type_name(),
))
}
}
pub fn keys(&self) -> Result<Vec<&str>> {
match self {
Value::Table(table) => Ok(table.keys().map(|k| k.as_str()).collect()),
_ => Err(Error::type_error(
"Cannot get keys from non-table value",
"table",
self.type_name(),
)),
}
}
pub fn contains_key(&self, path: &str) -> bool {
self.get(path).is_some()
}
pub fn set(&mut self, path: &str, value: Value) -> Result<()> {
self.set_nested(path, value)
}
pub fn len(&self) -> usize {
match self {
Value::Array(arr) => arr.len(),
Value::Table(table) => table.len(),
Value::String(s) => s.len(),
_ => 0,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Null => write!(f, "null"),
Value::Bool(b) => write!(f, "{b}"),
Value::Integer(i) => write!(f, "{i}"),
Value::Float(fl) => write!(f, "{fl}"),
Value::String(s) => write!(f, "{s}"),
Value::Array(arr) => {
write!(f, "[")?;
for (i, item) in arr.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{item}")?;
}
write!(f, "]")
}
Value::Table(table) => {
write!(f, "{{")?;
for (i, (key, value)) in table.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{key}: {value}")?;
}
write!(f, "}}")
}
#[cfg(feature = "chrono")]
Value::DateTime(dt) => write!(f, "{}", dt.to_rfc3339()),
}
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Value::Bool(value)
}
}
impl From<i32> for Value {
fn from(value: i32) -> Self {
Value::Integer(i64::from(value))
}
}
impl From<i64> for Value {
fn from(value: i64) -> Self {
Value::Integer(value)
}
}
impl From<f32> for Value {
fn from(value: f32) -> Self {
Value::Float(f64::from(value))
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Value::Float(value)
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Value::String(value)
}
}
impl From<&str> for Value {
fn from(value: &str) -> Self {
Value::String(value.to_string())
}
}
impl From<Vec<Value>> for Value {
fn from(value: Vec<Value>) -> Self {
Value::Array(value)
}
}
impl From<BTreeMap<String, Value>> for Value {
fn from(value: BTreeMap<String, Value>) -> Self {
Value::Table(value)
}
}
impl Value {
pub fn string_from_slice(value: &str) -> Self {
Value::String(value.to_string())
}
pub fn as_str(&self) -> Result<&str> {
match self {
Value::String(s) => Ok(s.as_str()),
_ => Err(Error::type_error(
"Value is not a string",
"string",
self.type_name(),
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_creation() {
assert_eq!(Value::null(), Value::Null);
assert_eq!(Value::bool(true), Value::Bool(true));
assert_eq!(Value::integer(42), Value::Integer(42));
assert_eq!(Value::float(1.234), Value::Float(1.234));
assert_eq!(Value::string("test"), Value::String("test".to_string()));
}
#[test]
fn test_type_checking() {
let null = Value::null();
let bool_val = Value::bool(true);
let int_val = Value::integer(42);
let float_val = Value::float(5.678);
let string_val = Value::string("test");
let array_val = Value::array(vec![Value::integer(1), Value::integer(2)]);
let table_val = Value::table(BTreeMap::new());
assert!(null.is_null());
assert!(bool_val.is_bool());
assert!(int_val.is_integer());
assert!(float_val.is_float());
assert!(string_val.is_string());
assert!(array_val.is_array());
assert!(table_val.is_table());
}
#[test]
fn test_value_conversion() {
let bool_val = Value::bool(true);
let int_val = Value::integer(42);
let float_val = Value::float(1.234);
let string_val = Value::string("test");
assert!(bool_val.as_bool().unwrap());
assert_eq!(int_val.as_integer().unwrap(), 42);
assert_eq!(float_val.as_float().unwrap(), 1.234);
assert_eq!(string_val.as_string().unwrap(), "test");
}
#[test]
fn test_nested_access() {
let mut table = BTreeMap::new();
let mut inner_table = BTreeMap::new();
inner_table.insert("inner_key".to_string(), Value::string("inner_value"));
table.insert("outer_key".to_string(), Value::table(inner_table));
let value = Value::table(table);
assert_eq!(
value
.get("outer_key.inner_key")
.unwrap()
.as_string()
.unwrap(),
"inner_value"
);
}
#[test]
fn test_enterprise_error_handling() {
let mut value = Value::table(BTreeMap::new());
assert!(value.get_mut_nested("nonexistent.key").is_err());
assert!(value.remove("nonexistent.key").is_err());
assert!(value.set_nested("test.key", Value::string("value")).is_ok());
assert!(value.get("test.key").is_some());
}
}