Skip to main content

ito_domain/changes/
mutations.rs

1//! Change artifact mutation port definitions.
2
3use std::io;
4
5use thiserror::Error;
6
7/// Result alias for change artifact mutation operations.
8pub type ChangeArtifactMutationServiceResult<T> = Result<T, ChangeArtifactMutationError>;
9
10/// A mutable artifact within an active change.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum ChangeArtifactKind {
13    /// `proposal.md`
14    Proposal,
15    /// `design.md`
16    Design,
17    /// The change's tracking artifact (usually `tasks.md`).
18    Tasks,
19    /// A change-local spec delta identified by capability.
20    SpecDelta {
21        /// Capability directory name under the change's `specs/` directory.
22        capability: String,
23    },
24}
25
26impl ChangeArtifactKind {
27    /// Render a stable label for diagnostics and CLI output.
28    pub fn label(&self) -> String {
29        match self {
30            ChangeArtifactKind::Proposal => "proposal".to_string(),
31            ChangeArtifactKind::Design => "design".to_string(),
32            ChangeArtifactKind::Tasks => "tasks".to_string(),
33            ChangeArtifactKind::SpecDelta { capability } => format!("spec:{capability}"),
34        }
35    }
36}
37
38/// Reference to a mutable artifact within an active change.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct ChangeArtifactRef {
41    /// Canonical change identifier.
42    pub change_id: String,
43    /// Artifact kind to mutate.
44    pub artifact: ChangeArtifactKind,
45}
46
47impl ChangeArtifactRef {
48    /// Render a stable label for diagnostics and CLI output.
49    pub fn label(&self) -> String {
50        format!("{}:{}", self.change_id, self.artifact.label())
51    }
52}
53
54/// Result of mutating a change artifact.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ChangeArtifactMutationResult {
57    /// Artifact target that was mutated.
58    pub target: ChangeArtifactRef,
59    /// Whether the artifact existed before the mutation.
60    pub existed: bool,
61    /// Backend or store revision after the mutation, when applicable.
62    pub revision: Option<String>,
63}
64
65/// Error type for change artifact mutation ports.
66#[derive(Debug, Error)]
67pub enum ChangeArtifactMutationError {
68    /// Filesystem or transport failure.
69    #[error("I/O failure while {context}: {source}")]
70    Io {
71        /// Short operation context.
72        context: String,
73        /// Source error.
74        #[source]
75        source: io::Error,
76    },
77
78    /// Validation or precondition failure.
79    #[error("{0}")]
80    Validation(String),
81
82    /// Requested artifact was not found.
83    #[error("{0}")]
84    NotFound(String),
85
86    /// Unexpected transport or backend failure.
87    #[error("{0}")]
88    Other(String),
89}
90
91impl ChangeArtifactMutationError {
92    /// Build an I/O flavored error.
93    pub fn io(context: impl Into<String>, source: io::Error) -> Self {
94        Self::Io {
95            context: context.into(),
96            source,
97        }
98    }
99
100    /// Build a validation error.
101    pub fn validation(message: impl Into<String>) -> Self {
102        Self::Validation(message.into())
103    }
104
105    /// Build a not-found error.
106    pub fn not_found(message: impl Into<String>) -> Self {
107        Self::NotFound(message.into())
108    }
109
110    /// Build a catch-all error.
111    pub fn other(message: impl Into<String>) -> Self {
112        Self::Other(message.into())
113    }
114}
115
116/// Port for active-change artifact mutations.
117pub trait ChangeArtifactMutationService: Send + Sync {
118    /// Load the current artifact contents, if available.
119    fn load_artifact(
120        &self,
121        target: &ChangeArtifactRef,
122    ) -> ChangeArtifactMutationServiceResult<Option<String>>;
123
124    /// Replace the artifact contents completely.
125    fn write_artifact(
126        &self,
127        target: &ChangeArtifactRef,
128        content: &str,
129    ) -> ChangeArtifactMutationServiceResult<ChangeArtifactMutationResult>;
130
131    /// Apply a targeted patch to the current artifact contents.
132    fn patch_artifact(
133        &self,
134        target: &ChangeArtifactRef,
135        patch: &str,
136    ) -> ChangeArtifactMutationServiceResult<ChangeArtifactMutationResult>;
137}