Skip to main content

pitchfork_cli/cli/
json_output.rs

1use crate::log_store::LogEntry;
2use serde::Serialize;
3
4#[derive(Serialize)]
5pub struct JsonListEntry {
6    pub id: String,
7    pub namespace: String,
8    pub name: String,
9    pub pid: Option<u32>,
10    pub status: String,
11    pub disabled: bool,
12    pub available: bool,
13    pub proxy_url: Option<String>,
14    pub error: Option<String>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub active_port: Option<u16>,
17    pub port: Vec<u16>,
18}
19
20#[derive(Serialize)]
21pub struct JsonStatusEntry {
22    pub id: String,
23    pub namespace: String,
24    pub name: String,
25    pub pid: Option<u32>,
26    pub status: String,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub active_port: Option<u16>,
29    pub port: Vec<u16>,
30    pub proxy_url: Option<String>,
31}
32
33#[derive(Serialize)]
34pub struct JsonLogEntry {
35    /// SQLite row id, used by the webui for backward pagination (scroll-up
36    /// history loading). Skipped in serialization when 0 to avoid changing
37    /// the CLI --json output shape for non-web callers.
38    #[serde(skip_serializing_if = "is_zero_id")]
39    pub id: i64,
40    pub timestamp: String,
41    pub daemon_id: String,
42    pub message: String,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub level: Option<String>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub msg: Option<String>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub logger: Option<String>,
49    /// Parsed structured fields as a JSON object, or null if the line was
50    /// not structured (plain text).
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub fields: Option<serde_json::Value>,
53}
54
55fn is_zero_id(id: &i64) -> bool {
56    *id == 0
57}
58
59impl From<LogEntry> for JsonLogEntry {
60    fn from(e: LogEntry) -> Self {
61        let fields = e
62            .fields_json
63            .as_deref()
64            .and_then(|s| serde_json::from_str(s).ok());
65        JsonLogEntry {
66            id: e.id,
67            timestamp: e.timestamp.format("%Y-%m-%d %H:%M:%S").to_string(),
68            daemon_id: e.daemon_id,
69            message: console::strip_ansi_codes(&e.message).to_string(),
70            level: e.level,
71            msg: e.msg,
72            logger: e.logger,
73            fields,
74        }
75    }
76}
77
78#[derive(Serialize)]
79pub struct JsonDaemonConfigEntry {
80    pub id: String,
81    pub run: String,
82}
83
84#[derive(Serialize)]
85pub struct JsonProxyStatus {
86    pub enabled: bool,
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub scheme: Option<String>,
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub tld: Option<String>,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub port: Option<u16>,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub lan: Option<JsonLanInfo>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub tls_cert: Option<String>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub trusted: Option<bool>,
99    pub slugs: Vec<JsonSlugEntry>,
100}
101
102#[derive(Serialize)]
103pub struct JsonLanInfo {
104    pub enabled: bool,
105    pub ip: String,
106}
107
108#[derive(Serialize)]
109pub struct JsonSlugEntry {
110    pub slug: String,
111    pub url: String,
112    pub dir: String,
113    pub daemon: String,
114    pub status: String,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub port: Option<u16>,
117}
118
119#[derive(Serialize)]
120pub struct JsonSupervisorStatus {
121    pub status: &'static str,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub web_ui: Option<String>,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub error: Option<String>,
126}
127
128#[derive(Serialize)]
129pub struct JsonSettingEntry {
130    pub key: String,
131    pub value: String,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub default: Option<&'static str>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub r#type: Option<&'static str>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub env_var: Option<&'static str>,
138}
139
140pub fn print_json<T: Serialize>(value: &T) -> crate::Result<()> {
141    let json = serde_json::to_string_pretty(value)
142        .map_err(|e| miette::miette!("failed to serialize JSON: {e}"))?;
143    println!("{json}");
144    Ok(())
145}