Skip to main content

gemini_cli/
diag_output.rs

1use 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}