coman/cli/
manager.rs

1//! CLI commands for managing collections and endpoints
2//!
3//! This module provides the command-line interface for managing API collections,
4//! delegating the actual work to the core CollectionManager.
5
6use std::fmt;
7
8use crate::core::collection_manager::CollectionManager;
9use crate::helper;
10use crate::models::collection::Method;
11use clap::Subcommand;
12use colored::Colorize;
13
14use super::request::{RequestCommands, RequestData};
15
16#[derive(Clone, Subcommand)]
17pub enum ManagerCommands {
18    #[clap(about = "List collections and endpoints")]
19    List {
20        #[clap(short = 'c', long = "col", default_value = "", required = false)]
21        col: String,
22
23        #[clap(short = 'e', long = "endpoint", default_value = "", required = false)]
24        endpoint: String,
25
26        #[clap(short = 'q', long = "quiet", default_value = "false")]
27        quiet: bool,
28
29        #[clap(short, long, default_value = "false")]
30        verbose: bool,
31    },
32    #[clap(about = "Update a collection or endpoint headers and body")]
33    Update {
34        collection: String,
35
36        #[clap(short = 'e', long, default_value = "", required = false)]
37        endpoint: String,
38
39        #[clap(short = 'u', long, default_value = "", required = false)]
40        url: String,
41
42        #[clap(
43            short = 'H',
44            long = "header",
45            value_parser = RequestData::parse_header,
46            value_name = "KEY:VALUE",
47            num_args = 1..,
48            required = false
49        )]
50        headers: Vec<(String, String)>,
51
52        #[clap(short = 'b', long, default_value = "", required = false)]
53        body: String,
54    },
55    #[clap(about = "Delete a collection or endpoint")]
56    Delete {
57        collection: String,
58
59        #[clap(short = 'e', long, default_value = "", required = false)]
60        endpoint: String,
61
62        #[clap(short, long, default_value = "false")]
63        yes: bool,
64    },
65    #[clap(about = "Copy a collection or endpoint")]
66    Copy {
67        collection: String,
68
69        #[clap(short = 'e', long, default_value = "", required = false)]
70        endpoint: String,
71
72        #[clap(short = 'c', long, default_value = "false", required = false)]
73        to_col: bool,
74
75        new_name: String,
76    },
77    #[clap(about = "Add a new collection")]
78    Col {
79        name: String,
80        url: String,
81
82        #[clap(
83            short = 'H',
84            long = "header",
85            value_parser = RequestData::parse_header,
86            value_name = "KEY:VALUE",
87            num_args = 1..,
88            required = false
89        )]
90        headers: Vec<(String, String)>,
91    },
92    #[clap(about = "Add a new endpoint to a collection")]
93    Endpoint {
94        collection: String,
95        name: String,
96        path: String,
97
98        #[clap(short = 'm', long, default_value = "GET")]
99        method: String,
100
101        #[clap(
102            short = 'H',
103            long = "header",
104            value_parser = RequestData::parse_header,
105            value_name = "KEY:VALUE",
106            num_args = 1..,
107            required = false
108        )]
109        headers: Vec<(String, String)>,
110
111        #[clap(short = 'b', long, default_value = "", required = false)]
112        body: String,
113    },
114}
115
116impl fmt::Display for ManagerCommands {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            ManagerCommands::List {
120                col,
121                endpoint,
122                quiet,
123                verbose,
124            } => write!(
125                f,
126                "List Command: col: '{}', endpoint: '{}', quiet: {}, verbose: {}",
127                col, endpoint, quiet, verbose
128            ),
129            ManagerCommands::Update {
130                collection,
131                endpoint,
132                url: _,
133                headers,
134                body,
135            } => {
136                write!(
137                    f,
138                    "Update Command: collection: '{}', endpoint: '{}', headers: {:?}, body: '{}'",
139                    collection, endpoint, headers, body
140                )
141            }
142            ManagerCommands::Delete {
143                collection,
144                endpoint,
145                yes,
146            } => {
147                write!(
148                    f,
149                    "Delete Command: collection: '{}', endpoint: '{}', yes: {}",
150                    collection, endpoint, yes
151                )
152            }
153            ManagerCommands::Copy {
154                collection,
155                endpoint,
156                to_col,
157                new_name,
158            } => {
159                write!(
160                    f,
161                    "Copy Command: collection: '{}', endpoint: '{}', To Col {}, new_name: '{}'",
162                    collection, endpoint, to_col, new_name
163                )
164            }
165            ManagerCommands::Col { name, url, headers } => {
166                write!(
167                    f,
168                    "Col Command: name: '{}', url: '{}', headers: {:?}",
169                    name, url, headers
170                )
171            }
172            ManagerCommands::Endpoint {
173                collection,
174                name,
175                path,
176                method,
177                headers,
178                body,
179            } => {
180                write!(f, "Endpoint Command: collection: '{}', name: '{}', path: '{}', method: '{}', headers: {:?}, body: '{}'",
181                    collection, name, path, method, headers, body)
182            }
183        }
184    }
185}
186
187impl ManagerCommands {
188    /// Get the default collection manager
189    fn get_manager() -> CollectionManager {
190        CollectionManager::default()
191    }
192
193    /// Get a RequestCommands for running an endpoint from a collection
194    pub fn get_endpoint_command(collection: &str, endpoint: &str) -> Option<RequestCommands> {
195        let manager = Self::get_manager();
196        let col = manager.get_collection(collection).ok()?;
197        let req = manager.get_endpoint(collection, endpoint).ok()?;
198
199        let data = RequestData {
200            url: format!("{}{}", col.url, req.endpoint),
201            headers: manager
202                .get_endpoint_headers(collection, endpoint)
203                .unwrap_or_default(),
204            body: req.body.clone().unwrap_or_default(),
205        };
206
207        Some(match req.method {
208            Method::Get => RequestCommands::Get { data },
209            Method::Post => RequestCommands::Post { data },
210            Method::Delete => RequestCommands::Delete { data },
211            Method::Patch => RequestCommands::Patch { data },
212            Method::Put => RequestCommands::Put { data },
213        })
214    }
215
216    pub fn run(&self) -> Result<String, Box<dyn std::error::Error>> {
217        let manager = Self::get_manager();
218
219        match self {
220            // List collections and endpoints
221            Self::List {
222                col,
223                endpoint,
224                quiet,
225                verbose,
226            } => {
227                let collections = manager.load_collections()?;
228                if collections.is_empty() {
229                    return Err("No collections found.".into());
230                } else {
231                    for collection in collections {
232                        if !col.is_empty() && &collection.name != col {
233                            continue;
234                        }
235                        println!(
236                            "[{}] - {}",
237                            collection.name.bright_magenta(),
238                            collection.url
239                        );
240                        if *quiet {
241                            continue;
242                        }
243                        if !collection.headers.is_empty() {
244                            println!("  Headers:");
245                            for (key, value) in &collection.headers {
246                                println!("  {}: {}", key.bright_cyan(), value.bright_cyan());
247                            }
248                        }
249                        if let Some(requests) = collection.requests {
250                            for request in requests {
251                                if !endpoint.is_empty() && &request.name != endpoint {
252                                    continue;
253                                }
254                                println!(
255                                    "  [{}] {} - {} - {} - {}",
256                                    request.name.bright_yellow(),
257                                    request.method.to_string().bright_green(),
258                                    request.endpoint.bright_white(),
259                                    request.headers.len(),
260                                    request.body.as_ref().map_or(0, |b| b.len())
261                                );
262                                if *verbose {
263                                    // check if headers present
264                                    if !request.headers.is_empty() {
265                                        println!("    Headers:");
266                                        for (key, value) in &request.headers {
267                                            println!(
268                                                "    {}: {}",
269                                                key.bright_cyan(),
270                                                value.bright_cyan()
271                                            );
272                                        }
273                                    }
274                                    // check if body present
275                                    if request.body.is_some() {
276                                        println!("    Body:");
277                                        if let Some(body) = &request.body {
278                                            println!("    {}", body.bright_cyan());
279                                        };
280                                    }
281                                }
282                            }
283                        }
284                    }
285                }
286            }
287
288            // Delete a collection or endpoint
289            Self::Delete {
290                collection,
291                endpoint,
292                yes,
293            } => {
294                if endpoint.is_empty() {
295                    // Deleting a collection
296                    println!("Deleting collection '{}'", collection);
297                    let confirm = if !yes {
298                        helper::confirm("Are you sure you want to delete this collection?")
299                    } else {
300                        true
301                    };
302                    if confirm {
303                        manager.delete_collection(collection)?;
304                        println!("Collection deleted successfully!");
305                    } else {
306                        return Err("Deletion cancelled.".into());
307                    }
308                } else {
309                    // Deleting an endpoint
310                    println!("Deleting endpoint '{}'", endpoint);
311                    let confirm = if !yes {
312                        helper::confirm("Are you sure you want to delete this endpoint?")
313                    } else {
314                        true
315                    };
316                    if confirm {
317                        manager.delete_endpoint(collection, endpoint)?;
318                        println!("Endpoint deleted successfully!");
319                    } else {
320                        return Err("Deletion cancelled.".into());
321                    }
322                }
323            }
324
325            // Copy a collection or endpoint
326            Self::Copy {
327                collection,
328                endpoint,
329                to_col,
330                new_name,
331            } => {
332                if endpoint.is_empty() {
333                    // Copy collection
334                    manager.copy_collection(collection, new_name)?;
335                } else if *to_col {
336                    // Copy endpoint to another collection
337                    manager.copy_endpoint(collection, endpoint, new_name, Some(new_name))?;
338                } else {
339                    // Copy endpoint with new name in same collection
340                    manager.copy_endpoint(collection, endpoint, new_name, None)?;
341                }
342                println!("Copy command successful!");
343            }
344
345            // Update a collection or endpoint headers and body
346            Self::Update {
347                collection,
348                endpoint,
349                url,
350                headers,
351                body,
352            } => {
353                if endpoint.is_empty() {
354                    // Update collection
355                    let url_opt = if url.is_empty() {
356                        None
357                    } else {
358                        Some(url.as_str())
359                    };
360                    let headers_opt = if headers.is_empty() {
361                        None
362                    } else {
363                        Some(headers.clone())
364                    };
365                    manager.update_collection(collection, url_opt, headers_opt)?;
366                } else {
367                    // Update endpoint
368                    let url_opt = if url.is_empty() {
369                        None
370                    } else {
371                        Some(url.as_str())
372                    };
373                    let headers_opt = if headers.is_empty() {
374                        None
375                    } else {
376                        Some(headers.clone())
377                    };
378                    let body_opt = if body.is_empty() {
379                        Some(String::new()) // Empty body clears the existing body
380                    } else {
381                        Some(body.clone())
382                    };
383                    manager.update_endpoint(
384                        collection,
385                        endpoint,
386                        url_opt,
387                        headers_opt,
388                        body_opt,
389                    )?;
390                }
391                println!("Collection updated successfully!");
392            }
393
394            // Add a new collection or update an existing one
395            Self::Col { name, url, headers } => {
396                let exists = manager.get_collection(name).is_ok();
397                manager.add_collection(name, url, headers.clone())?;
398                if exists {
399                    eprintln!("Collection with name '{}' already exists.", name);
400                    println!("Collection updated successfully!");
401                } else {
402                    println!("Collection added successfully!");
403                }
404            }
405
406            // Add a new endpoint to a collection or update an existing one
407            Self::Endpoint {
408                collection,
409                name,
410                path,
411                method,
412                headers,
413                body,
414            } => {
415                let method: Method = method
416                    .to_uppercase()
417                    .parse()
418                    .map_err(|_| format!("Invalid HTTP method: {}", method))?;
419
420                let body_opt = if body.trim().is_empty() {
421                    None
422                } else {
423                    Some(body.clone())
424                };
425
426                manager.add_endpoint(collection, name, path, method, headers.clone(), body_opt)?;
427                println!("Endpoint added successfully!");
428            }
429        }
430
431        Ok("".to_string())
432    }
433}