bhtune_db/error.rs
1//! `bhtune-db`'s error type.
2
3/// Everything that can go wrong opening the database, running migrations, or executing a
4/// query.
5#[derive(Debug, thiserror::Error)]
6pub enum DbError {
7 #[error("failed to open or connect to the database: {0}")]
8 Connect(#[source] sqlx::Error),
9
10 #[error("failed to run database migrations: {0}")]
11 Migrate(#[source] sqlx::migrate::MigrateError),
12
13 /// The database records a migration history entry that is not part of the embedded set
14 /// or has a different checksum. Preserve the old database and use a new database file
15 /// rather than applying the embedded schema over its migration history.
16 #[error(
17 "database at {} has an incompatible migration history; back it up and configure a new database file before using this version",
18 .0.display()
19 )]
20 IncompatibleMigrationHistory(std::path::PathBuf),
21
22 #[error("database query failed: {0}")]
23 Query(#[source] sqlx::Error),
24
25 /// A value read back from a TEXT enum column didn't match any known
26 /// variant. This can only happen if the row was written by something
27 /// other than this crate (or a future version wrote a variant this
28 /// version doesn't know about) — the `CHECK` constraints on every
29 /// enum-shaped column prevent the database itself from ever storing an
30 /// invalid value.
31 #[error("column {column:?} held an unrecognized value: {value:?}")]
32 InvalidEnumValue { column: &'static str, value: String },
33
34 #[error("invalid template PID precision: {0}")]
35 InvalidPidRounding(#[source] bhtune_core::PidRoundingError),
36
37 /// An MV-actuation finalization API was given [`crate::models::MvActuationStatus::Pending`].
38 /// Pending is the initial state inserted by
39 /// [`crate::models::TuneMvActuationRow::insert_pending`], not a terminal result.
40 #[error("pending is not a terminal MV actuation status")]
41 InvalidMvActuationFinalStatus,
42
43 /// A live controller resource is already conditionally claimed by another operation.
44 #[error("the live controller resource is already claimed by another operation")]
45 LiveResourceClaimed,
46
47 /// A process attempted to heartbeat or release a lease it no longer owns.
48 #[error("live controller ownership was lost before the operation completed")]
49 LiveOwnershipLost,
50
51 /// The SQLite connection is in-memory or does not expose a single stable filesystem
52 /// path, so process-shared live-mutation ownership cannot be established.
53 #[error("database has no stable filesystem path for live-operation ownership")]
54 DatabasePathUnavailable,
55
56 /// A JSON column (`tune_runs.template_snapshot_json`/`tags_json`/
57 /// `timing_metrics_json`, `dcs_templates.versions_json`) held syntactically valid JSON
58 /// -- the schema's `CHECK (json_valid(...))` already guarantees that much -- but it
59 /// didn't deserialize into the Rust shape the column is supposed to hold. This can
60 /// happen honestly, not just from external tampering: an older snapshot can predate a
61 /// field a later release added to its typed representation.
62 #[error("column {column:?} held JSON that didn't match the expected shape: {source}")]
63 InvalidJsonShape {
64 column: &'static str,
65 #[source]
66 source: serde_json::Error,
67 },
68
69 /// [`crate::models::DcsTemplateRow::delete`] targeted a template that at least one
70 /// `loops` row still references. The schema's `ON DELETE RESTRICT` foreign key is what
71 /// actually enforces this; this variant exists so the `bhtune` CLI's `template delete` can
72 /// turn the resulting SQLite foreign-key-violation error into a message naming the
73 /// template rather than a raw SQL error, without needing its own `sqlx` dependency just
74 /// to inspect the error kind.
75 #[error("template {id} is still referenced by one or more saved loops and cannot be deleted")]
76 TemplateInUse { id: i64 },
77
78 /// [`crate::backup::backup_to`]'s destination already exists. Refused rather than
79 /// silently overwritten — clobbering a previous backup because of a reused filename
80 /// would itself be a data-loss bug.
81 #[error("backup destination already exists: {}", .0.display())]
82 BackupDestinationExists(std::path::PathBuf),
83
84 /// [`crate::backup::restore_from`] (or [`crate::backup::backup_to`]'s own post-write
85 /// check) determined a file is not a usable bhtune backup: it failed
86 /// `PRAGMA integrity_check`, or it doesn't contain a `tune_runs` table.
87 #[error("not a valid bhtune backup file: {0}")]
88 InvalidBackup(String),
89
90 /// A filesystem operation during [`crate::backup::backup_to`]/
91 /// [`crate::backup::restore_from`] (copying, renaming, or removing a file) failed.
92 #[error("backup/restore file operation failed: {0}")]
93 Io(#[source] std::io::Error),
94
95 /// [`crate::backup::restore_from`] found another connection (in this process or
96 /// another) still attached to the live database. Restoring while that's true risks
97 /// desynchronizing whatever holds it: its view of the file would silently stop matching
98 /// what's on disk the moment the restore replaces it.
99 #[error(
100 "database at {} appears to be in use by another connection or process -- close it before restoring",
101 .0.display()
102 )]
103 DatabaseInUse(std::path::PathBuf),
104
105 /// A value that should have been JSON-representable could not be serialized. This is a
106 /// persistence failure, not a reason to panic while a live loop may already be in manual.
107 #[error("failed to serialize {context}: {source}")]
108 Serialize {
109 context: &'static str,
110 #[source]
111 source: Box<dyn std::error::Error + Send + Sync>,
112 },
113}
114
115pub type DbResult<T> = Result<T, DbError>;