nika_engine/
error_domains.rs1use crate::error::NikaError;
34
35#[derive(Debug, thiserror::Error)]
41pub enum ProviderError {
42 #[error("[NIKA-030] Provider '{provider}' not configured")]
43 NotConfigured { provider: String },
44
45 #[error("[NIKA-031] Provider API error: {message}")]
46 ApiError { message: String },
47
48 #[error("[NIKA-032] Missing API key for provider '{provider}'")]
49 MissingApiKey { provider: String },
50
51 #[error("[NIKA-033] Invalid configuration: {message}")]
52 InvalidConfig { message: String },
53}
54
55impl From<ProviderError> for NikaError {
56 fn from(e: ProviderError) -> Self {
57 match e {
58 ProviderError::NotConfigured { provider } => {
59 NikaError::ProviderNotConfigured { provider }
60 }
61 ProviderError::ApiError { message } => NikaError::ProviderApiError { message },
62 ProviderError::MissingApiKey { provider } => {
63 NikaError::MissingApiKey { provider }
64 }
65 ProviderError::InvalidConfig { message } => NikaError::InvalidConfig { message },
66 }
67 }
68}
69
70#[derive(Debug, thiserror::Error)]
76pub enum DagError {
77 #[error("[NIKA-020] Cycle detected in DAG: {cycle}")]
78 CycleDetected { cycle: String },
79
80 #[error("[NIKA-021] Missing dependency: task '{task_id}' depends on unknown '{dep_id}'")]
81 MissingDependency { task_id: String, dep_id: String },
82
83 #[error("[NIKA-022] Duplicate task ID: '{task_id}' appears multiple times")]
84 DuplicateTaskId { task_id: String },
85}
86
87impl From<DagError> for NikaError {
88 fn from(e: DagError) -> Self {
89 match e {
90 DagError::CycleDetected { cycle } => NikaError::CycleDetected { cycle },
91 DagError::MissingDependency { task_id, dep_id } => {
92 NikaError::MissingDependency { task_id, dep_id }
93 }
94 DagError::DuplicateTaskId { task_id } => NikaError::DuplicateTaskId { task_id },
95 }
96 }
97}
98
99#[derive(Debug, thiserror::Error)]
105pub enum ExecutionError {
106 #[error("[NIKA-044] Exec error: {reason}")]
107 ExecFailed { reason: String },
108
109 #[error("[NIKA-045] Fetch error: {reason}")]
110 FetchFailed { reason: String },
111
112 #[error("[NIKA-046] Extract error: {reason}")]
113 ExtractFailed { reason: String },
114
115 #[error("[NIKA-096] Execution error: {0}")]
116 General(String),
117
118 #[error("[NIKA-097] Workflow cancelled: {phase}")]
119 Cancelled { phase: String },
120
121 #[error("[NIKA-098] Task panicked: {reason}")]
122 Panicked { reason: String },
123}
124
125impl From<ExecutionError> for NikaError {
126 fn from(e: ExecutionError) -> Self {
127 match e {
128 ExecutionError::ExecFailed { reason } => NikaError::ExecError { reason },
129 ExecutionError::FetchFailed { reason } => NikaError::FetchError { reason },
130 ExecutionError::ExtractFailed { reason } => NikaError::ExtractError { reason },
131 ExecutionError::General(msg) => NikaError::Execution(msg),
132 ExecutionError::Cancelled { phase } => NikaError::WorkflowCancelled { phase },
133 ExecutionError::Panicked { reason } => NikaError::TaskPanicked { reason },
134 }
135 }
136}
137
138#[derive(Debug, thiserror::Error)]
144pub enum BindingError {
145 #[error("[NIKA-041] Template error in '{template}': {reason}")]
146 TemplateError { template: String, reason: String },
147
148 #[error("[NIKA-042] Binding '{alias}' not found")]
149 NotFound { alias: String },
150
151 #[error("[NIKA-043] Binding type mismatch at '{path}': expected {expected}, got {actual}")]
152 TypeMismatch {
153 path: String,
154 expected: String,
155 actual: String,
156 },
157}
158
159impl From<BindingError> for NikaError {
160 fn from(e: BindingError) -> Self {
161 match e {
162 BindingError::TemplateError { template, reason } => {
163 NikaError::TemplateError { template, reason }
164 }
165 BindingError::NotFound { alias } => NikaError::BindingNotFound { alias },
166 BindingError::TypeMismatch {
167 path,
168 expected,
169 actual,
170 } => NikaError::BindingTypeMismatch {
171 path,
172 expected,
173 actual,
174 },
175 }
176 }
177}
178
179#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn provider_error_converts_to_nika_error() {
189 let err: NikaError = ProviderError::NotConfigured {
190 provider: "anthropic".into(),
191 }
192 .into();
193 assert!(err.to_string().contains("NIKA-030"));
194 assert!(err.to_string().contains("anthropic"));
195 }
196
197 #[test]
198 fn dag_error_converts_to_nika_error() {
199 let err: NikaError = DagError::CycleDetected {
200 cycle: "a → b → a".into(),
201 }
202 .into();
203 assert!(err.to_string().contains("NIKA-020"));
204 }
205
206 #[test]
207 fn execution_error_converts_to_nika_error() {
208 let err: NikaError = ExecutionError::General("test error".into()).into();
209 assert!(err.to_string().contains("NIKA-096"));
210 }
211
212 #[test]
213 fn binding_error_converts_to_nika_error() {
214 let err: NikaError = BindingError::NotFound {
215 alias: "data".into(),
216 }
217 .into();
218 assert!(err.to_string().contains("NIKA-042"));
219 }
220
221 #[test]
222 fn domain_errors_are_send_sync() {
223 fn assert_send_sync<T: Send + Sync>() {}
224 assert_send_sync::<ProviderError>();
225 assert_send_sync::<DagError>();
226 assert_send_sync::<ExecutionError>();
227 assert_send_sync::<BindingError>();
228 }
229}