Skip to main content

romm_cli/commands/
api.rs

1use anyhow::{anyhow, Result};
2use clap::{Args, Subcommand};
3
4use crate::client::RommClient;
5use crate::commands::OutputFormat;
6
7/// Low-level escape hatch for calling arbitrary ROMM API endpoints.
8#[derive(Args, Debug)]
9pub struct ApiCommand {
10    #[command(subcommand)]
11    pub action: Option<ApiAction>,
12
13    /// HTTP method (legacy, use 'api call <method> <path>')
14    pub method: Option<String>,
15
16    /// API path (legacy, use 'api call <method> <path>')
17    pub path: Option<String>,
18
19    /// Query parameters as key=value, repeatable
20    #[arg(long = "query", global = true)]
21    pub query: Vec<String>,
22
23    /// JSON request body as a string
24    #[arg(long, global = true)]
25    pub data: Option<String>,
26}
27
28#[derive(Subcommand, Debug)]
29pub enum ApiAction {
30    /// Make a generic API call
31    Call {
32        /// HTTP method (GET, POST, etc.)
33        method: String,
34        /// API path (e.g. /api/roms)
35        path: String,
36    },
37    /// Shortcut for GET request
38    Get {
39        /// API path
40        path: String,
41    },
42    /// Shortcut for POST request
43    Post {
44        /// API path
45        path: String,
46    },
47}
48
49pub async fn handle(cmd: ApiCommand, client: &RommClient, format: OutputFormat) -> Result<()> {
50    let (method, path) = match cmd.action {
51        Some(ApiAction::Call { method, path }) => (method, path),
52        Some(ApiAction::Get { path }) => ("GET".to_string(), path),
53        Some(ApiAction::Post { path }) => ("POST".to_string(), path),
54        None => {
55            let m = cmd
56                .method
57                .ok_or_else(|| anyhow!("Method is required (e.g. 'api call GET /api/roms')"))?;
58            let p = cmd
59                .path
60                .ok_or_else(|| anyhow!("Path is required (e.g. 'api call GET /api/roms')"))?;
61            (m, p)
62        }
63    };
64
65    let mut query_pairs = Vec::new();
66    for q in &cmd.query {
67        if let Some((k, v)) = q.split_once('=') {
68            query_pairs.push((k.to_string(), v.to_string()));
69        } else {
70            eprintln!(
71                "warning: ignoring malformed --query value {:?}; expected key=value",
72                q
73            );
74        }
75    }
76
77    let body = if let Some(data) = &cmd.data {
78        Some(serde_json::from_str(data)?)
79    } else {
80        None
81    };
82
83    let value = client
84        .request_json(&method, &path, &query_pairs, body)
85        .await?;
86
87    match format {
88        OutputFormat::Json => {
89            println!("{}", serde_json::to_string_pretty(&value)?);
90        }
91        OutputFormat::Text => {
92            println!("{}", serde_json::to_string_pretty(&value)?);
93        }
94    }
95    Ok(())
96}