Skip to main content

chant/operations/
cancel.rs

1//! Cancel operation for specs.
2//!
3//! Provides the canonical implementation for canceling specs.
4
5use anyhow::Result;
6use std::path::Path;
7
8use crate::spec::{Spec, SpecStatus};
9
10/// Options for the cancel operation.
11#[derive(Debug, Clone, Default)]
12pub struct CancelOptions {
13    /// Whether to proceed even if the spec is already cancelled.
14    pub force: bool,
15}
16
17/// Cancel a spec by setting its status to Cancelled.
18///
19/// This operation:
20/// - Sets the spec status to Cancelled using the state machine
21/// - Saves the updated spec to disk
22///
23/// # Arguments
24/// * `specs_dir` - Path to the specs directory
25/// * `spec_id` - ID of the spec to cancel
26/// * `options` - Cancel operation options
27///
28/// # Returns
29/// * `Ok(())` if the spec was successfully cancelled
30/// * `Err(_)` if the spec doesn't exist, is already cancelled (without force), or can't be transitioned
31pub fn cancel_spec(specs_dir: &Path, spec_id: &str, options: &CancelOptions) -> Result<Spec> {
32    use crate::spec;
33
34    // Resolve and load the spec
35    let mut spec = spec::resolve_spec(specs_dir, spec_id)?;
36
37    // Check if already cancelled
38    if spec.frontmatter.status == SpecStatus::Cancelled && !options.force {
39        anyhow::bail!("Spec '{}' is already cancelled", spec.id);
40    }
41
42    // Set status to cancelled using state machine
43    spec.set_status(SpecStatus::Cancelled)
44        .map_err(|e| anyhow::anyhow!("Failed to transition spec to Cancelled: {}", e))?;
45
46    // Save the spec
47    let spec_path = specs_dir.join(format!("{}.md", spec.id));
48    spec.save(&spec_path)?;
49
50    Ok(spec)
51}