use crate::fixture::FixtureEvent;
use ma_harness_core::event::{EventType, SessionEvent, Severity};
use serde_json::Value;
use thiserror::Error;
pub fn event_type_from_str(s: &str) -> Option<EventType> {
match s {
"SessionStart" => Some(EventType::SessionStart),
"SessionEnd" => Some(EventType::SessionEnd),
"RunStart" => Some(EventType::RunStart),
"RunEnd" => Some(EventType::RunEnd),
"ModelRequest" => Some(EventType::ModelRequest),
"ModelResponse" => Some(EventType::ModelResponse),
"ModelError" => Some(EventType::ModelError),
"ToolCall" => Some(EventType::ToolCall),
"ToolResult" => Some(EventType::ToolResult),
"ToolError" => Some(EventType::ToolError),
"UserInput" => Some(EventType::UserInput),
"UserCancel" => Some(EventType::UserCancel),
"SandboxViolation" => Some(EventType::SandboxViolation),
"SandboxConfig" => Some(EventType::SandboxConfig),
"ApprovalRequest" => Some(EventType::ApprovalRequest),
"ApprovalDecision" => Some(EventType::ApprovalDecision),
_ => None,
}
}
pub fn event_type_to_str(t: EventType) -> &'static str {
match t {
EventType::Unspecified => "Unspecified",
EventType::SessionStart => "SessionStart",
EventType::SessionEnd => "SessionEnd",
EventType::RunStart => "RunStart",
EventType::RunEnd => "RunEnd",
EventType::ModelRequest => "ModelRequest",
EventType::ModelResponse => "ModelResponse",
EventType::ModelError => "ModelError",
EventType::ToolCall => "ToolCall",
EventType::ToolResult => "ToolResult",
EventType::ToolError => "ToolError",
EventType::UserInput => "UserInput",
EventType::UserCancel => "UserCancel",
EventType::SandboxViolation => "SandboxViolation",
EventType::SandboxConfig => "SandboxConfig",
EventType::ApprovalRequest => "ApprovalRequest",
EventType::ApprovalDecision => "ApprovalDecision",
}
}
pub fn fixture_to_session(
session_id: &str,
fixture_event: &FixtureEvent,
) -> Result<SessionEvent, ConvertError> {
let event_type = event_type_from_str(&fixture_event.event_type)
.ok_or_else(|| ConvertError::UnknownEventType(fixture_event.event_type.clone()))?;
let mut event = SessionEvent::new(session_id, event_type);
if let Some(sev) = fixture_event
.payload
.get("severity")
.and_then(|v| v.as_str())
{
event = match sev {
"Debug" => event.with_severity(Severity::Debug),
"Info" => event.with_severity(Severity::Info),
"Warn" => event.with_severity(Severity::Warn),
"Error" => event.with_severity(Severity::Error),
"Fatal" => event.with_severity(Severity::Fatal),
_ => event,
};
}
if let Some(rid) = fixture_event.payload.get("run_id").and_then(|v| v.as_str()) {
event = event.with_run_id(rid);
}
if let Some(pn) = fixture_event
.payload
.get("plugin_name")
.and_then(|v| v.as_str())
{
event = event.with_plugin(pn);
}
if let Some(err) = fixture_event
.payload
.get("error_message")
.and_then(|v| v.as_str())
{
event = event.with_error(err);
}
event = event
.with_payload(&fixture_event.payload)
.map_err(ConvertError::Serialize)?;
Ok(event)
}
pub fn session_to_fixture(event: &SessionEvent) -> FixtureEvent {
let payload: Value = event
.payload_json
.as_deref()
.and_then(|s| serde_json::from_str(s).ok())
.unwrap_or(Value::Null);
FixtureEvent {
event_type: event_type_to_str(event.event_type).to_string(),
payload,
timestamp_ms: Some(event.ts.timestamp_millis() as u64),
}
}
#[derive(Debug, Error)]
pub enum ConvertError {
#[error("unknown event type: {0}")]
UnknownEventType(String),
#[error("payload serialize error: {0}")]
Serialize(#[from] serde_json::Error),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fixture::FixtureEvent;
#[test]
fn event_type_roundtrip() {
let names = [
"SessionStart",
"SessionEnd",
"RunStart",
"RunEnd",
"ModelRequest",
"ModelResponse",
"ModelError",
"ToolCall",
"ToolResult",
"ToolError",
"UserInput",
"UserCancel",
"SandboxViolation",
"SandboxConfig",
];
for name in names {
let t = event_type_from_str(name).unwrap_or_else(|| panic!("missing: {name}"));
assert_eq!(event_type_to_str(t), name);
}
}
#[test]
fn event_type_from_str_unknown_returns_none() {
assert!(event_type_from_str("NotAType").is_none());
assert!(event_type_from_str("").is_none());
}
#[test]
fn fixture_to_session_basic() {
let fe = FixtureEvent {
event_type: "ToolCall".to_string(),
payload: serde_json::json!({"tool": "bash", "args": {"command": "echo hi"}}),
timestamp_ms: None,
};
let se = fixture_to_session("s1", &fe).unwrap();
assert_eq!(se.event_type, EventType::ToolCall);
assert_eq!(se.session_id, "s1");
assert!(se.model_visible); }
#[test]
fn fixture_to_session_with_severity() {
let fe = FixtureEvent {
event_type: "ModelError".to_string(),
payload: serde_json::json!({
"severity": "Error",
"error_message": "timeout",
}),
timestamp_ms: None,
};
let se = fixture_to_session("s1", &fe).unwrap();
assert_eq!(se.severity, Severity::Error);
assert_eq!(se.error_message.as_deref(), Some("timeout"));
}
#[test]
fn fixture_to_session_with_run_id() {
let fe = FixtureEvent {
event_type: "RunStart".to_string(),
payload: serde_json::json!({"run_id": "r-001", "model": "stub"}),
timestamp_ms: None,
};
let se = fixture_to_session("s1", &fe).unwrap();
assert_eq!(se.run_id.as_deref(), Some("r-001"));
}
#[test]
fn fixture_to_session_unknown_type_errors() {
let fe = FixtureEvent {
event_type: "Garbage".to_string(),
payload: serde_json::json!({}),
timestamp_ms: None,
};
let r = fixture_to_session("s1", &fe);
assert!(matches!(r, Err(ConvertError::UnknownEventType(_))));
}
#[test]
fn session_to_fixture_roundtrip() {
let fe = FixtureEvent {
event_type: "RunStart".to_string(),
payload: serde_json::json!({"model": "stub", "temperature": 0.7}),
timestamp_ms: None,
};
let se = fixture_to_session("sess", &fe).unwrap();
let fe2 = session_to_fixture(&se);
assert_eq!(fe2.event_type, "RunStart");
assert_eq!(fe2.payload, fe.payload);
}
#[test]
fn session_to_fixture_empty_payload() {
let se = SessionEvent::new("s1", EventType::RunStart);
let fe = session_to_fixture(&se);
assert_eq!(fe.event_type, "RunStart");
assert_eq!(fe.payload, Value::Null);
}
}