use serde_json::{json, Value};
use std::sync::Arc;
use parking_lot::RwLock;
pub type GetOption = Box<dyn Fn(Vec<u8>) -> crate::runtime::Result<Vec<u8>> + Send + Sync>;
pub type SetOption = Box<dyn Fn(Vec<u8>) -> crate::runtime::Result<Vec<u8>> + Send + Sync>;
#[derive(Clone)]
pub struct Context {
id: String,
data: Arc<RwLock<Value>>,
}
impl Context {
pub fn new(data: &[u8]) -> Self {
let value: Value = if data.is_empty() {
json!({})
} else {
serde_json::from_slice(data).unwrap_or_else(|_| json!({}))
};
let id = value
.get("id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
Self {
id,
data: Arc::new(RwLock::new(value)),
}
}
pub fn get_id(&self) -> &str {
&self.id
}
pub fn set(&self, path: &str, value: impl Into<Value>) -> crate::runtime::Result<()> {
let path = convert_path(path);
let mut data = self.data.write();
set_nested_value(&mut data, &path, value.into());
Ok(())
}
pub fn get(&self, path: &str) -> Option<Value> {
let path = convert_path(path);
let data = self.data.read();
get_nested_value(&data, &path)
}
pub fn get_string(&self, path: &str) -> String {
self.get(path)
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_default()
}
pub fn get_bool(&self, path: &str) -> bool {
self.get(path)
.and_then(|v| v.as_bool())
.unwrap_or(false)
}
pub fn get_int(&self, path: &str) -> i64 {
self.get(path)
.and_then(|v| v.as_i64())
.unwrap_or(0)
}
pub fn get_float(&self, path: &str) -> f64 {
self.get(path)
.and_then(|v| v.as_f64())
.unwrap_or(0.0)
}
pub fn get_raw(&self) -> crate::runtime::Result<Vec<u8>> {
let data = self.data.read();
Ok(serde_json::to_vec(&*data)?)
}
pub fn get_raw_with_options(&self, options: &[GetOption]) -> crate::runtime::Result<Vec<u8>> {
let mut raw = self.get_raw()?;
for opt in options {
raw = opt(raw)?;
}
Ok(raw)
}
pub fn set_raw(&self, data: Option<Vec<u8>>) -> crate::runtime::Result<()> {
match data {
Some(bytes) => {
let value: Value = serde_json::from_slice(&bytes)?;
*self.data.write() = value;
}
None => {
*self.data.write() = Value::Null;
}
}
Ok(())
}
pub fn set_raw_with_options(
&self,
data: Vec<u8>,
options: &[SetOption],
) -> crate::runtime::Result<()> {
let mut raw = data;
for opt in options {
raw = opt(raw)?;
}
self.set_raw(Some(raw))
}
pub fn is_empty(&self) -> bool {
let data = self.data.read();
data.is_null() || (data.is_object() && data.as_object().map_or(true, |o| o.is_empty()))
}
}
fn convert_path(path: &str) -> String {
path.replace('[', ".").replace(']', "")
}
fn get_nested_value(value: &Value, path: &str) -> Option<Value> {
let parts: Vec<&str> = path.split('.').filter(|s| !s.is_empty()).collect();
let mut current = value;
for part in parts {
if let Ok(index) = part.parse::<usize>() {
current = current.get(index)?;
} else {
current = current.get(part)?;
}
}
Some(current.clone())
}
fn set_nested_value(value: &mut Value, path: &str, new_value: Value) {
let parts: Vec<&str> = path.split('.').filter(|s| !s.is_empty()).collect();
if parts.is_empty() {
return;
}
if parts.len() == 1 {
let part = parts[0];
if let Ok(index) = part.parse::<usize>() {
if !value.is_array() {
*value = json!([]);
}
if let Some(arr) = value.as_array_mut() {
while arr.len() <= index {
arr.push(Value::Null);
}
arr[index] = new_value;
}
} else {
if !value.is_object() {
*value = json!({});
}
value[part] = new_value;
}
return;
}
let first = parts[0];
let rest = parts[1..].join(".");
if let Ok(index) = first.parse::<usize>() {
if !value.is_array() {
*value = json!([]);
}
if let Some(arr) = value.as_array_mut() {
while arr.len() <= index {
arr.push(json!({}));
}
set_nested_value(&mut arr[index], &rest, new_value);
}
} else {
if !value.is_object() {
*value = json!({});
}
if value.get(first).is_none() {
value[first] = json!({});
}
if let Some(child) = value.get_mut(first) {
set_nested_value(child, &rest, new_value);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_new() {
let ctx = Context::new(b"{}");
assert!(ctx.is_empty());
}
#[test]
fn test_context_set_get() {
let ctx = Context::new(b"{}");
ctx.set("foo", "bar").unwrap();
assert_eq!(ctx.get_string("foo"), "bar");
}
#[test]
fn test_context_nested() {
let ctx = Context::new(b"{}");
ctx.set("foo.bar.baz", 42).unwrap();
assert_eq!(ctx.get_int("foo.bar.baz"), 42);
}
#[test]
fn test_context_array() {
let ctx = Context::new(b"{}");
ctx.set("items.0", "first").unwrap();
ctx.set("items.1", "second").unwrap();
assert_eq!(ctx.get_string("items.0"), "first");
assert_eq!(ctx.get_string("items.1"), "second");
}
}