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,
},
#[cfg(feature = "alpha-async-tokio-rusqlite")]
ConnectionClosed,
SpecifiedSchemaVersion(SchemaVersionError),
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)) => a == 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 {
write!(f, "rusqlite_migrate error: {self:?}")
}
}
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::Hook(_) | Error::FileLoad(_) => None,
#[cfg(feature = "alpha-async-tokio-rusqlite")]
Error::ConnectionClosed => None,
Error::Unrecognized(ref e) => Some(&**e),
}
}
}
impl From<rusqlite::Error> for Error {
fn from(e: rusqlite::Error) -> Error {
Error::RusqliteError {
query: String::new(),
err: e,
}
}
}
#[cfg(feature = "alpha-async-tokio-rusqlite")]
impl From<tokio_rusqlite::Error> for Error {
fn from(e: tokio_rusqlite::Error) -> Self {
match e {
tokio_rusqlite::Error::ConnectionClosed => Error::ConnectionClosed,
tokio_rusqlite::Error::Rusqlite(e) | tokio_rusqlite::Error::Close((_, e)) => {
Error::RusqliteError {
err: e,
query: Default::default(),
}
}
e => Error::Unrecognized(Box::new(e)),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[allow(clippy::enum_variant_names)]
#[non_exhaustive]
pub enum SchemaVersionError {
TargetVersionOutOfRange {
specified: SchemaVersion,
highest: SchemaVersion,
},
}
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}.")
}
}
}
}
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;