Skip to main content

ito_domain/changes/
repository.rs

1//! Change repository port definitions.
2
3use super::{Change, ChangeSummary};
4use crate::errors::DomainResult;
5
6/// Lifecycle filter for change repository queries.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum ChangeLifecycleFilter {
9    /// Only active (non-archived) changes.
10    #[default]
11    Active,
12    /// Only archived changes.
13    Archived,
14    /// Both active and archived changes.
15    All,
16}
17
18impl ChangeLifecycleFilter {
19    /// Return `true` when active changes are included.
20    pub fn includes_active(self) -> bool {
21        matches!(
22            self,
23            ChangeLifecycleFilter::Active | ChangeLifecycleFilter::All
24        )
25    }
26
27    /// Return `true` when archived changes are included.
28    pub fn includes_archived(self) -> bool {
29        matches!(
30            self,
31            ChangeLifecycleFilter::Archived | ChangeLifecycleFilter::All
32        )
33    }
34
35    /// Render the filter as a lowercase string (for API usage).
36    pub fn as_str(self) -> &'static str {
37        match self {
38            ChangeLifecycleFilter::Active => "active",
39            ChangeLifecycleFilter::Archived => "archived",
40            ChangeLifecycleFilter::All => "all",
41        }
42    }
43
44    /// Parse a lowercase lifecycle filter string.
45    pub fn parse(value: &str) -> Option<Self> {
46        match value.trim().to_lowercase().as_str() {
47            "active" => Some(ChangeLifecycleFilter::Active),
48            "archived" => Some(ChangeLifecycleFilter::Archived),
49            "all" => Some(ChangeLifecycleFilter::All),
50            _ => None,
51        }
52    }
53}
54
55/// Deterministic resolution result for a change target input.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum ChangeTargetResolution {
58    /// Exactly one canonical change id matched.
59    Unique(String),
60    /// Multiple canonical change ids matched the target.
61    Ambiguous(Vec<String>),
62    /// No changes matched the target.
63    NotFound,
64}
65
66/// Options for resolving a change target.
67#[derive(Debug, Clone, Copy, Default)]
68pub struct ResolveTargetOptions {
69    /// Lifecycle filter to apply when resolving targets.
70    pub lifecycle: ChangeLifecycleFilter,
71}
72
73/// Port for accessing change data.
74///
75/// Domain and adapters should depend on this interface rather than concrete
76/// storage details.
77pub trait ChangeRepository {
78    /// Resolve an input change target into a canonical change id.
79    fn resolve_target(&self, input: &str) -> ChangeTargetResolution {
80        self.resolve_target_with_options(input, ResolveTargetOptions::default())
81    }
82
83    /// Resolve an input change target into a canonical change id using options.
84    fn resolve_target_with_options(
85        &self,
86        input: &str,
87        options: ResolveTargetOptions,
88    ) -> ChangeTargetResolution;
89
90    /// Return best-effort suggestions for a change target.
91    fn suggest_targets(&self, input: &str, max: usize) -> Vec<String>;
92
93    /// Check if a change exists (active lifecycle only).
94    fn exists(&self, id: &str) -> bool;
95
96    /// Check if a change exists with a lifecycle filter.
97    fn exists_with_filter(&self, id: &str, filter: ChangeLifecycleFilter) -> bool;
98
99    /// Get a full change with all artifacts loaded (active lifecycle only).
100    fn get(&self, id: &str) -> DomainResult<Change> {
101        self.get_with_filter(id, ChangeLifecycleFilter::Active)
102    }
103
104    /// Get a full change with all artifacts loaded, scoped by lifecycle.
105    fn get_with_filter(&self, id: &str, filter: ChangeLifecycleFilter) -> DomainResult<Change>;
106
107    /// List all active changes as summaries (lightweight).
108    fn list(&self) -> DomainResult<Vec<ChangeSummary>> {
109        self.list_with_filter(ChangeLifecycleFilter::Active)
110    }
111
112    /// List changes as summaries with a lifecycle filter.
113    fn list_with_filter(&self, filter: ChangeLifecycleFilter) -> DomainResult<Vec<ChangeSummary>>;
114
115    /// List active changes belonging to a specific module.
116    fn list_by_module(&self, module_id: &str) -> DomainResult<Vec<ChangeSummary>> {
117        self.list_by_module_with_filter(module_id, ChangeLifecycleFilter::Active)
118    }
119
120    /// List changes belonging to a specific module with a lifecycle filter.
121    fn list_by_module_with_filter(
122        &self,
123        module_id: &str,
124        filter: ChangeLifecycleFilter,
125    ) -> DomainResult<Vec<ChangeSummary>>;
126
127    /// List active changes with incomplete tasks.
128    fn list_incomplete(&self) -> DomainResult<Vec<ChangeSummary>> {
129        self.list_incomplete_with_filter(ChangeLifecycleFilter::Active)
130    }
131
132    /// List changes with incomplete tasks and a lifecycle filter.
133    fn list_incomplete_with_filter(
134        &self,
135        filter: ChangeLifecycleFilter,
136    ) -> DomainResult<Vec<ChangeSummary>>;
137
138    /// List active changes with all tasks complete.
139    fn list_complete(&self) -> DomainResult<Vec<ChangeSummary>> {
140        self.list_complete_with_filter(ChangeLifecycleFilter::Active)
141    }
142
143    /// List changes with all tasks complete and a lifecycle filter.
144    fn list_complete_with_filter(
145        &self,
146        filter: ChangeLifecycleFilter,
147    ) -> DomainResult<Vec<ChangeSummary>>;
148
149    /// Get a summary for a specific active change (lightweight).
150    fn get_summary(&self, id: &str) -> DomainResult<ChangeSummary> {
151        self.get_summary_with_filter(id, ChangeLifecycleFilter::Active)
152    }
153
154    /// Get a summary for a specific change (lightweight) with a lifecycle filter.
155    fn get_summary_with_filter(
156        &self,
157        id: &str,
158        filter: ChangeLifecycleFilter,
159    ) -> DomainResult<ChangeSummary>;
160}