1use std::io::IsTerminal;
2
3use crate::error::AppError;
4
5#[derive(Clone, Copy, Debug)]
6pub enum Format {
7 Json,
8 Human,
9}
10
11impl Format {
12 pub fn detect(json_flag: bool) -> Self {
13 if json_flag || !std::io::stdout().is_terminal() {
14 Format::Json
15 } else {
16 Format::Human
17 }
18 }
19}
20
21#[derive(Clone, Copy)]
22pub struct Ctx {
23 pub format: Format,
24 pub quiet: bool,
25}
26
27impl Ctx {
28 pub fn new(json_flag: bool, quiet: bool) -> Self {
29 Self {
30 format: Format::detect(json_flag),
31 quiet,
32 }
33 }
34}
35
36fn safe_json_string<T: serde::Serialize>(value: &T) -> String {
39 match serde_json::to_string_pretty(value) {
40 Ok(s) => s,
41 Err(e) => {
42 let fallback = serde_json::json!({
43 "version": "1",
44 "status": "error",
45 "error": {
46 "code": "serialize",
47 "message": e.to_string(),
48 "suggestion": "Retry the command",
49 },
50 });
51 serde_json::to_string_pretty(&fallback).unwrap_or_else(|_| {
52 r#"{"version":"1","status":"error","error":{"code":"serialize","message":"serialization failed","suggestion":"Retry the command"}}"#.to_string()
53 })
54 }
55 }
56}
57
58pub fn print_success<T, F>(ctx: Ctx, data: &T, human: F)
59where
60 T: serde::Serialize,
61 F: FnOnce(&T),
62{
63 match ctx.format {
64 Format::Json => {
65 let envelope = serde_json::json!({
66 "version": "1",
67 "status": "success",
68 "data": data,
69 });
70 println!("{}", safe_json_string(&envelope));
71 }
72 Format::Human if !ctx.quiet => human(data),
73 Format::Human => {}
74 }
75}
76
77pub fn print_error(format: Format, err: &AppError) {
78 match format {
79 Format::Json => {
80 let envelope = serde_json::json!({
81 "version": "1",
82 "status": "error",
83 "error": {
84 "code": err.error_code(),
85 "message": err.to_string(),
86 "suggestion": err.suggestion(),
87 },
88 });
89 eprintln!("{}", safe_json_string(&envelope));
90 }
91 Format::Human => {
92 eprintln!("error: {}", err);
93 let hint = err.suggestion();
94 if !hint.is_empty() {
95 eprintln!(" hint: {}", hint);
96 }
97 }
98 }
99}
100
101pub fn print_raw<T: serde::Serialize>(value: &T) {
102 println!("{}", safe_json_string(value));
103}
104
105pub fn print_help_envelope(text: &str) {
108 let envelope = serde_json::json!({
109 "version": "1",
110 "status": "success",
111 "data": { "usage": text },
112 });
113 println!("{}", safe_json_string(&envelope));
114}