use std::fmt;
use crate::SchemaVersion;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
#[non_exhaustive]
pub enum Error {
RusqliteError {
query: String,
err: rusqlite::Error,
},
SpecifiedSchemaVersion(SchemaVersionError),
InvalidUserVersion,
MigrationDefinition(MigrationDefinitionError),
ForeignKeyCheck(Vec<ForeignKeyCheckError>),
Hook(String),
FileLoad(String),
Unrecognized(Box<dyn std::error::Error + Send + Sync + 'static>),
}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::RusqliteError { query: q1, err: e1 },
Self::RusqliteError { query: q2, err: e2 },
) => q1 == q2 && e1 == e2,
(Self::SpecifiedSchemaVersion(a), Self::SpecifiedSchemaVersion(b)) => a == b,
(Self::MigrationDefinition(a), Self::MigrationDefinition(b)) => a == b,
(Self::ForeignKeyCheck(e1), Self::ForeignKeyCheck(e2)) => e1 == e2,
(Self::Hook(a), Self::Hook(b)) | (Self::FileLoad(a), Self::FileLoad(b)) => a == b,
(Self::Unrecognized(_), Self::Unrecognized(_)) => false,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
impl Error {
#[must_use]
pub fn with_sql(e: rusqlite::Error, sql: &str) -> Error {
Error::RusqliteError {
query: String::from(sql),
err: e,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::RusqliteError { query, err: e } => write!(
f,
"rusqlite_migration error while executing query '{query}': {e}"
),
Error::SpecifiedSchemaVersion(e) => {
write!(f, "error with the specified schema version: {e}")
}
Error::MigrationDefinition(e) => {
write!(f, "rusqlite_migration error in migrations definition: {e}")
}
Error::ForeignKeyCheck(vec) => {
writeln!(f, "rusqlite_migration error on foreign key check:")?;
for row in vec {
let ForeignKeyCheckError {
table,
rowid,
parent,
fkid,
} = row;
writeln!(f, " - row with rowid {rowid} in table '{table}' references non-existing row table '{parent}', using foreign key value {fkid}")?
}
Ok(())
}
Error::Unrecognized(ref e) => write!(
f,
"rusqlite_migration unknown error (the library might be out of date): {e}"
),
Error::Hook(e) => write!(f, "rusqlite_migration error in migration hook: {e}"),
Error::FileLoad(e) => write!(
f,
"rusqlite error while loading migrations from directory: {e}"
),
Error::InvalidUserVersion => {
write!(f, "rusqlite_migration error: invalid user version received")
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::RusqliteError { query: _, err } => Some(err),
Error::SpecifiedSchemaVersion(e) => Some(e),
Error::MigrationDefinition(e) => Some(e),
Error::ForeignKeyCheck(vec) => Some(vec.first()?),
Error::Unrecognized(ref e) => Some(&**e),
Error::Hook(_) | Error::FileLoad(_) | Error::InvalidUserVersion => None,
}
}
}
impl From<rusqlite::Error> for Error {
fn from(e: rusqlite::Error) -> Error {
Error::RusqliteError {
query: String::new(),
err: e,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[allow(clippy::enum_variant_names)]
#[non_exhaustive]
pub enum SchemaVersionError {
TargetVersionOutOfRange {
specified: SchemaVersion,
highest: SchemaVersion,
},
TooHigh,
}
impl fmt::Display for SchemaVersionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SchemaVersionError::TargetVersionOutOfRange { specified, highest } => {
write!(f, "Attempt to migrate to version {specified}, which is higher than the highest version currently supported, {highest}.")
}
SchemaVersionError::TooHigh => {
write!(f, "Attempt to use a schema version higher than supported.")
}
}
}
}
impl std::error::Error for SchemaVersionError {}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[allow(clippy::enum_variant_names)]
#[non_exhaustive]
pub enum MigrationDefinitionError {
DownNotDefined {
migration_index: usize,
},
NoMigrationsDefined,
DatabaseTooFarAhead,
}
impl fmt::Display for MigrationDefinitionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MigrationDefinitionError::DownNotDefined { migration_index } => {
write!(
f,
"Migration {} (version {} -> {}) cannot be reverted",
migration_index,
migration_index,
migration_index + 1
)
}
MigrationDefinitionError::NoMigrationsDefined => {
write!(f, "Attempt to migrate with no migrations defined")
}
MigrationDefinitionError::DatabaseTooFarAhead => {
write!(
f,
"Attempt to migrate a database with a migration number that is too high"
)
}
}
}
}
impl std::error::Error for MigrationDefinitionError {}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ForeignKeyCheckError {
pub(super) table: String,
pub(super) rowid: i64,
pub(super) parent: String,
pub(super) fkid: i64,
}
impl fmt::Display for ForeignKeyCheckError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Foreign key check found row with id {} in table '{}' missing from table '{}' \
but required by foreign key with id {}",
self.rowid, self.table, self.parent, self.fkid
)
}
}
impl std::error::Error for ForeignKeyCheckError {}
#[derive(Debug, PartialEq)]
#[allow(clippy::enum_variant_names)]
#[non_exhaustive]
pub enum HookError {
RusqliteError(rusqlite::Error),
Hook(String),
}
impl From<rusqlite::Error> for HookError {
fn from(e: rusqlite::Error) -> HookError {
HookError::RusqliteError(e)
}
}
impl From<HookError> for Error {
fn from(e: HookError) -> Error {
match e {
HookError::RusqliteError(err) => Error::with_sql(err, ""),
HookError::Hook(s) => Error::Hook(s),
}
}
}
pub type HookResult<E = HookError> = std::result::Result<(), E>;
#[cfg(test)]
mod tests;