1use gwk_domain::protocol::KernelErrorCode;
29use serde::Deserialize;
30use serde::de::{DeserializeOwned, MapAccess, SeqAccess, Visitor};
31
32use super::WireError;
33
34const DUPLICATE_KEY: &str = "duplicate object key";
41
42struct NoDuplicateKeys(serde_json::Value);
44
45impl<'de> Deserialize<'de> for NoDuplicateKeys {
46 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
47 d.deserialize_any(StrictValue).map(NoDuplicateKeys)
48 }
49}
50
51struct StrictValue;
52
53impl<'de> Visitor<'de> for StrictValue {
54 type Value = serde_json::Value;
55
56 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
57 f.write_str("a JSON value with no duplicate object key")
58 }
59
60 fn visit_unit<E>(self) -> Result<Self::Value, E> {
61 Ok(serde_json::Value::Null)
62 }
63
64 fn visit_none<E>(self) -> Result<Self::Value, E> {
65 Ok(serde_json::Value::Null)
66 }
67
68 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
69 Ok(serde_json::Value::Bool(v))
70 }
71
72 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
73 Ok(serde_json::Value::Number(v.into()))
74 }
75
76 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
77 Ok(serde_json::Value::Number(v.into()))
78 }
79
80 fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Self::Value, E> {
81 serde_json::Number::from_f64(v)
82 .map(serde_json::Value::Number)
83 .ok_or_else(|| E::custom("a non-finite number is not JSON"))
84 }
85
86 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
87 Ok(serde_json::Value::String(v.to_owned()))
88 }
89
90 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
91 let mut out = Vec::new();
92 while let Some(NoDuplicateKeys(value)) = seq.next_element()? {
93 out.push(value);
94 }
95 Ok(serde_json::Value::Array(out))
96 }
97
98 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
99 let mut out = serde_json::Map::new();
100 while let Some(key) = map.next_key::<String>()? {
101 let NoDuplicateKeys(value) = map.next_value()?;
102 if out.insert(key.clone(), value).is_some() {
106 return Err(serde::de::Error::custom(format!("{DUPLICATE_KEY} {key:?}")));
107 }
108 }
109 Ok(serde_json::Value::Object(out))
110 }
111}
112
113pub fn decode<T: DeserializeOwned>(body: &[u8]) -> Result<T, WireError> {
120 let text = std::str::from_utf8(body).map_err(|e| {
121 WireError::new(
122 KernelErrorCode::Schema,
123 format!("frame body is not UTF-8: {e}"),
124 )
125 })?;
126
127 let mut stream = serde_json::Deserializer::from_str(text).into_iter::<NoDuplicateKeys>();
133 let NoDuplicateKeys(value) = stream
134 .next()
135 .transpose()
136 .map_err(|e| {
137 let text = e.to_string();
138 let code = if text.starts_with(DUPLICATE_KEY) {
139 KernelErrorCode::DuplicateKey
140 } else {
141 KernelErrorCode::Schema
142 };
143 WireError::new(code, format!("frame body: {text}"))
144 })?
145 .ok_or_else(|| WireError::new(KernelErrorCode::Schema, "frame body holds no JSON value"))?;
146 let rest = text[stream.byte_offset()..].trim();
150 if !rest.is_empty() {
151 return Err(WireError::new(
152 KernelErrorCode::Schema,
153 format!("{} trailing bytes after the JSON value", rest.len()),
154 ));
155 }
156
157 serde_json::from_value(value)
160 .map_err(|e| WireError::new(KernelErrorCode::Validation, format!("frame body: {e}")))
161}
162
163#[cfg(test)]
164mod tests {
165 use gwk_domain::protocol::{ClientControl, KernelRequest};
166
167 use super::*;
168
169 fn control(raw: &str) -> Result<ClientControl, WireError> {
170 decode::<ClientControl>(raw.as_bytes())
171 }
172
173 #[test]
174 fn a_well_formed_request_decodes() {
175 let ok = control(r#"{"type":"request","request_id":"r-1","request":{"type":"health"}}"#)
176 .expect("decode");
177 match ok {
178 ClientControl::Request { request, .. } => {
179 assert_eq!(request, KernelRequest::Health {});
180 }
181 other => panic!("{other:?}"),
182 }
183 }
184
185 #[test]
186 fn a_field_less_request_still_refuses_an_extra_field() {
187 let error = control(
191 r#"{"type":"request","request_id":"r-1","request":{"type":"health","limit":500}}"#,
192 )
193 .expect_err("extra field accepted");
194 assert_eq!(error.code, KernelErrorCode::Validation);
195 assert!(error.message.contains("limit"), "{error}");
196 }
197
198 #[test]
199 fn an_unknown_field_on_a_known_request_is_refused() {
200 let error = control(
201 r#"{"type":"request","request_id":"r-1","request":{"type":"read_events","limit":5,"depth":2}}"#,
202 )
203 .expect_err("unknown field accepted");
204 assert_eq!(error.code, KernelErrorCode::Validation);
205 assert!(error.message.contains("depth"), "{error}");
206 }
207
208 #[test]
209 fn a_duplicate_key_is_refused_at_every_depth() {
210 for raw in [
214 r#"{"type":"request","type":"request","request_id":"r-1","request":{"type":"health"}}"#,
215 r#"{"type":"request","request_id":"r-1","request":{"type":"submit_command","envelope":{"command_id":"c","project_id":"p","command_type":"ingest_record","schema_version":1,"issued_at":"t","actor":{"kind":"kernel"},"origin":{"system":"gw"},"idempotency_key":"k","payload":{"deep":{"cost":1,"cost":2}}}}}"#,
216 ] {
217 let error = control(raw).expect_err("duplicate key accepted");
218 assert_eq!(error.code, KernelErrorCode::DuplicateKey);
219 assert!(error.message.contains(DUPLICATE_KEY), "{error}");
220 }
221 }
222
223 #[test]
224 fn a_legitimate_repeat_in_two_different_objects_is_not_a_duplicate() {
225 control(
229 r#"{"type":"request","request_id":"r-1","request":{"type":"submit_command","envelope":{"command_id":"c","project_id":"p","command_type":"ingest_record","schema_version":1,"issued_at":"t","actor":{"kind":"kernel"},"origin":{"system":"gw"},"idempotency_key":"k","payload":{"rows":[{"kind":"a"},{"kind":"b"}]}}}}"#,
230 )
231 .expect("a repeated name in sibling objects was refused");
232 }
233
234 #[test]
235 fn bytes_that_are_not_utf8_are_refused_before_parsing() {
236 let error = decode::<ClientControl>(&[0x7b, 0xff, 0x7d]).expect_err("invalid UTF-8");
237 assert_eq!(error.code, KernelErrorCode::Schema);
238 assert!(error.message.contains("UTF-8"), "{error}");
239 }
240
241 #[test]
242 fn a_second_value_in_one_frame_is_trailing_garbage() {
243 let error = control(
244 r#"{"type":"request","request_id":"r-1","request":{"type":"health"}}{"type":"request","request_id":"r-2","request":{"type":"status"}}"#,
245 )
246 .expect_err("two values in one frame accepted");
247 assert_eq!(error.code, KernelErrorCode::Schema);
248 assert!(error.message.contains("trailing"), "{error}");
249 }
250
251 #[test]
252 fn an_empty_body_is_a_refusal_and_not_a_default() {
253 let error = decode::<ClientControl>(b" ").expect_err("empty body accepted");
254 assert_eq!(error.code, KernelErrorCode::Schema);
255 }
256}