1use std::io::{self, Write};
2
3use serde::Serialize;
4
5use crate::cli::{OutputFormat, ProfileCommand};
6use crate::error::{CliError, Result};
7use crate::models::{Column, DataType, Row, Value};
8use crate::output::formatter::{create_formatter, Formatter};
9
10use super::config::{ConnectionType, LocalConfig, Profile, ProfileManager};
11
12#[derive(Debug, Serialize)]
13pub struct ProfileListOutput {
14 pub profiles: Vec<ProfileListItem>,
15}
16
17#[derive(Debug, Serialize, Clone)]
18pub struct ProfileListItem {
19 pub name: String,
20 pub data_dir: String,
21 pub is_default: bool,
22}
23
24#[derive(Debug, Serialize)]
25pub struct ProfileShowOutput {
26 pub name: String,
27 pub data_dir: String,
28 pub is_default: bool,
29}
30
31pub fn execute_profile_command(cmd: ProfileCommand, output: OutputFormat) -> Result<()> {
32 match cmd {
33 ProfileCommand::Create { name, data_dir } => {
34 let mut manager = ProfileManager::load()?;
35 manager.create(
36 &name,
37 Profile {
38 connection_type: ConnectionType::Local,
39 local: Some(LocalConfig {
40 path: data_dir.clone(),
41 }),
42 server: None,
43 data_dir: Some(data_dir),
44 },
45 )?;
46 manager.save()
47 }
48 ProfileCommand::List => {
49 let manager = ProfileManager::load()?;
50 let items = build_list_items(&manager);
51 output_profile_list(&items, output)
52 }
53 ProfileCommand::Show { name } => {
54 let manager = ProfileManager::load()?;
55 let show = build_show_output(&manager, &name)?;
56 output_profile_show(&show, output)
57 }
58 ProfileCommand::Delete { name } => {
59 let mut manager = ProfileManager::load()?;
60 manager.delete(&name)?;
61 manager.save()
62 }
63 ProfileCommand::SetDefault { name } => {
64 let mut manager = ProfileManager::load()?;
65 manager.set_default(&name)?;
66 manager.save()
67 }
68 }
69}
70
71fn build_list_items(manager: &ProfileManager) -> Vec<ProfileListItem> {
72 let default_name = manager.default_profile();
73 manager
74 .list()
75 .into_iter()
76 .filter_map(|name| {
77 manager.get(name).map(|profile| ProfileListItem {
78 name: name.to_string(),
79 data_dir: profile.local_path().unwrap_or_else(|| "-".to_string()),
80 is_default: default_name == Some(name),
81 })
82 })
83 .collect()
84}
85
86fn build_show_output(manager: &ProfileManager, name: &str) -> Result<ProfileShowOutput> {
87 let profile = manager
88 .get(name)
89 .ok_or_else(|| CliError::ProfileNotFound(name.to_string()))?;
90 let data_dir = profile.local_path().unwrap_or_else(|| "-".to_string());
91 Ok(ProfileShowOutput {
92 name: name.to_string(),
93 data_dir,
94 is_default: manager.default_profile() == Some(name),
95 })
96}
97
98fn output_profile_list(items: &[ProfileListItem], output: OutputFormat) -> Result<()> {
99 match output {
100 OutputFormat::Table => write_list_table(items),
101 OutputFormat::Json => write_list_json(items),
102 _ => Err(CliError::InvalidArgument(format!(
103 "Unsupported output format for profile list: {:?}",
104 output
105 ))),
106 }
107}
108
109fn output_profile_show(show: &ProfileShowOutput, output: OutputFormat) -> Result<()> {
110 match output {
111 OutputFormat::Table => write_show_table(show),
112 OutputFormat::Json => write_show_json(show),
113 _ => Err(CliError::InvalidArgument(format!(
114 "Unsupported output format for profile show: {:?}",
115 output
116 ))),
117 }
118}
119
120fn write_list_table(items: &[ProfileListItem]) -> Result<()> {
121 let mut writer = io::stdout().lock();
122 write_list_table_to(&mut writer, items)
123}
124
125fn write_list_table_to(writer: &mut dyn Write, items: &[ProfileListItem]) -> Result<()> {
126 let columns = vec![
127 Column::new("Name", DataType::Text),
128 Column::new("Data Dir", DataType::Text),
129 Column::new("Default", DataType::Text),
130 ];
131 let rows: Vec<Row> = items
132 .iter()
133 .map(|item| {
134 Row::new(vec![
135 Value::Text(item.name.clone()),
136 Value::Text(item.data_dir.clone()),
137 Value::Text(if item.is_default { "*" } else { "" }.to_string()),
138 ])
139 })
140 .collect();
141 write_rows_with_formatter_to(writer, OutputFormat::Table, &columns, &rows)
142}
143
144fn write_list_json(items: &[ProfileListItem]) -> Result<()> {
145 let mut writer = io::stdout().lock();
146 write_list_json_to(&mut writer, items)
147}
148
149fn write_list_json_to(writer: &mut dyn Write, items: &[ProfileListItem]) -> Result<()> {
150 let output = ProfileListOutput {
151 profiles: items.to_vec(),
152 };
153 let value = serde_json::to_value(output)?;
154 write_json_value_to(writer, &value)
155}
156
157fn write_show_table(show: &ProfileShowOutput) -> Result<()> {
158 let mut writer = io::stdout().lock();
159 write_show_table_to(&mut writer, show)
160}
161
162fn write_show_table_to(writer: &mut dyn Write, show: &ProfileShowOutput) -> Result<()> {
163 let columns = vec![
164 Column::new("Name", DataType::Text),
165 Column::new("Data Dir", DataType::Text),
166 Column::new("Default", DataType::Text),
167 ];
168 let rows = vec![Row::new(vec![
169 Value::Text(show.name.clone()),
170 Value::Text(show.data_dir.clone()),
171 Value::Text(if show.is_default { "Yes" } else { "No" }.to_string()),
172 ])];
173 let mut formatter = KeyValueFormatter::new();
174 formatter.write_header(writer, &columns)?;
175 for row in &rows {
176 formatter.write_row(writer, row)?;
177 }
178 formatter.write_footer(writer)
179}
180
181fn write_show_json(show: &ProfileShowOutput) -> Result<()> {
182 let mut writer = io::stdout().lock();
183 write_show_json_to(&mut writer, show)
184}
185
186fn write_show_json_to(writer: &mut dyn Write, show: &ProfileShowOutput) -> Result<()> {
187 let columns = vec![
188 Column::new("name", DataType::Text),
189 Column::new("data_dir", DataType::Text),
190 Column::new("is_default", DataType::Bool),
191 ];
192 let rows = vec![Row::new(vec![
193 Value::Text(show.name.clone()),
194 Value::Text(show.data_dir.clone()),
195 Value::Bool(show.is_default),
196 ])];
197 let array = rows_to_json_array(&columns, &rows)?;
198 let obj = array
199 .as_array()
200 .and_then(|items| items.first())
201 .cloned()
202 .unwrap_or(serde_json::Value::Null);
203 write_json_value_to(writer, &obj)
204}
205
206fn write_rows_with_formatter_to(
207 writer: &mut dyn Write,
208 output: OutputFormat,
209 columns: &[Column],
210 rows: &[Row],
211) -> Result<()> {
212 let mut formatter = create_formatter(output);
213 formatter.write_header(writer, columns)?;
214 for row in rows {
215 formatter.write_row(writer, row)?;
216 }
217 formatter.write_footer(writer)
218}
219
220fn rows_to_json_array(columns: &[Column], rows: &[Row]) -> Result<serde_json::Value> {
221 let mut buffer = Vec::new();
222 let mut formatter = create_formatter(OutputFormat::Json);
223 formatter.write_header(&mut buffer, columns)?;
224 for row in rows {
225 formatter.write_row(&mut buffer, row)?;
226 }
227 formatter.write_footer(&mut buffer)?;
228 let value: serde_json::Value = serde_json::from_slice(&buffer)?;
229 Ok(value)
230}
231
232fn write_json_value_to(writer: &mut dyn Write, value: &serde_json::Value) -> Result<()> {
233 serde_json::to_writer_pretty(&mut *writer, value)?;
234 writeln!(writer)?;
235 Ok(())
236}
237
238struct KeyValueFormatter {
239 columns: Vec<String>,
240 rows: Vec<Row>,
241}
242
243impl KeyValueFormatter {
244 fn new() -> Self {
245 Self {
246 columns: Vec::new(),
247 rows: Vec::new(),
248 }
249 }
250}
251
252impl Formatter for KeyValueFormatter {
253 fn write_header(&mut self, _writer: &mut dyn Write, columns: &[Column]) -> Result<()> {
254 self.columns = columns.iter().map(|column| column.name.clone()).collect();
255 Ok(())
256 }
257
258 fn write_row(&mut self, _writer: &mut dyn Write, row: &Row) -> Result<()> {
259 self.rows.push(row.clone());
260 Ok(())
261 }
262
263 fn write_footer(&mut self, writer: &mut dyn Write) -> Result<()> {
264 const LABEL_WIDTH: usize = 9;
265 if let Some(row) = self.rows.first() {
266 for (column, value) in self.columns.iter().zip(row.columns.iter()) {
267 let label = format!("{}:", column);
268 let value_str = format_value(value);
269 writeln!(
270 writer,
271 "{:<width$} {}",
272 label,
273 value_str,
274 width = LABEL_WIDTH
275 )?;
276 }
277 }
278 Ok(())
279 }
280
281 fn supports_streaming(&self) -> bool {
282 false
283 }
284}
285
286fn format_value(value: &Value) -> String {
287 match value {
288 Value::Null => "NULL".to_string(),
289 Value::Bool(b) => {
290 if *b {
291 "Yes".to_string()
292 } else {
293 "No".to_string()
294 }
295 }
296 Value::Int(i) => i.to_string(),
297 Value::Float(f) => format!("{:.6}", f),
298 Value::Text(s) => s.clone(),
299 Value::Bytes(bytes) => format!("{:?}", bytes),
300 Value::Vector(values) => format!("{:?}", values),
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 fn sample_items() -> Vec<ProfileListItem> {
309 vec![
310 ProfileListItem {
311 name: "dev".to_string(),
312 data_dir: "/path/dev".to_string(),
313 is_default: true,
314 },
315 ProfileListItem {
316 name: "prod".to_string(),
317 data_dir: "/path/prod".to_string(),
318 is_default: false,
319 },
320 ]
321 }
322
323 fn sample_show() -> ProfileShowOutput {
324 ProfileShowOutput {
325 name: "dev".to_string(),
326 data_dir: "/path/dev".to_string(),
327 is_default: true,
328 }
329 }
330
331 #[test]
332 fn test_list_table_output() {
333 let items = sample_items();
334 let mut buffer = Vec::new();
335 write_list_table_to(&mut buffer, &items).unwrap();
336 let output = String::from_utf8(buffer).unwrap();
337 assert!(output.contains("Name"));
338 assert!(output.contains("Data Dir"));
339 assert!(output.contains("Default"));
340 assert!(output.contains("dev"));
341 assert!(output.contains("/path/dev"));
342 assert!(output.contains("*"));
343 }
344
345 #[test]
346 fn test_list_json_output() {
347 let items = sample_items();
348 let mut buffer = Vec::new();
349 write_list_json_to(&mut buffer, &items).unwrap();
350 let value: serde_json::Value = serde_json::from_slice(&buffer).unwrap();
351 let profiles = value["profiles"].as_array().unwrap();
352 assert_eq!(profiles.len(), 2);
353 assert!(profiles.iter().any(|profile| {
354 profile["name"] == "dev"
355 && profile["data_dir"] == "/path/dev"
356 && profile["is_default"] == true
357 }));
358 assert!(profiles.iter().any(|profile| {
359 profile["name"] == "prod"
360 && profile["data_dir"] == "/path/prod"
361 && profile["is_default"] == false
362 }));
363 }
364
365 #[test]
366 fn test_show_table_output() {
367 let show = sample_show();
368 let mut buffer = Vec::new();
369 write_show_table_to(&mut buffer, &show).unwrap();
370 let output = String::from_utf8(buffer).unwrap();
371 assert!(output.contains("Name:"));
372 assert!(output.contains("Data Dir:"));
373 assert!(output.contains("Default:"));
374 assert!(output.contains("dev"));
375 assert!(output.contains("/path/dev"));
376 assert!(output.contains("Yes"));
377 }
378
379 #[test]
380 fn test_show_json_output() {
381 let show = sample_show();
382 let mut buffer = Vec::new();
383 write_show_json_to(&mut buffer, &show).unwrap();
384 let value: serde_json::Value = serde_json::from_slice(&buffer).unwrap();
385 assert_eq!(value["name"], "dev");
386 assert_eq!(value["data_dir"], "/path/dev");
387 assert_eq!(value["is_default"], true);
388 }
389}