use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ExecutionContext {
pub execution_id: i64,
pub step: String,
#[serde(default)]
pub variables: HashMap<String, serde_json::Value>,
#[serde(default, skip_serializing)]
pub secrets: HashMap<String, String>,
pub server_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub worker_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command_id: Option<String>,
#[serde(default)]
pub call_index: usize,
}
impl ExecutionContext {
pub fn new(execution_id: i64, step: impl Into<String>, server_url: impl Into<String>) -> Self {
Self {
execution_id,
step: step.into(),
variables: HashMap::new(),
secrets: HashMap::new(),
server_url: server_url.into(),
worker_id: None,
command_id: None,
call_index: 0,
}
}
pub fn set_variable(&mut self, name: impl Into<String>, value: serde_json::Value) {
self.variables.insert(name.into(), value);
}
pub fn get_variable(&self, name: &str) -> Option<&serde_json::Value> {
self.variables.get(name)
}
pub fn get_variable_str(&self, name: &str) -> Option<String> {
self.variables.get(name).map(|v| match v {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
_ => v.to_string(),
})
}
pub fn set_secret(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.secrets.insert(name.into(), value.into());
}
pub fn get_secret(&self, name: &str) -> Option<&str> {
self.secrets.get(name).map(|s| s.as_str())
}
pub fn with_worker_id(mut self, worker_id: impl Into<String>) -> Self {
self.worker_id = Some(worker_id.into());
self
}
pub fn with_command_id(mut self, command_id: impl Into<String>) -> Self {
self.command_id = Some(command_id.into());
self
}
pub fn next_call_index(&mut self) -> usize {
let idx = self.call_index;
self.call_index += 1;
idx
}
pub fn to_template_context(&self) -> HashMap<String, serde_json::Value> {
let mut ctx = self.variables.clone();
ctx.insert(
"execution_id".to_string(),
serde_json::json!(self.execution_id),
);
ctx.insert("step".to_string(), serde_json::json!(self.step));
ctx.insert("server_url".to_string(), serde_json::json!(self.server_url));
if let Some(ref worker_id) = self.worker_id {
ctx.insert("worker_id".to_string(), serde_json::json!(worker_id));
}
if let Some(ref command_id) = self.command_id {
ctx.insert("command_id".to_string(), serde_json::json!(command_id));
}
ctx
}
pub fn template_metadata(&self) -> Vec<(&'static str, serde_json::Value)> {
let mut meta: Vec<(&'static str, serde_json::Value)> = vec![
("execution_id", serde_json::json!(self.execution_id)),
("step", serde_json::json!(self.step)),
("server_url", serde_json::json!(self.server_url)),
];
if let Some(ref worker_id) = self.worker_id {
meta.push(("worker_id", serde_json::json!(worker_id)));
}
if let Some(ref command_id) = self.command_id {
meta.push(("command_id", serde_json::json!(command_id)));
}
meta
}
pub fn merge_variables(&mut self, other: &HashMap<String, serde_json::Value>) {
for (k, v) in other {
self.variables.insert(k.clone(), v.clone());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_new() {
let ctx = ExecutionContext::new(12345, "process_data", "http://localhost:8082");
assert_eq!(ctx.execution_id, 12345);
assert_eq!(ctx.step, "process_data");
assert_eq!(ctx.server_url, "http://localhost:8082");
}
#[test]
fn test_context_variables() {
let mut ctx = ExecutionContext::default();
ctx.set_variable("name", serde_json::json!("test"));
ctx.set_variable("count", serde_json::json!(42));
assert_eq!(ctx.get_variable("name"), Some(&serde_json::json!("test")));
assert_eq!(ctx.get_variable_str("count"), Some("42".to_string()));
assert_eq!(ctx.get_variable("missing"), None);
}
#[test]
fn test_context_secrets() {
let mut ctx = ExecutionContext::default();
ctx.set_secret("api_key", "secret123");
assert_eq!(ctx.get_secret("api_key"), Some("secret123"));
assert_eq!(ctx.get_secret("missing"), None);
}
#[test]
fn test_context_builder() {
let ctx = ExecutionContext::new(1, "step1", "http://localhost")
.with_worker_id("worker-1")
.with_command_id("cmd-123");
assert_eq!(ctx.worker_id, Some("worker-1".to_string()));
assert_eq!(ctx.command_id, Some("cmd-123".to_string()));
}
#[test]
fn test_context_call_index() {
let mut ctx = ExecutionContext::default();
assert_eq!(ctx.next_call_index(), 0);
assert_eq!(ctx.next_call_index(), 1);
assert_eq!(ctx.next_call_index(), 2);
}
#[test]
fn test_context_to_template() {
let mut ctx = ExecutionContext::new(12345, "step1", "http://localhost");
ctx.set_variable("input", serde_json::json!("value"));
let template_ctx = ctx.to_template_context();
assert_eq!(
template_ctx.get("execution_id"),
Some(&serde_json::json!(12345))
);
assert_eq!(template_ctx.get("step"), Some(&serde_json::json!("step1")));
assert_eq!(template_ctx.get("input"), Some(&serde_json::json!("value")));
}
#[test]
fn test_context_serialization() {
let ctx = ExecutionContext::new(12345, "step1", "http://localhost");
let json = serde_json::to_string(&ctx).unwrap();
assert!(json.contains("\"execution_id\":12345"));
assert!(!json.contains("secrets"));
}
}