cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

main.rs (3596B)


      1 #![forbid(unsafe_code)]
      2 
      3 use std::process::ExitCode;
      4 
      5 use clap::Parser;
      6 use radroots_cli::{
      7     HealthCommand, ProfileCommand, TargetCliArgs, TargetCommand, TargetOutputFormat,
      8 };
      9 use serde_json::{Value, json};
     10 
     11 const OUTPUT_SCHEMA: &str = "radroots.cli.output.v1";
     12 
     13 #[tokio::main]
     14 async fn main() -> ExitCode {
     15     let args = TargetCliArgs::parse();
     16     let operation_id = args.command.operation_id();
     17     let format = args.format.unwrap_or(TargetOutputFormat::Terminal);
     18 
     19     let result = execute(&args.command).await;
     20     let (status, data, error, exit_code) = match result {
     21         Ok(data) => ("ok", data, Value::Null, ExitCode::SUCCESS),
     22         Err(message) => (
     23             "error",
     24             Value::Null,
     25             json!({
     26                 "code": "unsupported_operation",
     27                 "message": message,
     28                 "recovery": "use `radroots health inspect` to inspect the release-v1 host surface"
     29             }),
     30             ExitCode::from(2),
     31         ),
     32     };
     33 
     34     let envelope = json!({
     35         "schema": OUTPUT_SCHEMA,
     36         "operation_id": operation_id,
     37         "status": status,
     38         "data": data,
     39         "error": error,
     40     });
     41     render(format, operation_id, status, &envelope);
     42     exit_code
     43 }
     44 
     45 async fn execute(command: &TargetCommand) -> Result<Value, String> {
     46     match command {
     47         TargetCommand::Profile(args) if matches!(args.command, ProfileCommand::Inspect) => {
     48             inspect_local_profile().await
     49         }
     50         TargetCommand::Health(args) if matches!(args.command, HealthCommand::Inspect) => {
     51             inspect_health().await
     52         }
     53         _ => Err(format!(
     54             "`{}` is not available in the crates-release-v1 CLI host",
     55             command.operation_id()
     56         )),
     57     }
     58 }
     59 
     60 async fn inspect_local_profile() -> Result<Value, String> {
     61     let client = radroots::client::memory()
     62         .build()
     63         .map_err(|error| format!("failed to construct the local SDK client: {error}"))?;
     64     let profile = radroots::client::local_only();
     65     client
     66         .close()
     67         .await
     68         .map_err(|error| format!("failed to close the local SDK client: {error}"))?;
     69 
     70     Ok(json!({
     71         "profile": "local_only",
     72         "network_enabled": !profile.is_local_only(),
     73         "storage": "memory",
     74         "artifact_graph": "registry"
     75     }))
     76 }
     77 
     78 async fn inspect_health() -> Result<Value, String> {
     79     let profile = inspect_local_profile().await?;
     80     Ok(json!({
     81         "ready": true,
     82         "release": "crates-v1",
     83         "profile": profile
     84     }))
     85 }
     86 
     87 fn render(format: TargetOutputFormat, operation_id: &str, status: &str, envelope: &Value) {
     88     match format {
     89         TargetOutputFormat::Json | TargetOutputFormat::Ndjson => println!("{envelope}"),
     90         TargetOutputFormat::Terminal => {
     91             if status == "ok" {
     92                 println!("{operation_id}: ready");
     93             } else {
     94                 let message = envelope["error"]["message"]
     95                     .as_str()
     96                     .unwrap_or("operation failed");
     97                 eprintln!("{operation_id}: {message}");
     98             }
     99         }
    100     }
    101 }
    102 
    103 #[cfg(test)]
    104 mod tests {
    105     use super::*;
    106 
    107     #[test]
    108     fn unsupported_operations_fail_closed() {
    109         let args = TargetCliArgs::try_parse_from(["radroots", "account", "list"])
    110             .expect("valid resource command");
    111         let runtime = tokio::runtime::Runtime::new().expect("runtime");
    112         let error = runtime
    113             .block_on(execute(&args.command))
    114             .expect_err("unsupported operation");
    115         assert!(error.contains("account.list"));
    116     }
    117 }