cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
use clap::{ArgMatches, Command};

use crate::domain::model::title::Title;
use crate::domain::usecases::issue::{edit_issue_title, EditIssueTitleOutcome, IssueRepository};
use crate::infra::driving::cli::commands::generic::title 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 {
            title: issue.title.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 raw = generic::required_title_arg(sub);
    let new_title = Title::new(raw).unwrap_or_else(|e| {
        die1(
            CliError::new(format!("invalid title: {e}")).kind("validation"),
            output_fmt,
        )
    });
    let repo = ctx.issue_repository();
    let view = match edit_issue_title(&repo, &id, new_title) {
        Ok(EditIssueTitleOutcome::Updated) => generic::View::Updated,
        Ok(EditIssueTitleOutcome::NoOp) => generic::View::NoOp,
        Err(e) if e.to_string().contains("not found") => 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 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)
}