1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! Core-layer error types.
//!
//! [`CoreError`](crate::errors::CoreError) is the canonical error type for `ito-core`. All public
//! functions in this crate return [`CoreResult`](crate::errors::CoreResult) rather than adapter-level
//! error types. Adapter layers (CLI, web) convert `CoreError` into their own
//! presentation types (e.g., miette `Report` for rich terminal output).
use std::io;
use ito_domain::errors::DomainError;
use thiserror::Error;
use crate::capabilities::CompiledFeature;
/// Result alias for core-layer operations.
pub type CoreResult<T> = Result<T, CoreError>;
/// Canonical error type for the core orchestration layer.
///
/// Variants cover the major failure categories encountered by core use-cases.
/// None of the variants carry presentation logic — that belongs in the adapter.
#[derive(Debug, Error)]
pub enum CoreError {
/// An error propagated from the domain layer.
#[error(transparent)]
Domain(#[from] DomainError),
/// Filesystem or other I/O failure.
#[error("{context}: {source}")]
Io {
/// Short description of the operation that failed.
context: String,
/// Underlying I/O error.
#[source]
source: io::Error,
},
/// Input validation failure (bad arguments, constraint violations).
#[error("{0}")]
Validation(String),
/// Parse failure (duration strings, JSON, YAML, etc.).
#[error("{0}")]
Parse(String),
/// Process execution failure (git, shell commands).
#[error("{0}")]
Process(String),
/// SQLite operation failure.
#[error("sqlite error: {0}")]
Sqlite(String),
/// An expected asset or resource was not found.
#[error("{0}")]
NotFound(String),
/// Serialization or deserialization failure.
#[error("{context}: {message}")]
Serde {
/// Short description of the operation.
context: String,
/// Error detail.
message: String,
},
/// Configuration or a compatibility command requested code not present in this build.
#[error(
"feature '{feature}' is unavailable (requested by {requested_by}). Recovery: {recovery}"
)]
FeatureUnavailable {
/// Stable Cargo feature identifier.
feature: CompiledFeature,
/// Configuration field or command that requested the feature.
requested_by: String,
/// Action the user can take without silent fallback.
recovery: String,
},
}
impl CoreError {
/// Build an I/O error with context.
pub fn io(context: impl Into<String>, source: io::Error) -> Self {
Self::Io {
context: context.into(),
source,
}
}
/// Build a validation error.
pub fn validation(msg: impl Into<String>) -> Self {
Self::Validation(msg.into())
}
/// Build a parse error.
pub fn parse(msg: impl Into<String>) -> Self {
Self::Parse(msg.into())
}
/// Build a process error.
pub fn process(msg: impl Into<String>) -> Self {
Self::Process(msg.into())
}
/// Build a not-found error.
pub fn not_found(msg: impl Into<String>) -> Self {
Self::NotFound(msg.into())
}
/// Create a `CoreError::Serde` containing a context and a message describing a
/// serialization or deserialization failure.
///
/// # Examples
///
/// ```
/// use ito_core::errors::CoreError;
///
/// let err = CoreError::serde("load config", "missing field `name`");
/// match err {
/// CoreError::Serde { context, message } => {
/// assert_eq!(context, "load config");
/// assert_eq!(message, "missing field `name`");
/// }
/// _ => panic!("expected Serde variant"),
/// }
/// ```
pub fn serde(context: impl Into<String>, message: impl Into<String>) -> Self {
Self::Serde {
context: context.into(),
message: message.into(),
}
}
/// Wraps a human-readable SQLite error message into a `CoreError::Sqlite`.
///
/// Returns a `CoreError::Sqlite` containing the provided message.
///
/// # Examples
///
/// ```
/// use ito_core::errors::CoreError;
///
/// let err = CoreError::sqlite("database locked");
/// let CoreError::Sqlite(msg) = err else {
/// panic!("expected Sqlite variant");
/// };
/// assert_eq!(msg, "database locked");
/// ```
pub fn sqlite(msg: impl Into<String>) -> Self {
Self::Sqlite(msg.into())
}
/// Build a typed unavailable-feature error.
pub fn feature_unavailable(
feature: CompiledFeature,
requested_by: impl Into<String>,
recovery: impl Into<String>,
) -> Self {
Self::FeatureUnavailable {
feature,
requested_by: requested_by.into(),
recovery: recovery.into(),
}
}
}
#[cfg(test)]
#[path = "errors_tests.rs"]
mod errors_tests;