Skip to main content

systemprompt_cli/shared/
command_result.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
6#[serde(rename_all = "snake_case")]
7pub enum ArtifactType {
8    Table,
9    List,
10    PresentationCard,
11    Text,
12    CopyPasteText,
13    Chart,
14    Form,
15    Dashboard,
16}
17
18#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
19#[serde(rename_all = "snake_case")]
20pub enum ChartType {
21    Bar,
22    Line,
23    Pie,
24    Area,
25}
26
27#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
28pub struct RenderingHints {
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub columns: Option<Vec<String>>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub chart_type: Option<ChartType>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub theme: Option<String>,
35    #[serde(flatten)]
36    pub extra: HashMap<String, serde_json::Value>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
40pub struct CommandResult<T> {
41    pub data: T,
42    pub artifact_type: ArtifactType,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub title: Option<String>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub hints: Option<RenderingHints>,
47    #[serde(skip)]
48    skip_render: bool,
49}
50
51impl<T> CommandResult<T> {
52    const fn new(data: T, artifact_type: ArtifactType) -> Self {
53        Self {
54            data,
55            artifact_type,
56            title: None,
57            hints: None,
58            skip_render: false,
59        }
60    }
61
62    pub const fn should_skip_render(&self) -> bool {
63        self.skip_render
64    }
65
66    pub const fn with_skip_render(mut self) -> Self {
67        self.skip_render = true;
68        self
69    }
70
71    pub const fn table(data: T) -> Self {
72        Self::new(data, ArtifactType::Table)
73    }
74
75    pub const fn list(data: T) -> Self {
76        Self::new(data, ArtifactType::List)
77    }
78
79    pub const fn card(data: T) -> Self {
80        Self::new(data, ArtifactType::PresentationCard)
81    }
82
83    pub const fn text(data: T) -> Self {
84        Self::new(data, ArtifactType::Text)
85    }
86
87    pub const fn copy_paste(data: T) -> Self {
88        Self::new(data, ArtifactType::CopyPasteText)
89    }
90
91    pub fn chart(data: T, chart_type: ChartType) -> Self {
92        let mut result = Self::new(data, ArtifactType::Chart);
93        result.hints = Some(RenderingHints {
94            chart_type: Some(chart_type),
95            ..Default::default()
96        });
97        result
98    }
99
100    pub const fn form(data: T) -> Self {
101        Self::new(data, ArtifactType::Form)
102    }
103
104    pub const fn dashboard(data: T) -> Self {
105        Self::new(data, ArtifactType::Dashboard)
106    }
107
108    pub fn with_title(mut self, title: impl Into<String>) -> Self {
109        self.title = Some(title.into());
110        self
111    }
112
113    pub fn with_hints(mut self, hints: RenderingHints) -> Self {
114        self.hints = Some(hints);
115        self
116    }
117
118    pub fn with_columns(mut self, columns: Vec<String>) -> Self {
119        let mut hints = self.hints.unwrap_or_else(RenderingHints::default);
120        hints.columns = Some(columns);
121        self.hints = Some(hints);
122        self
123    }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
127pub struct TextOutput {
128    pub message: String,
129}
130
131impl TextOutput {
132    pub fn new(message: impl Into<String>) -> Self {
133        Self {
134            message: message.into(),
135        }
136    }
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
140pub struct SuccessOutput {
141    pub message: String,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub details: Option<Vec<String>>,
144}
145
146impl SuccessOutput {
147    pub fn new(message: impl Into<String>) -> Self {
148        Self {
149            message: message.into(),
150            details: None,
151        }
152    }
153
154    pub fn with_details(mut self, details: Vec<String>) -> Self {
155        self.details = Some(details);
156        self
157    }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
161pub struct KeyValueOutput {
162    pub items: Vec<KeyValueItem>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
166pub struct KeyValueItem {
167    pub key: String,
168    pub value: String,
169}
170
171impl KeyValueOutput {
172    pub const fn new() -> Self {
173        Self { items: Vec::new() }
174    }
175
176    pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
177        self.items.push(KeyValueItem {
178            key: key.into(),
179            value: value.into(),
180        });
181        self
182    }
183}
184
185impl Default for KeyValueOutput {
186    fn default() -> Self {
187        Self::new()
188    }
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
192pub struct TableOutput<T> {
193    pub rows: Vec<T>,
194}
195
196impl<T> TableOutput<T> {
197    pub const fn new(rows: Vec<T>) -> Self {
198        Self { rows }
199    }
200}
201
202impl<T> Default for TableOutput<T> {
203    fn default() -> Self {
204        Self { rows: Vec::new() }
205    }
206}
207
208use crate::cli_settings::{get_global_config, OutputFormat};
209use systemprompt_logging::CliService;
210
211pub fn render_result<T: Serialize>(result: &CommandResult<T>) {
212    if result.should_skip_render() {
213        return;
214    }
215
216    let config = get_global_config();
217
218    match config.output_format() {
219        OutputFormat::Json => {
220            CliService::json(result);
221        },
222        OutputFormat::Yaml => {
223            CliService::yaml(result);
224        },
225        OutputFormat::Table => {
226            if let Some(title) = &result.title {
227                CliService::section(title);
228            }
229            CliService::json(&result.data);
230        },
231    }
232}