pub(crate) mod agent;
pub(crate) mod command;
pub(crate) mod http;
pub(crate) mod wasm;
pub(crate) use agent::AgentHandler;
pub(crate) use command::CommandHandler;
pub(crate) use http::HttpHandler;
pub(crate) use wasm::WasmHandler;
use crate::result::HookResult;
use std::time::Duration;
use thiserror::Error;
#[derive(Debug, Error)]
pub(crate) enum HandlerError {
#[error("command execution failed: {0}")]
CommandFailed(String),
#[error("HTTP request failed: {0}")]
HttpFailed(String),
#[error("WASM execution failed: {0}")]
WasmFailed(String),
#[error("agent execution failed: {0}")]
AgentFailed(String),
#[error("handler timed out after {0:?}")]
Timeout(Duration),
#[error("handler not implemented: {0}")]
NotImplemented(String),
#[error("invalid handler configuration: {0}")]
InvalidConfiguration(String),
#[error("failed to parse handler output: {0}")]
ParseError(String),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
}
pub(crate) type HandlerResult<T> = Result<T, HandlerError>;
pub(crate) fn parse_hook_result(output: &str) -> HandlerResult<HookResult> {
let trimmed = output.trim();
if trimmed.is_empty() {
return Ok(HookResult::Continue);
}
if trimmed.starts_with('{') {
return serde_json::from_str(trimmed)
.map_err(|e| HandlerError::ParseError(format!("invalid JSON: {e}")));
}
let lower = trimmed.to_lowercase();
if lower == "continue" {
return Ok(HookResult::Continue);
}
if let Some(reason) = lower.strip_prefix("block:") {
return Ok(HookResult::block(reason.trim()));
}
if let Some(question) = lower.strip_prefix("ask:") {
return Ok(HookResult::ask(question.trim()));
}
Ok(HookResult::Continue)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_hook_result_empty() {
let result = parse_hook_result("").unwrap();
assert!(matches!(result, HookResult::Continue));
}
#[test]
fn test_parse_hook_result_continue() {
let result = parse_hook_result("continue").unwrap();
assert!(matches!(result, HookResult::Continue));
}
#[test]
fn test_parse_hook_result_block() {
let result = parse_hook_result("block: Policy violation").unwrap();
assert!(matches!(result, HookResult::Block { reason } if reason == "policy violation"));
}
#[test]
fn test_parse_hook_result_ask() {
let result = parse_hook_result("ask: Are you sure?").unwrap();
assert!(matches!(result, HookResult::Ask { question, .. } if question == "are you sure?"));
}
#[test]
fn test_parse_hook_result_json() {
let json = r#"{"action": "block", "reason": "Not allowed"}"#;
let result = parse_hook_result(json).unwrap();
assert!(matches!(result, HookResult::Block { .. }));
}
}