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
13/// Result alias for core-layer operations.
14pub type CoreResult<T> = Result<T, CoreError>;
15
16/// Canonical error type for the core orchestration layer.
17///
18/// Variants cover the major failure categories encountered by core use-cases.
19/// None of the variants carry presentation logic — that belongs in the adapter.
20#[derive(Debug, Error)]
21pub enum CoreError {
22 /// An error propagated from the domain layer.
23 #[error(transparent)]
24 Domain(#[from] DomainError),
25
26 /// Filesystem or other I/O failure.
27 #[error("{context}: {source}")]
28 Io {
29 /// Short description of the operation that failed.
30 context: String,
31 /// Underlying I/O error.
32 #[source]
33 source: io::Error,
34 },
35
36 /// Input validation failure (bad arguments, constraint violations).
37 #[error("{0}")]
38 Validation(String),
39
40 /// Parse failure (duration strings, JSON, YAML, etc.).
41 #[error("{0}")]
42 Parse(String),
43
44 /// Process execution failure (git, shell commands).
45 #[error("{0}")]
46 Process(String),
47
48 /// SQLite operation failure.
49 #[error("sqlite error: {0}")]
50 Sqlite(String),
51
52 /// An expected asset or resource was not found.
53 #[error("{0}")]
54 NotFound(String),
55
56 /// Serialization or deserialization failure.
57 #[error("{context}: {message}")]
58 Serde {
59 /// Short description of the operation.
60 context: String,
61 /// Error detail.
62 message: String,
63 },
64}
65
66impl CoreError {
67 /// Build an I/O error with context.
68 pub fn io(context: impl Into<String>, source: io::Error) -> Self {
69 Self::Io {
70 context: context.into(),
71 source,
72 }
73 }
74
75 /// Build a validation error.
76 pub fn validation(msg: impl Into<String>) -> Self {
77 Self::Validation(msg.into())
78 }
79
80 /// Build a parse error.
81 pub fn parse(msg: impl Into<String>) -> Self {
82 Self::Parse(msg.into())
83 }
84
85 /// Build a process error.
86 pub fn process(msg: impl Into<String>) -> Self {
87 Self::Process(msg.into())
88 }
89
90 /// Build a not-found error.
91 pub fn not_found(msg: impl Into<String>) -> Self {
92 Self::NotFound(msg.into())
93 }
94
95 /// Create a `CoreError::Serde` containing a context and a message describing a
96 /// serialization or deserialization failure.
97 ///
98 /// # Examples
99 ///
100 /// ```
101 /// use ito_core::errors::CoreError;
102 ///
103 /// let err = CoreError::serde("load config", "missing field `name`");
104 /// match err {
105 /// CoreError::Serde { context, message } => {
106 /// assert_eq!(context, "load config");
107 /// assert_eq!(message, "missing field `name`");
108 /// }
109 /// _ => panic!("expected Serde variant"),
110 /// }
111 /// ```
112 pub fn serde(context: impl Into<String>, message: impl Into<String>) -> Self {
113 Self::Serde {
114 context: context.into(),
115 message: message.into(),
116 }
117 }
118
119 /// Wraps a human-readable SQLite error message into a `CoreError::Sqlite`.
120 ///
121 /// Returns a `CoreError::Sqlite` containing the provided message.
122 ///
123 /// # Examples
124 ///
125 /// ```
126 /// use ito_core::errors::CoreError;
127 ///
128 /// let err = CoreError::sqlite("database locked");
129 /// let CoreError::Sqlite(msg) = err else {
130 /// panic!("expected Sqlite variant");
131 /// };
132 /// assert_eq!(msg, "database locked");
133 /// ```
134 pub fn sqlite(msg: impl Into<String>) -> Self {
135 Self::Sqlite(msg.into())
136 }
137}
138
139#[cfg(test)]
140#[path = "errors_tests.rs"]
141mod errors_tests;