use std::collections::HashMap;
use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(untagged)]
pub enum ContextValue {
#[default]
Null,
Bool(bool),
Integer(i64),
Float(f64),
String(String),
Array(Vec<ContextValue>),
Object(HashMap<String, ContextValue>),
}
impl ContextValue {
#[inline]
pub fn is_null(&self) -> bool {
matches!(self, ContextValue::Null)
}
#[inline]
pub fn as_bool(&self) -> Option<bool> {
match self {
ContextValue::Bool(b) => Some(*b),
_ => None,
}
}
#[inline]
pub fn as_i64(&self) -> Option<i64> {
match self {
ContextValue::Integer(i) => Some(*i),
_ => None,
}
}
#[inline]
pub fn as_f64(&self) -> Option<f64> {
match self {
ContextValue::Float(f) => Some(*f),
ContextValue::Integer(i) => Some(*i as f64),
_ => None,
}
}
#[inline]
pub fn as_str(&self) -> Option<&str> {
match self {
ContextValue::String(s) => Some(s),
_ => None,
}
}
#[inline]
pub fn as_array(&self) -> Option<&[ContextValue]> {
match self {
ContextValue::Array(arr) => Some(arr),
_ => None,
}
}
#[inline]
pub fn as_object(&self) -> Option<&HashMap<String, ContextValue>> {
match self {
ContextValue::Object(obj) => Some(obj),
_ => None,
}
}
}
impl From<bool> for ContextValue {
fn from(value: bool) -> Self {
ContextValue::Bool(value)
}
}
impl From<i32> for ContextValue {
fn from(value: i32) -> Self {
ContextValue::Integer(value as i64)
}
}
impl From<i64> for ContextValue {
fn from(value: i64) -> Self {
ContextValue::Integer(value)
}
}
impl From<f64> for ContextValue {
fn from(value: f64) -> Self {
ContextValue::Float(value)
}
}
impl From<&str> for ContextValue {
fn from(value: &str) -> Self {
ContextValue::String(value.to_owned())
}
}
impl From<String> for ContextValue {
fn from(value: String) -> Self {
ContextValue::String(value)
}
}
impl<T: Into<ContextValue>> From<Vec<T>> for ContextValue {
fn from(value: Vec<T>) -> Self {
ContextValue::Array(value.into_iter().map(Into::into).collect())
}
}
impl<T: Into<ContextValue>> From<Option<T>> for ContextValue {
fn from(value: Option<T>) -> Self {
match value {
Some(v) => v.into(),
None => ContextValue::Null,
}
}
}
impl fmt::Display for ContextValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ContextValue::Null => write!(f, "null"),
ContextValue::Bool(b) => write!(f, "{}", b),
ContextValue::Integer(i) => write!(f, "{}", i),
ContextValue::Float(fl) => write!(f, "{}", fl),
ContextValue::String(s) => write!(f, "\"{}\"", s),
ContextValue::Array(arr) => {
write!(f, "[")?;
for (i, v) in arr.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", v)?;
}
write!(f, "]")
}
ContextValue::Object(obj) => {
write!(f, "{{")?;
for (i, (k, v)) in obj.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "\"{}\": {}", k, v)?;
}
write!(f, "}}")
}
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Context {
#[serde(flatten)]
values: HashMap<String, ContextValue>,
}
impl Context {
pub fn new() -> Self {
Self {
values: HashMap::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
values: HashMap::with_capacity(capacity),
}
}
#[must_use]
pub fn with(mut self, key: impl Into<String>, value: impl Into<ContextValue>) -> Self {
self.values.insert(key.into(), value.into());
self
}
pub fn insert(
&mut self,
key: impl Into<String>,
value: impl Into<ContextValue>,
) -> Option<ContextValue> {
self.values.insert(key.into(), value.into())
}
pub fn get(&self, key: &str) -> Option<&ContextValue> {
self.values.get(key)
}
pub fn remove(&mut self, key: &str) -> Option<ContextValue> {
self.values.remove(key)
}
pub fn contains_key(&self, key: &str) -> bool {
self.values.contains_key(key)
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &ContextValue)> {
self.values.iter()
}
pub fn extend(&mut self, other: Context) {
self.values.extend(other.values);
}
#[must_use]
pub fn merge(mut self, other: Context) -> Self {
self.extend(other);
self
}
pub fn into_value(self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or(serde_json::Value::Null)
}
}
impl FromIterator<(String, ContextValue)> for Context {
fn from_iter<T: IntoIterator<Item = (String, ContextValue)>>(iter: T) -> Self {
Self {
values: iter.into_iter().collect(),
}
}
}
impl IntoIterator for Context {
type Item = (String, ContextValue);
type IntoIter = std::collections::hash_map::IntoIter<String, ContextValue>;
fn into_iter(self) -> Self::IntoIter {
self.values.into_iter()
}
}
impl<'a> IntoIterator for &'a Context {
type Item = (&'a String, &'a ContextValue);
type IntoIter = std::collections::hash_map::Iter<'a, String, ContextValue>;
fn into_iter(self) -> Self::IntoIter {
self.values.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_value_types() {
assert!(ContextValue::Null.is_null());
assert_eq!(ContextValue::Bool(true).as_bool(), Some(true));
assert_eq!(ContextValue::Integer(42).as_i64(), Some(42));
assert_eq!(ContextValue::Float(2.5).as_f64(), Some(2.5));
assert_eq!(ContextValue::String("test".into()).as_str(), Some("test"));
}
#[test]
fn test_context_value_conversions() {
let b: ContextValue = true.into();
assert_eq!(b.as_bool(), Some(true));
let i: ContextValue = 42i32.into();
assert_eq!(i.as_i64(), Some(42));
let f: ContextValue = 2.5.into();
assert_eq!(f.as_f64(), Some(2.5));
let s: ContextValue = "hello".into();
assert_eq!(s.as_str(), Some("hello"));
let arr: ContextValue = vec![1i32, 2, 3].into();
assert!(arr.as_array().is_some());
}
#[test]
fn test_context_new() {
let ctx = Context::new();
assert!(ctx.is_empty());
assert_eq!(ctx.len(), 0);
}
#[test]
fn test_context_with_capacity() {
let ctx = Context::with_capacity(10);
assert!(ctx.is_empty());
assert_eq!(ctx.len(), 0);
}
#[test]
fn test_context_with() {
let ctx = Context::new()
.with("env", "prod")
.with("debug", false)
.with("count", 10);
assert_eq!(ctx.len(), 3);
assert_eq!(ctx.get("env").and_then(|v| v.as_str()), Some("prod"));
assert_eq!(ctx.get("debug").and_then(|v| v.as_bool()), Some(false));
assert_eq!(ctx.get("count").and_then(|v| v.as_i64()), Some(10));
}
#[test]
fn test_context_insert() {
let mut ctx = Context::new();
assert!(ctx.insert("key", "value1").is_none());
assert!(ctx.insert("key", "value2").is_some());
assert_eq!(ctx.get("key").and_then(|v| v.as_str()), Some("value2"));
}
#[test]
fn test_context_remove() {
let mut ctx = Context::new().with("key", "value");
assert!(ctx.remove("key").is_some());
assert!(ctx.remove("key").is_none());
assert!(!ctx.contains_key("key"));
}
#[test]
fn test_context_merge() {
let ctx1 = Context::new().with("a", 1).with("b", 2);
let ctx2 = Context::new().with("b", 3).with("c", 4);
let merged = ctx1.merge(ctx2);
assert_eq!(merged.get("a").and_then(|v| v.as_i64()), Some(1));
assert_eq!(merged.get("b").and_then(|v| v.as_i64()), Some(3)); assert_eq!(merged.get("c").and_then(|v| v.as_i64()), Some(4));
}
#[test]
fn test_context_iteration() {
let ctx = Context::new().with("a", 1).with("b", 2);
let keys: Vec<_> = ctx.iter().map(|(k, _)| k.as_str()).collect();
assert!(keys.contains(&"a"));
assert!(keys.contains(&"b"));
}
#[test]
fn test_context_serialization() {
let ctx = Context::new()
.with("string", "hello")
.with("number", 42)
.with("bool", true);
let json = serde_json::to_string(&ctx).unwrap();
let parsed: Context = serde_json::from_str(&json).unwrap();
assert_eq!(ctx, parsed);
}
#[test]
fn test_context_value_display() {
assert_eq!(ContextValue::Null.to_string(), "null");
assert_eq!(ContextValue::Bool(true).to_string(), "true");
assert_eq!(ContextValue::Integer(42).to_string(), "42");
assert_eq!(ContextValue::String("test".into()).to_string(), "\"test\"");
}
#[test]
fn test_nested_context() {
let mut inner = HashMap::new();
inner.insert("nested_key".to_string(), ContextValue::from("nested_value"));
let ctx = Context::new().with("outer", ContextValue::Object(inner));
let obj = ctx.get("outer").and_then(|v| v.as_object()).unwrap();
assert_eq!(
obj.get("nested_key").and_then(|v| v.as_str()),
Some("nested_value")
);
}
#[test]
fn test_array_context_value() {
let arr = ContextValue::from(vec!["a", "b", "c"]);
let values = arr.as_array().unwrap();
assert_eq!(values.len(), 3);
assert_eq!(values[0].as_str(), Some("a"));
}
#[test]
fn test_option_conversion() {
let some: ContextValue = Some("value").into();
assert_eq!(some.as_str(), Some("value"));
let none: ContextValue = Option::<String>::None.into();
assert!(none.is_null());
}
#[test]
fn test_from_iterator() {
let pairs = vec![
("a".to_string(), ContextValue::from(1)),
("b".to_string(), ContextValue::from(2)),
];
let ctx: Context = pairs.into_iter().collect();
assert_eq!(ctx.len(), 2);
}
#[test]
fn test_context_value_as_wrong_type() {
assert!(ContextValue::Integer(42).as_bool().is_none());
assert!(ContextValue::Null.as_bool().is_none());
assert!(ContextValue::Bool(true).as_i64().is_none());
assert!(ContextValue::String("test".into()).as_i64().is_none());
assert!(ContextValue::Bool(true).as_f64().is_none());
assert!(ContextValue::Null.as_f64().is_none());
assert!(ContextValue::Integer(42).as_str().is_none());
assert!(ContextValue::Bool(true).as_str().is_none());
assert!(ContextValue::Integer(42).as_array().is_none());
assert!(ContextValue::String("test".into()).as_array().is_none());
assert!(ContextValue::Integer(42).as_object().is_none());
assert!(ContextValue::Array(vec![]).as_object().is_none());
}
#[test]
fn test_context_value_as_f64_from_integer() {
let val = ContextValue::Integer(42);
assert_eq!(val.as_f64(), Some(42.0));
}
#[test]
fn test_context_value_from_i64() {
let val: ContextValue = 100i64.into();
assert_eq!(val.as_i64(), Some(100));
}
#[test]
fn test_context_value_from_string_owned() {
let val: ContextValue = String::from("hello").into();
assert_eq!(val.as_str(), Some("hello"));
}
#[test]
fn test_context_value_display_float() {
let val = ContextValue::Float(1.23);
assert_eq!(val.to_string(), "1.23");
}
#[test]
fn test_context_value_display_array_multiple() {
let arr = ContextValue::Array(vec![
ContextValue::Integer(1),
ContextValue::Integer(2),
ContextValue::Integer(3),
]);
assert_eq!(arr.to_string(), "[1, 2, 3]");
}
#[test]
fn test_context_value_display_object() {
let mut obj = HashMap::new();
obj.insert("key".to_string(), ContextValue::String("value".into()));
let val = ContextValue::Object(obj);
let display = val.to_string();
assert!(display.starts_with("{"));
assert!(display.ends_with("}"));
assert!(display.contains("\"key\""));
assert!(display.contains("\"value\""));
}
#[test]
fn test_context_value_display_object_multiple() {
let mut obj = HashMap::new();
obj.insert("a".to_string(), ContextValue::Integer(1));
obj.insert("b".to_string(), ContextValue::Integer(2));
let val = ContextValue::Object(obj);
let display = val.to_string();
assert!(display.contains(", "));
}
#[test]
fn test_context_into_iterator() {
let ctx = Context::new().with("a", 1).with("b", 2);
let mut count = 0;
for (key, _) in ctx {
assert!(key == "a" || key == "b");
count += 1;
}
assert_eq!(count, 2);
}
#[test]
fn test_context_ref_into_iterator() {
let ctx = Context::new().with("a", 1).with("b", 2);
let mut count = 0;
for (key, _) in &ctx {
assert!(key == "a" || key == "b");
count += 1;
}
assert_eq!(count, 2);
}
#[test]
fn test_context_extend() {
let mut ctx1 = Context::new().with("a", 1);
let ctx2 = Context::new().with("b", 2).with("a", 3);
ctx1.extend(ctx2);
assert_eq!(ctx1.get("a").and_then(|v| v.as_i64()), Some(3));
assert_eq!(ctx1.get("b").and_then(|v| v.as_i64()), Some(2));
}
#[test]
fn test_context_value_debug() {
let val = ContextValue::Integer(42);
let debug = format!("{:?}", val);
assert!(debug.contains("Integer"));
assert!(debug.contains("42"));
}
#[test]
fn test_context_value_clone() {
let val = ContextValue::String("test".into());
let cloned = val.clone();
assert_eq!(val, cloned);
}
#[test]
fn test_context_default() {
let ctx = Context::default();
assert!(ctx.is_empty());
}
#[test]
fn test_context_debug() {
let ctx = Context::new().with("key", "value");
let debug = format!("{:?}", ctx);
assert!(debug.contains("Context"));
}
#[test]
fn test_context_clone() {
let ctx = Context::new().with("key", "value");
let cloned = ctx.clone();
assert_eq!(ctx.get("key"), cloned.get("key"));
}
}