kaizen/collect/hooks/
cursor.rs1use super::{EventKind, HookEvent};
8use anyhow::{Context, Result, bail};
9use serde_json::Value;
10
11pub fn parse_cursor_hook(input: &str) -> Result<HookEvent> {
16 let v: Value = serde_json::from_str(input.trim()).context("cursor hook: invalid JSON")?;
17 let obj = v.as_object().context("cursor hook: expected JSON object")?;
18
19 let kind_str = obj
20 .get("event")
21 .and_then(|v| v.as_str())
22 .context("cursor hook: missing 'event' field")?;
23
24 let session_id = obj
25 .get("session_id")
26 .and_then(|v| v.as_str())
27 .unwrap_or("")
28 .to_string();
29
30 let ts_ms = obj
31 .get("timestamp_ms")
32 .and_then(|v| v.as_u64())
33 .unwrap_or(0);
34
35 if session_id.is_empty() {
36 bail!("cursor hook: missing 'session_id' field");
37 }
38
39 Ok(HookEvent {
40 kind: EventKind::parse(kind_str),
41 session_id,
42 ts_ms,
43 payload: Value::Object(obj.clone()),
44 })
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[test]
52 fn parse_stop_fixture() {
53 let json = include_str!("../../../tests/fixtures/hooks/cursor_stop.json");
54 let ev = parse_cursor_hook(json).unwrap();
55 assert_eq!(ev.kind, EventKind::Stop);
56 assert!(!ev.session_id.is_empty());
57 }
58
59 #[test]
60 fn missing_event_field_errors() {
61 let err = parse_cursor_hook(r#"{"session_id":"s1","timestamp_ms":0}"#);
62 assert!(err.is_err());
63 }
64}