#![allow(dead_code)]
use chrono::{DateTime, Utc};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::time::{Duration as StdDuration, SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolicContext {
memory: HashMap<String, String>,
children: HashMap<String, SymbolicContext>,
last_modified: HashMap<String, DateTime<Utc>>,
key_meta: HashMap<String, SymbolicKeyMeta>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolicKeyMeta {
pub last_modified: DateTime<Utc>,
pub ttl: Option<StdDuration>,
pub origin: Option<String>,
pub phase: Option<String>,
pub source_agent: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SymbolicDiff {
pub added: HashMap<String, String>,
pub removed: HashMap<String, String>,
pub changed: HashMap<String, (String, String)>, }
pub trait SymbolicObserver {
fn on_set(&self, key: &str, value: &str);
fn on_merge(&self, other: &SymbolicContext);
fn on_resolve(&self, path: &str, result: Option<&str>);
}
impl SymbolicContext {
pub fn new() -> Self {
Self {
memory: HashMap::new(),
children: HashMap::new(),
last_modified: HashMap::new(),
key_meta: HashMap::new(),
}
}
pub fn set(&mut self, key: &str, value: &str) {
self.memory.insert(key.to_string(), value.to_string());
let now = Utc::now();
self.last_modified.insert(key.to_string(), now);
self.key_meta.insert(
key.to_string(),
SymbolicKeyMeta {
last_modified: now,
ttl: None,
origin: None,
phase: None,
source_agent: None,
},
);
}
pub fn set_with_meta(&mut self, key: &str, value: &str, meta: SymbolicKeyMeta) {
self.memory.insert(key.to_string(), value.to_string());
self.last_modified
.insert(key.to_string(), meta.last_modified);
self.key_meta.insert(key.to_string(), meta);
}
pub fn merge(&mut self, other: &SymbolicContext) {
for (k, v) in &other.memory {
self.memory.insert(k.clone(), v.clone());
}
}
pub fn get(&self, key: &str) -> Option<&String> {
self.memory.get(key)
}
pub fn set_child(&mut self, name: &str, ctx: SymbolicContext) {
self.children.insert(name.to_string(), ctx);
}
pub fn get_child(&self, name: &str) -> Option<&SymbolicContext> {
self.children.get(name)
}
pub fn resolve_path(&self, path: &str) -> Option<String> {
let result = {
let mut parts = path.split('.');
let first = parts.next()?;
if let Some(child) = self.children.get(first) {
return child.resolve_path(&parts.collect::<Vec<_>>().join("."));
}
let current = self.memory.get(first)?;
let mut value: Value = match serde_json::from_str(current) {
Ok(json) => json,
Err(_) => Value::String(current.clone()),
};
for part in parts {
match &value {
Value::Object(map) => {
value = map.get(part)?.clone();
}
_ => return None,
}
}
match value {
Value::String(s) => Some(s),
v => Some(v.to_string()),
}
};
result
}
pub fn enter_scope(&mut self, scope: &str) -> &mut SymbolicContext {
self.children
.entry(scope.to_string())
.or_insert_with(SymbolicContext::new)
}
pub fn set_ttl(&mut self, key: &str, ttl: StdDuration) {
if let Some(meta) = self.key_meta.get_mut(key) {
meta.ttl = Some(ttl);
}
}
pub fn cleanup_expired(&mut self) {
let now = SystemTime::now();
let expired: Vec<_> = self
.key_meta
.iter()
.filter_map(|(k, meta)| {
if let Some(ttl) = meta.ttl {
let lm = meta.last_modified.with_timezone(&Utc);
let lm_sys = lm.into();
if now.duration_since(lm_sys).unwrap_or(StdDuration::ZERO) > ttl {
return Some(k.clone());
}
}
None
})
.collect();
for k in expired {
self.memory.remove(&k);
self.key_meta.remove(&k);
}
}
pub fn flatten(&self) -> HashMap<String, String> {
let mut flat = self.memory.clone();
for (child_name, child_ctx) in &self.children {
for (k, v) in child_ctx.flatten() {
flat.insert(format!("{}.{}", child_name, k), v);
}
}
flat
}
pub fn diff(&self, other: &Self) -> SymbolicDiff {
let mut added = HashMap::new();
let mut removed = HashMap::new();
let mut changed = HashMap::new();
let self_flat = self.flatten();
let other_flat = other.flatten();
for (k, v) in &other_flat {
if !self_flat.contains_key(k) {
added.insert(k.clone(), v.clone());
} else if self_flat[k] != *v {
changed.insert(k.clone(), (self_flat[k].clone(), v.clone()));
}
}
for (k, v) in &self_flat {
if !other_flat.contains_key(k) {
removed.insert(k.clone(), v.clone());
}
}
SymbolicDiff {
added,
removed,
changed,
}
}
pub fn to_json(&self) -> String {
serde_json::to_string(&self.flatten()).unwrap()
}
pub fn from_json(json: &str) -> Self {
let flat: HashMap<String, String> = serde_json::from_str(json).unwrap();
let mut ctx = SymbolicContext::new();
for (k, v) in flat {
ctx.set(&k, &v);
}
ctx
}
pub fn resolve_as_type<T: DeserializeOwned>(&self, path: &str) -> Option<T> {
self.resolve_path(path)
.and_then(|s| serde_json::from_str(&s).ok())
}
pub fn interpolate(&self, input: &str) -> String {
let mut out = String::new();
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c == '$' && chars.peek() == Some(&'{') {
chars.next(); let mut var = String::new();
while let Some(&nc) = chars.peek() {
if nc == '}' {
chars.next();
break;
}
var.push(nc);
chars.next();
}
if let Some(val) = self.resolve_path(&var) {
out.push_str(&val);
} else {
out.push_str(&format!("${{{}}}", var));
}
} else {
out.push(c);
}
}
out
}
pub fn resolve_or_default(&self, path: &str, default: &str) -> String {
self.resolve_path(path)
.unwrap_or_else(|| default.to_string())
}
pub fn snapshot(&self) -> HashMap<String, String> {
self.memory.clone()
}
pub fn restore(&mut self, snapshot: HashMap<String, String>) {
self.memory = snapshot;
}
pub fn to_symbolic_ir(&self) -> Value {
let mut obj = serde_json::Map::new();
for (k, v) in &self.memory {
obj.insert(k.clone(), Value::String(v.clone()));
}
for (k, child) in &self.children {
obj.insert(k.clone(), child.to_symbolic_ir());
}
Value::Object(obj)
}
}