use crate::cli::{RelationAction, SortDir, SortKey};
use crate::context::CliContext;
use crate::error::{KanbanCliError, KanbanCliResult};
use crate::output;
use kanban_domain::dependencies::messages;
use kanban_domain::error::{DependencyError, DomainError};
use kanban_domain::sort::OrderedSorter;
use kanban_domain::{Card, CardSummary, GraphOperations, KanbanError, KanbanOperations};
use uuid::Uuid;
fn resolve_cards(ctx: &CliContext, ids: Vec<Uuid>) -> Vec<Card> {
ids.into_iter()
.filter_map(|id| match ctx.get_card(id) {
Ok(Some(card)) => Some(card),
Ok(None) => {
tracing::warn!(
"graph references unknown card id {id}; dropping from list (possible corruption)"
);
None
}
Err(e) => {
tracing::warn!("failed to resolve card id {id}: {e}; dropping from list");
None
}
})
.collect()
}
fn sort_and_summarize(mut cards: Vec<Card>, sort: SortKey, order: SortDir) -> Vec<CardSummary> {
let sorter = OrderedSorter::new(sort.to_sort_by(), order.to_sort_order());
sorter.sort_by(&mut cards);
cards.iter().map(CardSummary::from).collect()
}
fn resolve_children(ctx: &CliContext, raw: &[String]) -> KanbanCliResult<Vec<Uuid>> {
raw.iter()
.map(|r| ctx.resolve_card_id(r).map_err(Into::into))
.collect()
}
fn enrich_add_error_for_batch(
e: KanbanError,
parent: &str,
children_raw: &[String],
) -> KanbanCliError {
match e {
KanbanError::Domain(DomainError::Dependency(DependencyError::CycleDetected)) => {
let hint = if let [only] = children_raw {
messages::parent_cycle(parent, only)
} else {
format!(
"cycle detected: making {parent} a parent of one of [{}] would create a cycle",
children_raw.join(", ")
)
};
KanbanCliError::Resolution { hint }
}
KanbanError::Domain(DomainError::Dependency(DependencyError::SelfReference)) => {
KanbanCliError::Resolution {
hint: messages::parent_self_reference(parent),
}
}
KanbanError::Domain(DomainError::Dependency(DependencyError::DuplicateEdge)) => {
let hint = if let [only] = children_raw {
messages::parent_duplicate(parent, only)
} else {
format!(
"edge already exists: one of [{}] is already a child of {parent}",
children_raw.join(", ")
)
};
KanbanCliError::Resolution { hint }
}
KanbanError::Domain(DomainError::Dependency(DependencyError::EdgeNotFound)) => e.into(),
other => other.into(),
}
}
fn enrich_remove_error_for_batch(
e: KanbanError,
parent: &str,
children_raw: &[String],
) -> KanbanCliError {
match e {
KanbanError::Domain(DomainError::Dependency(DependencyError::EdgeNotFound)) => {
let hint = if let [only] = children_raw {
messages::parent_edge_not_found(parent, only)
} else {
format!(
"edge not found: no parent->child edge from {parent} to any of [{}] to remove",
children_raw.join(", ")
)
};
KanbanCliError::Resolution { hint }
}
KanbanError::Domain(DomainError::Dependency(DependencyError::CycleDetected)) => e.into(),
KanbanError::Domain(DomainError::Dependency(DependencyError::SelfReference)) => e.into(),
KanbanError::Domain(DomainError::Dependency(DependencyError::DuplicateEdge)) => e.into(),
other => other.into(),
}
}
pub async fn handle(ctx: &mut CliContext, action: RelationAction) -> anyhow::Result<()> {
let result: KanbanCliResult<serde_json::Value> = run(ctx, action).await;
match result {
Ok(value) => {
output::output_success(value);
Ok(())
}
Err(e) => output::output_error(&e.to_string()),
}
}
async fn run(ctx: &mut CliContext, action: RelationAction) -> KanbanCliResult<serde_json::Value> {
match action {
RelationAction::Add { parent, children } => {
let parent_uuid = ctx.resolve_card_id(&parent)?;
let child_uuids = resolve_children(ctx, &children)?;
let response = serde_json::json!({
"parent": parent_uuid.to_string(),
"children": serde_json::to_value(&child_uuids)?,
});
ctx.attach_children(parent_uuid, child_uuids)
.map_err(|e| enrich_add_error_for_batch(e, &parent, &children))?;
ctx.save().await?;
Ok(response)
}
RelationAction::Remove { parent, children } => {
let parent_uuid = ctx.resolve_card_id(&parent)?;
let child_uuids = resolve_children(ctx, &children)?;
let response = serde_json::json!({
"parent": parent_uuid.to_string(),
"children": serde_json::to_value(&child_uuids)?,
});
ctx.detach_children(parent_uuid, child_uuids)
.map_err(|e| enrich_remove_error_for_batch(e, &parent, &children))?;
ctx.save().await?;
Ok(response)
}
RelationAction::Parents { card, sort, order } => {
let uuid = ctx.resolve_card_id(&card)?;
let ids = ctx.list_parents_of(uuid)?;
let cards = resolve_cards(ctx, ids);
Ok(serde_json::to_value(sort_and_summarize(
cards, sort, order,
))?)
}
RelationAction::Children { card, sort, order } => {
let uuid = ctx.resolve_card_id(&card)?;
let ids = ctx.list_children_of(uuid)?;
let cards = resolve_cards(ctx, ids);
Ok(serde_json::to_value(sort_and_summarize(
cards, sort, order,
))?)
}
}
}