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 }