use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraceEvent {
pub lineno: u32,
pub event: TraceEventKind,
pub context: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TraceEventKind {
Line,
Call {
function: String,
},
Return {
function: String,
},
Exception {
message: String,
},
CallbackStart {
name: String,
},
CallbackEnd {
name: String,
duration_ms: u64,
},
}
#[async_trait]
pub trait TraceHandler: Send + Sync {
async fn on_trace(&self, event: TraceEvent);
}
#[async_trait]
pub trait OutputHandler: Send + Sync {
async fn on_output(&self, chunk: &str);
async fn on_stderr(&self, chunk: &str) {
let _ = chunk;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trace_event_serializes_correctly() {
let event = TraceEvent {
lineno: 42,
event: TraceEventKind::Line,
context: None,
};
let json = serde_json::to_string(&event).unwrap_or_default();
assert!(json.contains("42"));
assert!(json.contains("line"));
}
#[test]
fn trace_event_kind_variants_serialize() {
let call = TraceEventKind::Call {
function: "test_fn".to_string(),
};
let json = serde_json::to_string(&call).unwrap_or_default();
assert!(json.contains("call"));
assert!(json.contains("test_fn"));
let callback_end = TraceEventKind::CallbackEnd {
name: "http.get".to_string(),
duration_ms: 150,
};
let json = serde_json::to_string(&callback_end).unwrap_or_default();
assert!(json.contains("callback_end"));
assert!(json.contains("150"));
}
}