use clap::{ArgMatches, Command};
use crate::domain::model::event::{Event, EventAction, State};
use crate::domain::usecases::clock::Clock;
use crate::domain::usecases::issue::{update_issue, IssueRepository, UpdateIssueOutcome};
use crate::infra::driven::clock::SystemClock;
use crate::infra::driving::cli::commands::generic::status as generic;
use crate::infra::driving::cli::errors::{die1, CliError};
use crate::infra::driving::cli::helpers::required_str;
use crate::infra::driving::cli::id_parsing::parse_issue_id;
use crate::infra::driving::cli::Context;
const NOUN: &str = "issue";
pub(super) fn subcommand() -> Command {
generic::subcommand(NOUN, "Issue ID (e.g. ISSUE-01H8MSQGXYZ12)")
}
pub(super) fn execute(matches: &ArgMatches, ctx: &Context<'_>) {
match matches.subcommand() {
Some(("show", sub)) => execute_show(sub, ctx),
Some(("update", sub)) => execute_update(sub, ctx),
_ => unreachable!(),
}
}
fn execute_show(sub: &ArgMatches, ctx: &Context<'_>) {
let output_fmt = ctx.output_fmt;
let (id_str, id) = parse_id(sub, ctx);
let repo = ctx.issue_repository();
let view = match repo.find_by_id(&id) {
Ok(Some(issue)) => generic::View::Shown {
status: issue.status.as_str().to_string(),
},
Ok(None) => generic::View::NotFound,
Err(e) => die1(CliError::new(e.to_string()), output_fmt),
};
generic::render(view, id_str, &id.to_string(), NOUN, output_fmt);
}
fn execute_update(sub: &ArgMatches, ctx: &Context<'_>) {
let output_fmt = ctx.output_fmt;
let (id_str, id) = parse_id(sub, ctx);
let repo = ctx.issue_repository();
let current = repo
.find_by_id(&id)
.unwrap_or_else(|e| die1(CliError::new(e.to_string()), output_fmt));
let current_status = match current {
Some(ref issue) => issue.status.clone(),
None => {
generic::render(
generic::View::NotFound,
id_str,
&id.to_string(),
NOUN,
output_fmt,
);
return;
}
};
let raw_new = generic::required_status_arg(sub);
let new_status = generic::resolve_status_or_die(ctx.issues_statuses, raw_new, output_fmt);
let event = Event {
timestamp: SystemClock.now(),
action: EventAction::StatusChanged {
from: State::new(current_status.as_str())
.expect("resolved status names are valid State"),
to: State::new(new_status.as_str()).expect("resolved status names are valid State"),
},
};
let outcome = update_issue(&repo, &id, event)
.unwrap_or_else(|e| die1(CliError::new(e.to_string()), output_fmt));
let view = match outcome {
UpdateIssueOutcome::StatusChanged { from, to } => generic::View::Changed { from, to },
UpdateIssueOutcome::NoOp => generic::View::NoOp,
UpdateIssueOutcome::EventAppended => generic::View::NoOp,
};
generic::render(view, id_str, &id.to_string(), NOUN, output_fmt);
}
fn parse_id<'a>(
sub: &'a ArgMatches,
ctx: &Context<'_>,
) -> (&'a str, crate::domain::model::record_ref::IssueRef) {
let output_fmt = ctx.output_fmt;
let id_str = required_str(sub, "id");
let id = parse_issue_id(id_str, ctx.issues_id_prefix()).unwrap_or_else(|e| {
die1(
CliError::new(format!("invalid issue ID '{id_str}': {e}")).kind("validation"),
output_fmt,
);
});
(id_str, id)
}