1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! Error types for Waypoint operations.
use thiserror::Error;
/// Extract the full error message from a tokio_postgres::Error,
/// including the underlying DbError details that Display hides.
#[cfg(feature = "postgres")]
pub fn format_db_error(e: &tokio_postgres::Error) -> String {
// The source chain contains the actual DbError with message/detail/hint
if let Some(db_err) = e.as_db_error() {
let mut msg = db_err.message().to_string();
if let Some(detail) = db_err.detail() {
msg.push_str(&format!("\n Detail: {}", detail));
}
if let Some(hint) = db_err.hint() {
msg.push_str(&format!("\n Hint: {}", hint));
}
if let Some(position) = db_err.position() {
msg.push_str(&format!("\n Position: {:?}", position));
}
return msg;
}
// Fallback: walk the source chain
let mut msg = e.to_string();
let mut source = std::error::Error::source(e);
while let Some(s) = source {
msg.push_str(&format!(": {}", s));
source = s.source();
}
// Append connection-loss context when the connection is closed
if e.is_closed() {
msg.push_str("\n Note: The database connection was closed unexpectedly. This may indicate a network issue or server restart.");
}
msg
}
/// All error types that Waypoint operations can produce.
#[derive(Error, Debug)]
pub enum WaypointError {
/// Invalid or missing configuration (TOML parse errors, missing required fields, etc.).
#[error("Configuration error: {0}")]
ConfigError(String),
/// A database query or connection operation failed (PostgreSQL).
#[cfg(feature = "postgres")]
#[error("Database error: {}", format_db_error(.0))]
DatabaseError(#[from] tokio_postgres::Error),
/// A database query or connection operation failed (MySQL).
#[cfg(feature = "mysql")]
#[error("Database error: {0}")]
MysqlError(#[from] mysql_async::Error),
/// A migration filename could not be parsed into a valid migration.
#[error("Migration parse error: {0}")]
MigrationParseError(String),
/// **Reserved / unused.** No code path currently constructs this variant —
/// checksum mismatches surface as `ValidationFailed(String)` from the
/// `validate` command (which aggregates one or more mismatches into a
/// single human-readable string). Kept for back-compat with external
/// matchers; scheduled for removal in 0.4.0.
#[deprecated(
since = "0.3.4",
note = "Never produced — checksum mismatches surface as ValidationFailed. Will be removed in 0.4.0."
)]
#[error("Checksum mismatch for migration {script}: expected {expected}, found {found}")]
ChecksumMismatch {
script: String,
expected: i32,
found: i32,
},
/// One or more validation checks failed before migration could proceed.
#[error("Validation failed:\n{0}")]
ValidationFailed(String),
/// A migration script failed to execute against the database.
#[error("Migration failed for {script}: {reason}")]
MigrationFailed { script: String, reason: String },
/// Could not acquire the PostgreSQL advisory lock used to prevent concurrent migrations.
#[error("Failed to acquire advisory lock: {0}")]
LockError(String),
/// The `clean` command was invoked but clean is not enabled in the configuration.
#[error(
"Clean is disabled. Pass --allow-clean to enable it or set clean_enabled = true in config."
)]
CleanDisabled,
/// A baseline was requested but the schema history table already contains entries.
#[error("Baseline already exists. The schema history table is not empty.")]
BaselineExists,
/// A filesystem I/O operation failed (reading migration files, config, etc.).
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
/// A migration version is lower than the highest applied version and out-of-order is disabled.
#[error("Out-of-order migration not allowed: version {version} is below the highest applied version {highest}. Enable out_of_order to allow this.")]
OutOfOrder { version: String, highest: String },
/// A `${key}` placeholder in migration SQL has no corresponding value defined.
#[error("Placeholder '{key}' not found. Available placeholders: {available}")]
PlaceholderNotFound { key: String, available: String },
/// A SQL callback hook script failed during execution.
#[error("Hook failed during {phase} ({script}): {reason}")]
HookFailed {
phase: String,
script: String,
reason: String,
},
/// The self-update mechanism encountered an error.
#[error("Self-update failed: {0}")]
UpdateError(String),
/// An undo migration script failed to execute against the database.
#[error("Undo failed for {script}: {reason}")]
UndoFailed { script: String, reason: String },
/// No undo migration file was found for the requested version.
#[error("No undo migration found for version {version}. Expected U{version}__*.sql file.")]
UndoMissing { version: String },
/// Lint analysis found one or more errors in migration SQL.
#[error("Lint found {error_count} error(s): {details}")]
LintFailed { error_count: usize, details: String },
/// **Reserved / unused.** No code path currently constructs this variant —
/// the `diff` command surfaces failures as `ConfigError(String)` or
/// `DatabaseError`. Kept for back-compat with external matchers; scheduled
/// for removal in 0.4.0.
#[deprecated(
since = "0.3.4",
note = "Never produced — diff failures surface as ConfigError or DatabaseError. Will be removed in 0.4.0."
)]
#[error("Diff failed: {reason}")]
DiffFailed { reason: String },
/// The live database schema differs from the expected snapshot.
#[error("Schema drift detected: {count} difference(s): {details}")]
DriftDetected { count: usize, details: String },
/// A schema snapshot operation (save, load, or compare) failed.
#[error("Snapshot error: {reason}")]
SnapshotError { reason: String },
/// A circular dependency was detected among migration `@depends` directives.
#[error("Migration dependency cycle detected: {path}")]
DependencyCycle { path: String },
/// A migration declares a dependency on a version that does not exist on disk.
#[error("Migration V{version} depends on V{dependency}, which does not exist")]
MissingDependency { version: String, dependency: String },
/// A migration directive comment is malformed or contains invalid values.
#[error("Invalid directive in {script}: {reason}")]
InvalidDirective { script: String, reason: String },
/// A Git operation required for branch conflict detection failed.
#[error("Git error: {0}")]
GitError(String),
/// Multiple branches introduced conflicting migration versions.
#[error("Migration conflicts detected: {count} conflict(s): {details}")]
ConflictsDetected { count: usize, details: String },
/// A named database referenced in multi-database config was not found.
#[error("Database '{name}' not found. Available: {available}")]
DatabaseNotFound { name: String, available: String },
/// A circular dependency was detected among multi-database `depends_on` declarations.
#[error("Multi-database dependency cycle: {path}")]
MultiDbDependencyCycle { path: String },
/// A multi-database migration operation failed for a specific named database.
#[error("Multi-database error for '{name}': {reason}")]
MultiDbError { name: String, reason: String },
/// One or more pre-flight safety checks failed before migration could proceed.
#[error("Pre-flight checks failed: {checks}")]
PreflightFailed { checks: String },
/// A guard precondition or postcondition check failed.
#[error("Guard {kind} failed for {script}: {expression}")]
GuardFailed {
kind: String,
script: String,
expression: String,
},
/// A migration was blocked by a DANGER safety verdict.
#[error("Migration blocked for {script}: {reason}. Use --force to override.")]
MigrationBlocked { script: String, reason: String },
/// A schema advisor analysis encountered an error.
#[error("Advisor error: {0}")]
AdvisorError(String),
/// A migration simulation failed.
#[error("Simulation failed: {reason}")]
SimulationFailed { reason: String },
/// A migration contains statements that cannot run inside a transaction (e.g. CONCURRENTLY).
#[error("Migration {script} contains non-transactional statement: {statement}. Remove --transaction or rewrite the migration.")]
NonTransactionalStatement { script: String, statement: String },
/// The database connection was lost during an operation.
#[error("Connection lost during {operation}: {detail}")]
ConnectionLost { operation: String, detail: String },
}
/// Convenience type alias for `Result<T, WaypointError>`.
pub type Result<T> = std::result::Result<T, WaypointError>;