1use thiserror::Error;
4
5#[cfg(feature = "postgres")]
8pub fn format_db_error(e: &tokio_postgres::Error) -> String {
9 if let Some(db_err) = e.as_db_error() {
11 let mut msg = db_err.message().to_string();
12 if let Some(detail) = db_err.detail() {
13 msg.push_str(&format!("\n Detail: {}", detail));
14 }
15 if let Some(hint) = db_err.hint() {
16 msg.push_str(&format!("\n Hint: {}", hint));
17 }
18 if let Some(position) = db_err.position() {
19 msg.push_str(&format!("\n Position: {:?}", position));
20 }
21 return msg;
22 }
23 let mut msg = e.to_string();
25 let mut source = std::error::Error::source(e);
26 while let Some(s) = source {
27 msg.push_str(&format!(": {}", s));
28 source = s.source();
29 }
30 if e.is_closed() {
32 msg.push_str("\n Note: The database connection was closed unexpectedly. This may indicate a network issue or server restart.");
33 }
34 msg
35}
36
37#[derive(Error, Debug)]
39pub enum WaypointError {
40 #[error("Configuration error: {0}")]
42 ConfigError(String),
43
44 #[cfg(feature = "postgres")]
46 #[error("Database error: {}", format_db_error(.0))]
47 DatabaseError(#[from] tokio_postgres::Error),
48
49 #[cfg(feature = "mysql")]
51 #[error("Database error: {0}")]
52 MysqlError(#[from] mysql_async::Error),
53
54 #[error("Migration parse error: {0}")]
56 MigrationParseError(String),
57
58 #[deprecated(
65 since = "0.3.4",
66 note = "Never produced — checksum mismatches surface as ValidationFailed. Will be removed in 1.0."
67 )]
68 #[error("Checksum mismatch for migration {script}: expected {expected}, found {found}")]
69 ChecksumMismatch {
70 script: String,
71 expected: i32,
72 found: i32,
73 },
74
75 #[error("Validation failed:\n{0}")]
77 ValidationFailed(String),
78
79 #[error("Migration failed for {script}: {reason}")]
81 MigrationFailed { script: String, reason: String },
82
83 #[error("Failed to acquire advisory lock: {0}")]
85 LockError(String),
86
87 #[error(
89 "Clean is disabled. Pass --allow-clean to enable it or set clean_enabled = true in config."
90 )]
91 CleanDisabled,
92
93 #[error("Baseline already exists. The schema history table is not empty.")]
95 BaselineExists,
96
97 #[error("IO error: {0}")]
99 IoError(#[from] std::io::Error),
100
101 #[error(
103 "Out-of-order migration not allowed: version {version} is below the highest applied version {highest}. Enable out_of_order to allow this."
104 )]
105 OutOfOrder { version: String, highest: String },
106
107 #[error("Placeholder '{key}' not found. Available placeholders: {available}")]
109 PlaceholderNotFound { key: String, available: String },
110
111 #[error("Hook failed during {phase} ({script}): {reason}")]
113 HookFailed {
114 phase: String,
115 script: String,
116 reason: String,
117 },
118
119 #[error("Self-update failed: {0}")]
121 UpdateError(String),
122
123 #[error("Undo failed for {script}: {reason}")]
125 UndoFailed { script: String, reason: String },
126
127 #[error("No undo migration found for version {version}. Expected U{version}__*.sql file.")]
129 UndoMissing { version: String },
130
131 #[error("Lint found {error_count} error(s): {details}")]
133 LintFailed { error_count: usize, details: String },
134
135 #[deprecated(
141 since = "0.3.4",
142 note = "Never produced — diff failures surface as ConfigError or DatabaseError. Will be removed in 1.0."
143 )]
144 #[error("Diff failed: {reason}")]
145 DiffFailed { reason: String },
146
147 #[error("Schema drift detected: {count} difference(s): {details}")]
149 DriftDetected { count: usize, details: String },
150
151 #[error("Snapshot error: {reason}")]
153 SnapshotError { reason: String },
154
155 #[error("Migration dependency cycle detected: {path}")]
157 DependencyCycle { path: String },
158
159 #[error("Migration V{version} depends on V{dependency}, which does not exist")]
161 MissingDependency { version: String, dependency: String },
162
163 #[error("Invalid directive in {script}: {reason}")]
165 InvalidDirective { script: String, reason: String },
166
167 #[error("Git error: {0}")]
169 GitError(String),
170
171 #[error("Migration conflicts detected: {count} conflict(s): {details}")]
173 ConflictsDetected { count: usize, details: String },
174
175 #[error("Database '{name}' not found. Available: {available}")]
177 DatabaseNotFound { name: String, available: String },
178
179 #[error("Multi-database dependency cycle: {path}")]
181 MultiDbDependencyCycle { path: String },
182
183 #[error("Multi-database error for '{name}': {reason}")]
185 MultiDbError { name: String, reason: String },
186
187 #[error("Pre-flight checks failed: {checks}")]
189 PreflightFailed { checks: String },
190
191 #[error("Guard {kind} failed for {script}: {expression}")]
193 GuardFailed {
194 kind: String,
195 script: String,
196 expression: String,
197 },
198
199 #[error("Migration blocked for {script}: {reason}. Use --force to override.")]
201 MigrationBlocked { script: String, reason: String },
202
203 #[error("Advisor error: {0}")]
205 AdvisorError(String),
206
207 #[error("Simulation failed: {reason}")]
209 SimulationFailed { reason: String },
210
211 #[error(
213 "Migration {script} contains non-transactional statement: {statement}. Remove --transaction or rewrite the migration."
214 )]
215 NonTransactionalStatement { script: String, statement: String },
216
217 #[error("Connection lost during {operation}: {detail}")]
219 ConnectionLost { operation: String, detail: String },
220}
221
222pub type Result<T> = std::result::Result<T, WaypointError>;