Skip to main content

ito_core/
errors.rs

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