1use thiserror::Error;
7use uuid::Uuid;
8
9use crate::entities::{ConcurrencyLimitError, RunStatus, WorkerTagError};
10
11#[derive(Debug, Error)]
23pub enum StoreError {
24 #[error("run not found: {0}")]
26 RunNotFound(Uuid),
27
28 #[error("step not found: {0}")]
30 StepNotFound(Uuid),
31
32 #[error("invalid status transition: {from} -> {to}")]
34 InvalidTransition {
35 from: RunStatus,
37 to: RunStatus,
39 },
40
41 #[error("email already exists: {0}")]
43 DuplicateEmail(String),
44
45 #[error("username already exists: {0}")]
47 DuplicateUsername(String),
48
49 #[error("user not found: {0}")]
51 UserNotFound(Uuid),
52
53 #[error("lease lost on run {run_id}")]
58 LeaseLost {
59 run_id: Uuid,
61 held_by: Option<String>,
63 },
64
65 #[error("artifact {name:?} already exists on step {step_id}")]
67 DuplicateArtifact {
68 step_id: Uuid,
70 name: String,
72 },
73
74 #[error("concurrency key {key:?} is held by active run {run_id}")]
76 ConcurrencyConflict {
77 key: String,
79 run_id: Uuid,
81 },
82
83 #[error("invalid concurrency limit: {0}")]
85 InvalidConcurrencyLimit(#[from] ConcurrencyLimitError),
86
87 #[error("invalid worker tag: {0}")]
89 InvalidWorkerTag(#[from] WorkerTagError),
90
91 #[error("schedule not found: {0}")]
93 ScheduleNotFound(Uuid),
94
95 #[error("approval delegation not found: {0}")]
97 DelegationNotFound(Uuid),
98
99 #[error("provider account not found: {0}")]
101 ProviderAccountNotFound(Uuid),
102
103 #[error("provider account already exists: {0}")]
105 DuplicateProviderAccount(String),
106
107 #[error("agent role '{0}' not found")]
109 AgentRoleNotFound(String),
110
111 #[error("database error: {0}")]
113 Database(String),
114
115 #[error("serialization error: {0}")]
117 Serialization(#[from] serde_json::Error),
118
119 #[error("crypto error: {0}")]
121 Crypto(String),
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn run_not_found_display() {
130 let id = Uuid::nil();
131 let err = StoreError::RunNotFound(id);
132 assert_eq!(err.to_string(), format!("run not found: {id}"));
133 }
134
135 #[test]
136 fn agent_role_not_found_display() {
137 let err = StoreError::AgentRoleNotFound("reviewer".to_string());
138 assert_eq!(err.to_string(), "agent role 'reviewer' not found");
139 }
140
141 #[test]
142 fn step_not_found_display() {
143 let id = Uuid::nil();
144 let err = StoreError::StepNotFound(id);
145 assert_eq!(err.to_string(), format!("step not found: {id}"));
146 }
147
148 #[test]
149 fn invalid_transition_display() {
150 let err = StoreError::InvalidTransition {
151 from: RunStatus::Pending,
152 to: RunStatus::Completed,
153 };
154 assert!(err.to_string().contains("Pending"));
155 assert!(err.to_string().contains("Completed"));
156 }
157
158 #[test]
159 fn concurrency_conflict_display() {
160 let run_id = Uuid::now_v7();
161 let err = StoreError::ConcurrencyConflict {
162 key: "issue:12".to_string(),
163 run_id,
164 };
165 let message = err.to_string();
166 assert!(message.contains("issue:12"));
167 assert!(message.contains(&run_id.to_string()));
168 }
169
170 #[test]
171 fn invalid_concurrency_limit_display() {
172 let err = StoreError::from(ConcurrencyLimitError::EmptyGroup);
173 assert_eq!(
174 err.to_string(),
175 "invalid concurrency limit: concurrency group must not be empty"
176 );
177 }
178
179 #[test]
180 fn invalid_worker_tag_display() {
181 let err = StoreError::from(WorkerTagError::Empty);
182 assert_eq!(
183 err.to_string(),
184 "invalid worker tag: worker tag must not be empty"
185 );
186 }
187
188 #[test]
189 fn delegation_not_found_display() {
190 let id = Uuid::nil();
191 let err = StoreError::DelegationNotFound(id);
192 assert_eq!(
193 err.to_string(),
194 format!("approval delegation not found: {id}")
195 );
196 }
197
198 #[test]
199 fn database_error_display() {
200 let err = StoreError::Database("connection refused".to_string());
201 assert!(err.to_string().contains("connection refused"));
202 }
203
204 #[test]
205 fn serialization_error_from_serde() {
206 let serde_err = serde_json::from_str::<String>("not json").unwrap_err();
207 let err = StoreError::from(serde_err);
208 assert!(err.to_string().contains("serialization error"));
209 }
210}