Skip to main content

xcom_rs/
output.rs

1use crate::protocol::Envelope;
2use anyhow::Result;
3use serde::Serialize;
4use std::str::FromStr;
5
6/// Output format options
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum OutputFormat {
9    Json,
10    Yaml,
11    Text,
12    Ndjson,
13}
14
15impl FromStr for OutputFormat {
16    type Err = anyhow::Error;
17
18    fn from_str(s: &str) -> Result<Self> {
19        match s.to_lowercase().as_str() {
20            "json" | "json-schema" => Ok(OutputFormat::Json),
21            "yaml" => Ok(OutputFormat::Yaml),
22            "text" => Ok(OutputFormat::Text),
23            "ndjson" => Ok(OutputFormat::Ndjson),
24            _ => Err(anyhow::anyhow!("Invalid output format: {}", s)),
25        }
26    }
27}
28
29/// Format and print an envelope to stdout
30pub fn print_envelope<T: Serialize>(envelope: &Envelope<T>, format: OutputFormat) -> Result<()> {
31    match format {
32        OutputFormat::Json => {
33            let json = serde_json::to_string_pretty(envelope)?;
34            println!("{}", json);
35        }
36        OutputFormat::Yaml => {
37            let yaml = serde_yaml::to_string(envelope)?;
38            println!("{}", yaml);
39        }
40        OutputFormat::Text => {
41            print_envelope_text(envelope)?;
42        }
43        OutputFormat::Ndjson => {
44            // For NDJSON, print compact JSON without pretty formatting
45            let json = serde_json::to_string(envelope)?;
46            println!("{}", json);
47        }
48    }
49    Ok(())
50}
51
52/// Print array items as NDJSON (newline-delimited JSON)
53pub fn print_ndjson<T: Serialize>(items: &[T]) -> Result<()> {
54    for item in items {
55        let json = serde_json::to_string(item)?;
56        println!("{}", json);
57    }
58    Ok(())
59}
60
61/// Print envelope in human-readable text format
62fn print_envelope_text<T: Serialize>(envelope: &Envelope<T>) -> Result<()> {
63    if envelope.ok {
64        if let Some(ref data) = envelope.data {
65            let json = serde_json::to_value(data)?;
66            print_value_text(&json, 0);
67        } else {
68            println!("Success");
69        }
70    } else if let Some(ref error) = envelope.error {
71        eprintln!("Error: {:?} - {}", error.code, error.message);
72        if let Some(ref details) = error.details {
73            eprintln!("Details:");
74            let json = serde_json::to_value(details)?;
75            print_value_text(&json, 1);
76        }
77    }
78    Ok(())
79}
80
81/// Helper to print JSON value in text format
82fn print_value_text(value: &serde_json::Value, indent: usize) {
83    let prefix = "  ".repeat(indent);
84    match value {
85        serde_json::Value::Object(map) => {
86            for (key, val) in map {
87                match val {
88                    serde_json::Value::Object(_) | serde_json::Value::Array(_) => {
89                        println!("{}{}:", prefix, key);
90                        print_value_text(val, indent + 1);
91                    }
92                    _ => {
93                        println!("{}{}: {}", prefix, key, format_value(val));
94                    }
95                }
96            }
97        }
98        serde_json::Value::Array(arr) => {
99            for (i, val) in arr.iter().enumerate() {
100                match val {
101                    serde_json::Value::Object(_) | serde_json::Value::Array(_) => {
102                        println!("{}[{}]:", prefix, i);
103                        print_value_text(val, indent + 1);
104                    }
105                    _ => {
106                        println!("{}[{}] {}", prefix, i, format_value(val));
107                    }
108                }
109            }
110        }
111        _ => {
112            println!("{}{}", prefix, format_value(value));
113        }
114    }
115}
116
117fn format_value(value: &serde_json::Value) -> String {
118    match value {
119        serde_json::Value::String(s) => s.clone(),
120        serde_json::Value::Number(n) => n.to_string(),
121        serde_json::Value::Bool(b) => b.to_string(),
122        serde_json::Value::Null => "null".to_string(),
123        _ => value.to_string(),
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::protocol::{ErrorCode, ErrorDetails};
131    use std::str::FromStr;
132
133    #[test]
134    fn test_output_format_from_str() {
135        assert_eq!(OutputFormat::from_str("json").unwrap(), OutputFormat::Json);
136        assert_eq!(OutputFormat::from_str("JSON").unwrap(), OutputFormat::Json);
137        assert_eq!(OutputFormat::from_str("yaml").unwrap(), OutputFormat::Yaml);
138        assert_eq!(OutputFormat::from_str("text").unwrap(), OutputFormat::Text);
139        assert!(OutputFormat::from_str("invalid").is_err());
140    }
141
142    #[test]
143    fn test_print_json_envelope() {
144        let envelope = Envelope::success("test", "data");
145        let result = print_envelope(&envelope, OutputFormat::Json);
146        assert!(result.is_ok());
147    }
148
149    #[test]
150    fn test_print_error_envelope() {
151        let error = ErrorDetails::new(ErrorCode::InvalidArgument, "test error");
152        let envelope = Envelope::<()>::error("test", error);
153        let result = print_envelope(&envelope, OutputFormat::Json);
154        assert!(result.is_ok());
155    }
156}