1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Request {
19 pub id: u64,
20 pub verb: String,
21 #[serde(default)]
22 pub args: serde_json::Value,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Response {
28 pub id: u64,
29 #[serde(flatten)]
30 pub outcome: ResponseOutcome,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(untagged)]
50pub enum ResponseOutcome {
51 Result { result: serde_json::Value },
52 Error { error: WireError },
53 Event { event: serde_json::Value },
54 End { end: EndMarker },
55}
56
57#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
61pub struct EndMarker {}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct WireError {
73 pub kind: WireErrorKind,
74 pub message: String,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub details: Option<serde_json::Value>,
77}
78
79impl WireError {
80 #[must_use]
81 pub fn new(kind: WireErrorKind, message: impl Into<String>) -> Self {
82 Self { kind, message: message.into(), details: None }
83 }
84
85 #[must_use]
86 pub fn with_details(mut self, details: serde_json::Value) -> Self {
87 self.details = Some(details);
88 self
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
95#[serde(rename_all = "snake_case")]
96pub enum WireErrorKind {
97 UnknownVerb,
98 BadArgs,
99 Internal,
100 Timeout,
104 NotImplemented,
106}
107
108pub fn encode_line<T: Serialize>(value: &T) -> Result<Vec<u8>, serde_json::Error> {
115 let mut buf = serde_json::to_vec(value)?;
116 buf.push(b'\n');
117 Ok(buf)
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn request_round_trips_through_json_with_args() {
126 let req =
127 Request { id: 42, verb: "stats".to_string(), args: serde_json::json!({ "scope": "all" }) };
128 let encoded = serde_json::to_string(&req).expect("serialize");
129 let decoded: Request = serde_json::from_str(&encoded).expect("deserialize");
130 assert_eq!(decoded.id, 42);
131 assert_eq!(decoded.verb, "stats");
132 assert_eq!(decoded.args, serde_json::json!({ "scope": "all" }));
133 }
134
135 #[test]
136 fn request_default_args_are_null() {
137 let raw = r#"{"id":1,"verb":"ping"}"#;
139 let req: Request = serde_json::from_str(raw).expect("deserialize");
140 assert!(req.args.is_null());
141 }
142
143 #[test]
144 fn response_result_serializes_with_flat_result_key() {
145 let resp = Response {
146 id: 7,
147 outcome: ResponseOutcome::Result { result: serde_json::json!({ "pong": true }) },
148 };
149 let value = serde_json::to_value(&resp).expect("to_value");
150 assert_eq!(value["id"], 7);
151 assert_eq!(value["result"], serde_json::json!({ "pong": true }));
152 assert!(value.get("error").is_none(), "result frame must not carry error key");
153 assert!(value.get("outcome").is_none(), "must flatten — no nested outcome key");
154 }
155
156 #[test]
157 fn response_error_serializes_with_flat_error_key() {
158 let resp = Response {
159 id: 3,
160 outcome: ResponseOutcome::Error {
161 error: WireError::new(WireErrorKind::UnknownVerb, "no such verb"),
162 },
163 };
164 let value = serde_json::to_value(&resp).expect("to_value");
165 assert_eq!(value["id"], 3);
166 assert_eq!(value["error"]["kind"], "unknown_verb");
167 assert_eq!(value["error"]["message"], "no such verb");
168 assert!(value.get("result").is_none());
169 assert!(value["error"].get("details").is_none(), "details omitted when None");
170 }
171
172 #[test]
173 fn wire_error_with_details_round_trips() {
174 let err = WireError::new(WireErrorKind::BadArgs, "compile failed")
175 .with_details(serde_json::json!({"file": "rules/a.json", "line": 12}));
176 let s = serde_json::to_string(&err).expect("serialize");
177 assert!(s.contains("\"details\""), "details present on the wire: {s}");
178 let back: WireError = serde_json::from_str(&s).expect("deserialize");
179 assert_eq!(back.kind, WireErrorKind::BadArgs);
180 assert_eq!(back.details.expect("details")["line"], 12);
181 }
182
183 #[test]
184 fn unknown_verb_kind_round_trips_via_snake_case() {
185 for kind in [
186 WireErrorKind::UnknownVerb,
187 WireErrorKind::BadArgs,
188 WireErrorKind::Internal,
189 WireErrorKind::Timeout,
190 WireErrorKind::NotImplemented,
191 ] {
192 let s = serde_json::to_string(&kind).expect("serialize kind");
193 let back: WireErrorKind = serde_json::from_str(&s).expect("deserialize kind");
194 assert_eq!(kind, back);
195 }
196 assert_eq!(serde_json::to_string(&WireErrorKind::UnknownVerb).unwrap(), "\"unknown_verb\"");
197 assert_eq!(serde_json::to_string(&WireErrorKind::BadArgs).unwrap(), "\"bad_args\"");
198 assert_eq!(serde_json::to_string(&WireErrorKind::Timeout).unwrap(), "\"timeout\"");
199 assert_eq!(
200 serde_json::to_string(&WireErrorKind::NotImplemented).unwrap(),
201 "\"not_implemented\""
202 );
203 }
204
205 #[test]
206 fn response_event_outcome_serializes_with_event_key() {
207 let resp = Response {
208 id: 9,
209 outcome: ResponseOutcome::Event { event: serde_json::json!({ "kind": "trajectory" }) },
210 };
211 let value = serde_json::to_value(&resp).expect("to_value");
212 assert_eq!(value["id"], 9);
213 assert_eq!(value["event"]["kind"], "trajectory");
214 assert!(value.get("result").is_none());
215 assert!(value.get("error").is_none());
216 assert!(value.get("end").is_none());
217 }
218
219 #[test]
220 fn response_end_outcome_serializes_as_empty_end_object() {
221 let resp = Response { id: 4, outcome: ResponseOutcome::End { end: EndMarker {} } };
222 let value = serde_json::to_value(&resp).expect("to_value");
223 assert_eq!(value["id"], 4);
224 assert_eq!(value["end"], serde_json::json!({}));
225 assert!(value.get("event").is_none());
226 }
227
228 #[test]
229 fn response_event_round_trips_through_json() {
230 let frames = vec![
234 Response { id: 1, outcome: ResponseOutcome::Result { result: serde_json::json!(42) } },
235 Response { id: 2, outcome: ResponseOutcome::Event { event: serde_json::json!("hi") } },
236 Response { id: 3, outcome: ResponseOutcome::End { end: EndMarker {} } },
237 ];
238 for f in frames {
239 let s = serde_json::to_string(&f).expect("serialize");
240 let back: Response = serde_json::from_str(&s).expect("deserialize");
241 assert_eq!(back.id, f.id);
242 match (&f.outcome, &back.outcome) {
243 (ResponseOutcome::Result { .. }, ResponseOutcome::Result { .. })
244 | (ResponseOutcome::Event { .. }, ResponseOutcome::Event { .. })
245 | (ResponseOutcome::End { .. }, ResponseOutcome::End { .. }) => {}
246 other => panic!("variant changed: {other:?}"),
247 }
248 }
249 }
250
251 #[test]
252 fn encode_line_appends_newline() {
253 let req = Request { id: 1, verb: "ping".to_string(), args: serde_json::Value::Null };
254 let bytes = encode_line(&req).expect("encode");
255 assert_eq!(*bytes.last().expect("non-empty"), b'\n');
256 let body = &bytes[..bytes.len() - 1];
258 let decoded: Request = serde_json::from_slice(body).expect("decode body");
259 assert_eq!(decoded.verb, "ping");
260 }
261}