cli

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

commit b07692e148c6e139a25ec5dd2f14b7459419b3ee
parent f97e6106c21f54f8fd6c8e4463eb4f37b5f02773
Author: triesap <tyson@radroots.org>
Date:   Mon,  6 Apr 2026 23:24:34 +0000

cli: make identity show read-only

Diffstat:
Msrc/commands/identity.rs | 41+++++++++++++++++++++++++++++++++++------
Msrc/commands/mod.rs | 4+---
Msrc/domain/runtime.rs | 15++++++++++++++-
Msrc/render/mod.rs | 24++++++++++++++++--------
Mtests/identity_commands.rs | 34++++++++++++++++++++++++++++++++++
5 files changed, 100 insertions(+), 18 deletions(-)

diff --git a/src/commands/identity.rs b/src/commands/identity.rs @@ -1,7 +1,11 @@ -use crate::domain::runtime::{IdentityInitView, IdentityPublicView, IdentityShowView}; +use crate::domain::runtime::{ + CommandDisposition, CommandOutput, CommandView, IdentityInitView, IdentityPublicView, + IdentityShowView, +}; use crate::runtime::RuntimeError; use crate::runtime::config::RuntimeConfig; use crate::runtime::identity::{initialize_identity, load_identity}; +use radroots_identity::IdentityError; pub fn init(config: &RuntimeConfig) -> Result<IdentityInitView, RuntimeError> { let identity = initialize_identity(&config.identity)?; @@ -12,10 +16,35 @@ pub fn init(config: &RuntimeConfig) -> Result<IdentityInitView, RuntimeError> { }) } -pub fn show(config: &RuntimeConfig) -> Result<IdentityShowView, RuntimeError> { - let identity = load_identity(&config.identity)?; - Ok(IdentityShowView { - path: identity.path.display().to_string(), - public_identity: IdentityPublicView::from_public_identity(&identity.public_identity), +pub fn show(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> { + let view = match load_identity(&config.identity) { + Ok(identity) => IdentityShowView { + path: identity.path.display().to_string(), + state: "ready".to_owned(), + reason: None, + public_identity: Some(IdentityPublicView::from_public_identity( + &identity.public_identity, + )), + }, + Err(RuntimeError::Identity(IdentityError::NotFound(path))) => IdentityShowView { + path: path.display().to_string(), + state: "unconfigured".to_owned(), + reason: Some(format!( + "local identity file was not found at {}", + path.display() + )), + public_identity: None, + }, + Err(error) => return Err(error), + }; + + Ok(match view.disposition() { + CommandDisposition::Success => CommandOutput::success(CommandView::IdentityShow(view)), + CommandDisposition::Unconfigured => { + CommandOutput::unconfigured(CommandView::IdentityShow(view)) + } + CommandDisposition::ExternalUnavailable => { + CommandOutput::external_unavailable(CommandView::IdentityShow(view)) + } }) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs @@ -19,9 +19,7 @@ pub fn dispatch( IdentityCommand::Init => Ok(CommandOutput::success(CommandView::IdentityInit( identity::init(config)?, ))), - IdentityCommand::Show => Ok(CommandOutput::success(CommandView::IdentityShow( - identity::show(config)?, - ))), + IdentityCommand::Show => identity::show(config), }, Command::Myc(myc) => match myc.command { MycCommand::Status => Ok(myc::status(config)), diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs @@ -119,7 +119,20 @@ impl IdentityPublicView { #[derive(Debug, Clone, Serialize)] pub struct IdentityShowView { pub path: String, - pub public_identity: IdentityPublicView, + pub state: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub public_identity: Option<IdentityPublicView>, +} + +impl IdentityShowView { + pub fn disposition(&self) -> CommandDisposition { + match self.state.as_str() { + "unconfigured" => CommandDisposition::Unconfigured, + _ => CommandDisposition::Success, + } + } } #[derive(Debug, Clone, Serialize)] diff --git a/src/render/mod.rs b/src/render/mod.rs @@ -33,17 +33,25 @@ fn render_human(output: &CommandOutput) -> Result<(), RuntimeError> { CommandView::IdentityShow(view) => { writeln!(stdout, "identity")?; writeln!(stdout, " path: {}", view.path)?; - writeln!(stdout, " id: {}", view.public_identity.id)?; - writeln!( - stdout, - " public key hex: {}", - view.public_identity.public_key_hex - )?; + writeln!(stdout, " state: {}", view.state)?; writeln!( stdout, - " public key npub: {}", - view.public_identity.public_key_npub + " reason: {}", + view.reason.as_deref().unwrap_or("<none>") )?; + if let Some(public_identity) = &view.public_identity { + writeln!(stdout, " id: {}", public_identity.id)?; + writeln!( + stdout, + " public key hex: {}", + public_identity.public_key_hex + )?; + writeln!( + stdout, + " public key npub: {}", + public_identity.public_key_npub + )?; + } } CommandView::MycStatus(view) => { render_myc_status(&mut stdout, view)?; diff --git a/tests/identity_commands.rs b/tests/identity_commands.rs @@ -68,8 +68,42 @@ fn identity_show_json_reads_existing_public_identity() { let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); let json: Value = serde_json::from_str(stdout.as_str()).expect("json output"); assert_eq!(json["path"], identity_path.display().to_string()); + assert_eq!(json["state"], "ready"); assert!(json["public_identity"]["id"].is_string()); assert!(json["public_identity"]["public_key_hex"].is_string()); assert!(json["public_identity"]["public_key_npub"].is_string()); assert!(json.get("secret_key").is_none()); } + +#[test] +fn identity_show_json_reports_unconfigured_without_creating_identity() { + let dir = tempdir().expect("tempdir"); + let identity_path = dir.path().join("missing-identity.json"); + + let output = Command::cargo_bin("radroots") + .expect("binary") + .args([ + "--json", + "--identity-path", + identity_path.to_str().expect("identity path"), + "--allow-generate-identity", + "identity", + "show", + ]) + .output() + .expect("run identity show"); + + assert_eq!(output.status.code(), Some(3)); + assert!(!identity_path.exists()); + + let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); + let json: Value = serde_json::from_str(stdout.as_str()).expect("json output"); + assert_eq!(json["path"], identity_path.display().to_string()); + assert_eq!(json["state"], "unconfigured"); + assert!( + json["reason"] + .as_str() + .is_some_and(|value| value.contains("local identity file was not found")) + ); + assert_eq!(json.get("public_identity"), None); +}