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(
64 since = "0.3.4",
65 note = "Never produced — checksum mismatches surface as ValidationFailed. Will be removed in 0.4.0."
66 )]
67 #[error("Checksum mismatch for migration {script}: expected {expected}, found {found}")]
68 ChecksumMismatch {
69 script: String,
70 expected: i32,
71 found: i32,
72 },
73
74 #[error("Validation failed:\n{0}")]
76 ValidationFailed(String),
77
78 #[error("Migration failed for {script}: {reason}")]
80 MigrationFailed { script: String, reason: String },
81
82 #[error("Failed to acquire advisory lock: {0}")]
84 LockError(String),
85
86 #[error(
88 "Clean is disabled. Pass --allow-clean to enable it or set clean_enabled = true in config."
89 )]
90 CleanDisabled,
91
92 #[error("Baseline already exists. The schema history table is not empty.")]
94 BaselineExists,
95
96 #[error("IO error: {0}")]
98 IoError(#[from] std::io::Error),
99
100 #[error("Out-of-order migration not allowed: version {version} is below the highest applied version {highest}. Enable out_of_order to allow this.")]
102 OutOfOrder { version: String, highest: String },
103
104 #[error("Placeholder '{key}' not found. Available placeholders: {available}")]
106 PlaceholderNotFound { key: String, available: String },
107
108 #[error("Hook failed during {phase} ({script}): {reason}")]
110 HookFailed {
111 phase: String,
112 script: String,
113 reason: String,
114 },
115
116 #[error("Self-update failed: {0}")]
118 UpdateError(String),
119
120 #[error("Undo failed for {script}: {reason}")]
122 UndoFailed { script: String, reason: String },
123
124 #[error("No undo migration found for version {version}. Expected U{version}__*.sql file.")]
126 UndoMissing { version: String },
127
128 #[error("Lint found {error_count} error(s): {details}")]
130 LintFailed { error_count: usize, details: String },
131
132 #[deprecated(
137 since = "0.3.4",
138 note = "Never produced — diff failures surface as ConfigError or DatabaseError. Will be removed in 0.4.0."
139 )]
140 #[error("Diff failed: {reason}")]
141 DiffFailed { reason: String },
142
143 #[error("Schema drift detected: {count} difference(s): {details}")]
145 DriftDetected { count: usize, details: String },
146
147 #[error("Snapshot error: {reason}")]
149 SnapshotError { reason: String },
150
151 #[error("Migration dependency cycle detected: {path}")]
153 DependencyCycle { path: String },
154
155 #[error("Migration V{version} depends on V{dependency}, which does not exist")]
157 MissingDependency { version: String, dependency: String },
158
159 #[error("Invalid directive in {script}: {reason}")]
161 InvalidDirective { script: String, reason: String },
162
163 #[error("Git error: {0}")]
165 GitError(String),
166
167 #[error("Migration conflicts detected: {count} conflict(s): {details}")]
169 ConflictsDetected { count: usize, details: String },
170
171 #[error("Database '{name}' not found. Available: {available}")]
173 DatabaseNotFound { name: String, available: String },
174
175 #[error("Multi-database dependency cycle: {path}")]
177 MultiDbDependencyCycle { path: String },
178
179 #[error("Multi-database error for '{name}': {reason}")]
181 MultiDbError { name: String, reason: String },
182
183 #[error("Pre-flight checks failed: {checks}")]
185 PreflightFailed { checks: String },
186
187 #[error("Guard {kind} failed for {script}: {expression}")]
189 GuardFailed {
190 kind: String,
191 script: String,
192 expression: String,
193 },
194
195 #[error("Migration blocked for {script}: {reason}. Use --force to override.")]
197 MigrationBlocked { script: String, reason: String },
198
199 #[error("Advisor error: {0}")]
201 AdvisorError(String),
202
203 #[error("Simulation failed: {reason}")]
205 SimulationFailed { reason: String },
206
207 #[error("Migration {script} contains non-transactional statement: {statement}. Remove --transaction or rewrite the migration.")]
209 NonTransactionalStatement { script: String, statement: String },
210
211 #[error("Connection lost during {operation}: {detail}")]
213 ConnectionLost { operation: String, detail: String },
214}
215
216pub type Result<T> = std::result::Result<T, WaypointError>;