agent_first_http/shared/
envelope.rs1use serde::Serialize;
9use std::io::Write;
10
11use crate::shared::error::Error;
12
13pub fn emit<W: Write, T: Serialize>(writer: &mut W, code: &str, payload: &T) -> Result<(), Error> {
21 let mut value = serde_json::to_value(payload).map_err(|e| {
22 Error::new(
23 crate::shared::error::ErrorCode::InternalError,
24 format!("envelope: failed to serialize payload: {e}"),
25 )
26 })?;
27 if let serde_json::Value::Object(ref mut map) = value {
28 map.insert("code".into(), serde_json::Value::String(code.to_string()));
29 } else {
30 return Err(Error::new(
31 crate::shared::error::ErrorCode::InternalError,
32 "envelope: payload must serialize to a JSON object",
33 ));
34 }
35
36 serde_json::to_writer(&mut *writer, &value).map_err(|e| {
37 Error::new(
38 crate::shared::error::ErrorCode::IoError,
39 format!("envelope: write failed: {e}"),
40 )
41 })?;
42 writer.write_all(b"\n")?;
43 Ok(())
44}
45
46pub fn emit_error<W: Write>(writer: &mut W, err: &Error) -> Result<(), Error> {
48 emit(writer, "error", err)
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[derive(Serialize)]
56 struct HealthPayload {
57 status: &'static str,
58 uptime_s: u64,
59 }
60
61 #[test]
62 fn json_envelope_is_single_line_with_code_field() {
63 let mut buf = Vec::new();
64 let payload = HealthPayload {
65 status: "ok",
66 uptime_s: 42,
67 };
68 emit(&mut buf, "health", &payload).unwrap();
69 let s = String::from_utf8(buf).unwrap_or_default();
70 assert!(s.ends_with('\n'));
71 let trimmed = s.trim_end();
72 let parsed: serde_json::Value = serde_json::from_str(trimmed).unwrap();
73 assert_eq!(parsed["code"], "health");
74 assert_eq!(parsed["status"], "ok");
75 assert_eq!(parsed["uptime_s"], 42);
76 assert_eq!(trimmed.lines().count(), 1);
77 }
78
79 #[test]
80 fn error_envelope_uses_error_code_tag() {
81 let mut buf = Vec::new();
82 let err = Error::new(
83 crate::shared::error::ErrorCode::NavigationTimeout,
84 "no load",
85 );
86 emit_error(&mut buf, &err).unwrap();
87 let parsed: serde_json::Value =
88 serde_json::from_slice(&buf).unwrap_or(serde_json::Value::Null);
89 assert_eq!(parsed["code"], "error");
90 assert_eq!(parsed["error_code"], "navigation_timeout");
91 assert_eq!(parsed["error"], "no load");
92 assert_eq!(parsed["retryable"], true);
93 }
94}