polyhook_core/
response.rs1use crate::types::{CallerKind, HookResponse};
2use serde_json::{json, Value};
3
4pub fn serialize_response(resp: &HookResponse, caller: &CallerKind) -> Value {
6 match caller {
7 CallerKind::ClaudeCode | CallerKind::Unknown => serialize_claude_code(resp),
8 CallerKind::Cursor => serialize_cursor(resp),
9 CallerKind::Windsurf => serialize_windsurf(resp),
10 CallerKind::Cline => serialize_cline(resp),
11 CallerKind::Amp => serialize_amp(resp),
12 }
13}
14
15fn serialize_claude_code(resp: &HookResponse) -> Value {
20 match resp {
21 HookResponse::ApproveResponse(_) => json!({}),
22 HookResponse::BlockResponse(b) => {
23 json!({ "decision": "block", "reason": b.message })
24 }
25 HookResponse::ModifyResponse(m) => {
26 json!({ "decision": "approve", "tool_input": m.input })
27 }
28 }
29}
30
31fn serialize_cursor(resp: &HookResponse) -> Value {
32 match resp {
33 HookResponse::ApproveResponse(_) => json!({ "action": "allow" }),
34 HookResponse::BlockResponse(b) => {
35 json!({ "action": "deny", "message": b.message })
36 }
37 HookResponse::ModifyResponse(m) => {
38 json!({ "action": "modify", "args": m.input })
39 }
40 }
41}
42
43fn serialize_windsurf(resp: &HookResponse) -> Value {
44 match resp {
45 HookResponse::ApproveResponse(_) => json!({ "allow": true }),
46 HookResponse::BlockResponse(b) => {
47 json!({ "allow": false, "reason": b.message })
48 }
49 HookResponse::ModifyResponse(m) => {
50 json!({ "allow": true, "modified_parameters": m.input })
51 }
52 }
53}
54
55fn serialize_cline(resp: &HookResponse) -> Value {
56 match resp {
57 HookResponse::ApproveResponse(_) => json!({ "approved": true }),
58 HookResponse::BlockResponse(b) => {
59 json!({ "approved": false, "reason": b.message })
60 }
61 HookResponse::ModifyResponse(m) => {
62 json!({ "approved": true, "modifiedInput": m.input })
63 }
64 }
65}
66
67fn serialize_amp(resp: &HookResponse) -> Value {
68 match resp {
69 HookResponse::ApproveResponse(_) => json!({ "result": "allow" }),
70 HookResponse::BlockResponse(b) => {
71 json!({ "result": "deny", "reason": b.message })
72 }
73 HookResponse::ModifyResponse(m) => {
74 json!({ "result": "allow", "modified": m.input })
75 }
76 }
77}
78
79#[cfg(test)]
84mod tests {
85 use super::serialize_response;
86 use crate::{CallerKind, HookResponse};
87 use serde_json::json;
88
89 #[test]
90 fn claude_code_approve() {
91 let val = serialize_response(&HookResponse::approve(), &CallerKind::ClaudeCode);
92 assert_eq!(val, json!({}));
93 }
94
95 #[test]
96 fn claude_code_block() {
97 let val = serialize_response(&HookResponse::block("not allowed"), &CallerKind::ClaudeCode);
98 assert_eq!(val["decision"], json!("block"));
99 assert_eq!(val["reason"], json!("not allowed"));
100 }
101
102 #[test]
103 fn claude_code_modify() {
104 let new_input = json!({"command": "echo safe"});
105 let val = serialize_response(&HookResponse::modify(new_input.clone()), &CallerKind::ClaudeCode);
106 assert_eq!(val["decision"], json!("approve"));
107 assert_eq!(val["tool_input"], new_input);
108 }
109
110 #[test]
111 fn unknown_approve() {
112 let val = serialize_response(&HookResponse::approve(), &CallerKind::Unknown);
113 assert_eq!(val, json!({}));
114 }
115
116 #[test]
117 fn unknown_block() {
118 let val = serialize_response(&HookResponse::block("denied"), &CallerKind::Unknown);
119 assert_eq!(val["decision"], json!("block"));
120 assert_eq!(val["reason"], json!("denied"));
121 }
122
123 #[test]
124 fn unknown_modify() {
125 let new_input = json!({"x": 1});
126 let val = serialize_response(&HookResponse::modify(new_input.clone()), &CallerKind::Unknown);
127 assert_eq!(val["decision"], json!("approve"));
128 assert_eq!(val["tool_input"], new_input);
129 }
130
131 #[test]
132 fn cursor_approve() {
133 let val = serialize_response(&HookResponse::approve(), &CallerKind::Cursor);
134 assert_eq!(val["action"], json!("allow"));
135 }
136
137 #[test]
138 fn cursor_block() {
139 let val = serialize_response(&HookResponse::block("too risky"), &CallerKind::Cursor);
140 assert_eq!(val["action"], json!("deny"));
141 assert_eq!(val["message"], json!("too risky"));
142 }
143
144 #[test]
145 fn cursor_modify() {
146 let new_input = json!({"command": "echo safe"});
147 let val = serialize_response(&HookResponse::modify(new_input.clone()), &CallerKind::Cursor);
148 assert_eq!(val["action"], json!("modify"));
149 assert_eq!(val["args"], new_input);
150 }
151
152 #[test]
153 fn windsurf_approve() {
154 let val = serialize_response(&HookResponse::approve(), &CallerKind::Windsurf);
155 assert_eq!(val["allow"], json!(true));
156 }
157
158 #[test]
159 fn windsurf_block() {
160 let val = serialize_response(&HookResponse::block("blocked by policy"), &CallerKind::Windsurf);
161 assert_eq!(val["allow"], json!(false));
162 assert_eq!(val["reason"], json!("blocked by policy"));
163 }
164
165 #[test]
166 fn windsurf_modify() {
167 let new_input = json!({"path": "/safe/dir"});
168 let val = serialize_response(&HookResponse::modify(new_input.clone()), &CallerKind::Windsurf);
169 assert_eq!(val["allow"], json!(true));
170 assert_eq!(val["modified_parameters"], new_input);
171 }
172
173 #[test]
174 fn cline_approve() {
175 let val = serialize_response(&HookResponse::approve(), &CallerKind::Cline);
176 assert_eq!(val["approved"], json!(true));
177 }
178
179 #[test]
180 fn cline_block() {
181 let val = serialize_response(&HookResponse::block("cline blocked"), &CallerKind::Cline);
182 assert_eq!(val["approved"], json!(false));
183 assert_eq!(val["reason"], json!("cline blocked"));
184 }
185
186 #[test]
187 fn cline_modify() {
188 let new_input = json!({"command": "ls /safe"});
189 let val = serialize_response(&HookResponse::modify(new_input.clone()), &CallerKind::Cline);
190 assert_eq!(val["approved"], json!(true));
191 assert_eq!(val["modifiedInput"], new_input);
192 }
193
194 #[test]
195 fn amp_approve() {
196 let val = serialize_response(&HookResponse::approve(), &CallerKind::Amp);
197 assert_eq!(val["result"], json!("allow"));
198 }
199
200 #[test]
201 fn amp_block() {
202 let val = serialize_response(&HookResponse::block("amp denied"), &CallerKind::Amp);
203 assert_eq!(val["result"], json!("deny"));
204 assert_eq!(val["reason"], json!("amp denied"));
205 }
206
207 #[test]
208 fn amp_modify() {
209 let new_input = json!({"command": "echo ok"});
210 let val = serialize_response(&HookResponse::modify(new_input.clone()), &CallerKind::Amp);
211 assert_eq!(val["result"], json!("allow"));
212 assert_eq!(val["modified"], new_input);
213 }
214}