gemini_cli/
diag_output.rs1use anyhow::Result;
2use serde::Serialize;
3use serde_json::Value;
4
5#[derive(Debug, Clone, Serialize)]
6pub struct ErrorEnvelope {
7 pub code: String,
8 pub message: String,
9 #[serde(skip_serializing_if = "Option::is_none")]
10 pub details: Option<Value>,
11}
12
13#[derive(Debug, Clone, Serialize)]
14pub struct JsonEnvelopeResult<T: Serialize> {
15 pub schema_version: String,
16 pub command: String,
17 pub ok: bool,
18 pub result: T,
19}
20
21#[derive(Debug, Clone, Serialize)]
22pub struct JsonEnvelopeResults<T: Serialize> {
23 pub schema_version: String,
24 pub command: String,
25 pub ok: bool,
26 pub results: Vec<T>,
27}
28
29#[derive(Debug, Clone, Serialize)]
30pub struct JsonEnvelopeError {
31 pub schema_version: String,
32 pub command: String,
33 pub ok: bool,
34 pub error: ErrorEnvelope,
35}
36
37pub fn emit_json<T: Serialize>(payload: &T) -> Result<()> {
38 println!("{}", serde_json::to_string(payload)?);
39 Ok(())
40}
41
42pub fn emit_success_result<T: Serialize>(
43 schema_version: &str,
44 command: &str,
45 result: T,
46) -> Result<()> {
47 emit_json(&JsonEnvelopeResult {
48 schema_version: schema_version.to_string(),
49 command: command.to_string(),
50 ok: true,
51 result,
52 })
53}
54
55pub fn emit_success_results<T: Serialize>(
56 schema_version: &str,
57 command: &str,
58 results: Vec<T>,
59) -> Result<()> {
60 emit_json(&JsonEnvelopeResults {
61 schema_version: schema_version.to_string(),
62 command: command.to_string(),
63 ok: true,
64 results,
65 })
66}
67
68pub fn emit_error(
69 schema_version: &str,
70 command: &str,
71 code: &str,
72 message: impl Into<String>,
73 details: Option<Value>,
74) -> Result<()> {
75 emit_json(&JsonEnvelopeError {
76 schema_version: schema_version.to_string(),
77 command: command.to_string(),
78 ok: false,
79 error: ErrorEnvelope {
80 code: code.to_string(),
81 message: message.into(),
82 details,
83 },
84 })
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use serde_json::{json, to_value};
91
92 #[test]
93 fn error_envelope_serialization_omits_details_when_none() {
94 let envelope = JsonEnvelopeError {
95 schema_version: "gemini-cli.test.v1".to_string(),
96 command: "diag test".to_string(),
97 ok: false,
98 error: ErrorEnvelope {
99 code: "bad-input".to_string(),
100 message: "invalid".to_string(),
101 details: None,
102 },
103 };
104 let value = to_value(envelope).expect("serialize");
105 assert_eq!(value["ok"], false);
106 assert!(value["error"].get("details").is_none());
107 }
108
109 #[test]
110 fn emit_helpers_return_ok() {
111 assert!(
112 emit_success_result("gemini-cli.test.v1", "diag test", json!({"status":"ok"})).is_ok()
113 );
114 assert!(
115 emit_success_results(
116 "gemini-cli.test.v1",
117 "diag test",
118 vec![json!({"item":1}), json!({"item":2})]
119 )
120 .is_ok()
121 );
122 assert!(
123 emit_error(
124 "gemini-cli.test.v1",
125 "diag test",
126 "failure",
127 "boom",
128 Some(json!({"hint":"retry"})),
129 )
130 .is_ok()
131 );
132 }
133}