1#![allow(clippy::missing_errors_doc)]
2
3use super::OutputFormat;
4use crate::api::RedashClient;
5use crate::models::DataSource;
6use anyhow::Result;
7
8fn build_status_string(ds: &DataSource) -> String {
9 let mut status_parts = Vec::new();
10 if ds.paused != 0 {
11 status_parts.push("paused");
12 }
13 if ds.view_only {
14 status_parts.push("view-only");
15 }
16 if let Some(desc) = &ds.description
17 && desc.to_lowercase().contains("deprecated")
18 {
19 status_parts.push("deprecated");
20 }
21
22 if status_parts.is_empty() {
23 String::new()
24 } else {
25 format!("[{}]", status_parts.join(", "))
26 }
27}
28
29pub async fn list_data_sources(client: &RedashClient, format: OutputFormat) -> Result<()> {
30 let data_sources = client.list_data_sources().await?;
31
32 match format {
33 OutputFormat::Json => {
34 let json = serde_json::to_string_pretty(&data_sources)?;
35 println!("{json}");
36 }
37 OutputFormat::Table => {
38 println!("Fetching data sources from Redash...\n");
39 println!("=== DATA SOURCES ({}) ===\n", data_sources.len());
40 println!("{:<6} {:<40} {:<15} Status", "ID", "Name", "Type");
41 println!("{}", "-".repeat(80));
42
43 for ds in &data_sources {
44 let status = build_status_string(ds);
45 println!("{:<6} {:<40} {:<15} {}", ds.id, ds.name, ds.ds_type, status);
46 }
47
48 println!("\nUse 'stmo-cli data-sources <id>' to view details.");
49 println!("Use 'stmo-cli data-sources <id> --schema' to view table schema.");
50 }
51 }
52
53 Ok(())
54}
55
56pub async fn show_data_source(
57 client: &RedashClient,
58 data_source_id: u64,
59 show_schema: bool,
60 refresh_schema: bool,
61 format: OutputFormat,
62) -> Result<()> {
63 let ds = client.get_data_source(data_source_id).await?;
64
65 let schema = if show_schema {
66 match client
67 .get_data_source_schema(data_source_id, refresh_schema)
68 .await
69 {
70 Ok(s) => Some(s),
71 Err(e) => {
72 eprintln!("Error fetching schema: {e:#}");
73 None
74 }
75 }
76 } else {
77 None
78 };
79
80 match format {
81 OutputFormat::Json => {
82 let output = serde_json::json!({
83 "data_source": ds,
84 "schema": schema,
85 });
86 let json = serde_json::to_string_pretty(&output)?;
87 println!("{json}");
88 }
89 OutputFormat::Table => {
90 println!("=== DATA SOURCE: {} ===\n", ds.name);
91 println!("ID: {}", ds.id);
92 println!("Type: {}", ds.ds_type);
93
94 if let Some(syntax) = &ds.syntax {
95 println!("Syntax: {syntax}");
96 }
97
98 if let Some(description) = &ds.description {
99 println!("Description: {description}");
100 }
101
102 if ds.paused != 0 {
103 println!("Status: PAUSED");
104 if let Some(reason) = &ds.pause_reason {
105 println!("Reason: {reason}");
106 }
107 } else {
108 println!("Status: Active");
109 }
110
111 if ds.view_only {
112 println!("Access: View-only");
113 }
114
115 if let Some(queue) = &ds.queue_name {
116 println!("Queue: {queue}");
117 }
118
119 if show_schema {
120 if let Some(schema) = schema {
121 println!("\n=== SCHEMA ({} tables) ===\n", schema.schema.len());
122
123 for table in &schema.schema {
124 println!("\nTable: {} ({} columns)", table.name, table.columns.len());
125 println!(" {:<40} Type", "Column");
126 println!(" {}", "-".repeat(60));
127
128 for column in &table.columns {
129 println!(" {:<40} {}", column.name, column.column_type);
130 }
131 }
132 } else {
133 eprintln!("\nNote: Schema could not be fetched. See error above for details.");
134 }
135 } else {
136 println!("\nUse --schema flag to view table schema.");
137 }
138 }
139 }
140
141 Ok(())
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn create_test_datasource(
149 id: u64,
150 name: &str,
151 ds_type: &str,
152 paused: u8,
153 view_only: bool,
154 description: Option<String>,
155 ) -> DataSource {
156 DataSource {
157 id,
158 name: name.to_string(),
159 ds_type: ds_type.to_string(),
160 syntax: Some("sql".to_string()),
161 description,
162 paused,
163 pause_reason: None,
164 view_only,
165 queue_name: None,
166 scheduled_queue_name: None,
167 groups: None,
168 options: None,
169 }
170 }
171
172 #[test]
173 fn test_build_status_string_no_status() {
174 let ds = create_test_datasource(1, "Test DB", "bigquery", 0, false, None);
175 assert_eq!(build_status_string(&ds), "");
176 }
177
178 #[test]
179 fn test_build_status_string_paused() {
180 let ds = create_test_datasource(1, "Test DB", "bigquery", 1, false, None);
181 assert_eq!(build_status_string(&ds), "[paused]");
182 }
183
184 #[test]
185 fn test_build_status_string_view_only() {
186 let ds = create_test_datasource(1, "Test DB", "bigquery", 0, true, None);
187 assert_eq!(build_status_string(&ds), "[view-only]");
188 }
189
190 #[test]
191 fn test_build_status_string_deprecated() {
192 let ds = create_test_datasource(
193 1,
194 "Test DB",
195 "bigquery",
196 0,
197 false,
198 Some("This is deprecated".to_string()),
199 );
200 assert_eq!(build_status_string(&ds), "[deprecated]");
201 }
202
203 #[test]
204 fn test_build_status_string_multiple() {
205 let ds = create_test_datasource(
206 1,
207 "Test DB",
208 "bigquery",
209 1,
210 true,
211 Some("This is deprecated".to_string()),
212 );
213 assert_eq!(build_status_string(&ds), "[paused, view-only, deprecated]");
214 }
215
216 #[test]
217 fn test_build_status_string_deprecated_case_insensitive() {
218 let ds1 = create_test_datasource(
219 1,
220 "Test DB",
221 "bigquery",
222 0,
223 false,
224 Some("DEPRECATED: do not use".to_string()),
225 );
226 assert_eq!(build_status_string(&ds1), "[deprecated]");
227
228 let ds2 = create_test_datasource(
229 2,
230 "Test DB",
231 "bigquery",
232 0,
233 false,
234 Some("Deprecated API".to_string()),
235 );
236 assert_eq!(build_status_string(&ds2), "[deprecated]");
237 }
238
239 #[test]
240 fn test_build_status_string_no_description() {
241 let ds = create_test_datasource(1, "Test DB", "bigquery", 0, true, None);
242 assert_eq!(build_status_string(&ds), "[view-only]");
243 }
244
245 #[test]
246 fn test_build_status_string_description_no_deprecated() {
247 let ds = create_test_datasource(
248 1,
249 "Test DB",
250 "bigquery",
251 0,
252 false,
253 Some("Some other description".to_string()),
254 );
255 assert_eq!(build_status_string(&ds), "");
256 }
257
258 #[test]
259 fn test_output_format_from_str() {
260 assert!(matches!(
261 "json".parse::<OutputFormat>().unwrap(),
262 OutputFormat::Json
263 ));
264 assert!(matches!(
265 "JSON".parse::<OutputFormat>().unwrap(),
266 OutputFormat::Json
267 ));
268 assert!(matches!(
269 "table".parse::<OutputFormat>().unwrap(),
270 OutputFormat::Table
271 ));
272 assert!(matches!(
273 "TABLE".parse::<OutputFormat>().unwrap(),
274 OutputFormat::Table
275 ));
276 assert!("invalid".parse::<OutputFormat>().is_err());
277 assert!("csv".parse::<OutputFormat>().is_err());
278 }
279}