Skip to main content

tern_core/
error.rs

1//! Error type for migration operations.
2use crate::migration::{Migration, MigrationId};
3use crate::runner::{MigrationResult, Report};
4
5use std::error::Error as StdError;
6
7/// Alias for a result whose error type is [`Error`].
8pub type TernResult<T> = Result<T, Error>;
9type BoxDynError = Box<dyn StdError + Send + Sync + 'static>;
10
11/// All the ways the lifecycle of applying migrations
12/// can end in failure.
13#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum Error {
16    /// An error that came from applying migrations.
17    #[error("error applying migrations {0}")]
18    Execute(#[source] BoxDynError),
19    /// Error from one migration.
20    #[error("error applying migration: {{name: {1}, no_tx: {2}}}: {0}")]
21    ExecuteMigration(#[source] BoxDynError, MigrationId, bool),
22    /// An error resolving the query before applying.
23    /// Can be used as a fallthrough to map arbitrary error types to when
24    /// implementing `QueryBuilder`.
25    #[error("runtime could not resolve query: {0}")]
26    ResolveQuery(String),
27    /// Error processing a migration source.
28    #[error("could not parse migration query: {0}")]
29    Sql(#[from] std::fmt::Error),
30    /// Error parsing a SQL source into statements.
31    #[error("error splitting statement {1}: {0}")]
32    Split(std::io::Error, usize),
33    /// Local migration source has fewer migrations than the history table.
34    #[error(
35        "missing source: {local} migrations found but {history} have been applied: {msg}"
36    )]
37    MissingSource { local: i64, history: i64, msg: String },
38    /// The source migrations and the history are not synchronized in a way that
39    /// is expected.
40    #[error("inconsistent source: {msg}: {at_issue:?}")]
41    OutOfSync { at_issue: Vec<MigrationId>, msg: String },
42    /// The options passed are not valid.
43    #[error("invalid parameter for the operation requested: {0}")]
44    Invalid(String),
45    /// An error occurred, resulting in a partial migration run.
46    #[error("migration could not complete: {source}, partial report: {report}")]
47    Partial { source: BoxDynError, report: Report },
48}
49
50impl Error {
51    pub fn to_resolve_query_error<E>(e: E) -> Self
52    where
53        E: std::fmt::Display,
54    {
55        Self::ResolveQuery(e.to_string())
56    }
57
58    pub(crate) fn split_err(idx: usize) -> impl FnMut(std::io::Error) -> Self {
59        move |e| Self::Split(e, idx)
60    }
61}
62
63/// Converting a result with a generic `std::error::Error` to one with this
64/// crate's error type.
65///
66/// The `*_migration_result` methods allow attaching a migration to the error,
67/// such as the one being handled when the error occurred.  The `with_report`
68/// method allows attaching a slice of `MigrationResult` to the error to show
69/// what collection of the migration set did succeed in being applied before the
70/// error was encountered.
71pub trait DatabaseError<T, E> {
72    /// Convert `E` to an [`Error`].
73    fn tern_result(self) -> TernResult<T>;
74
75    /// Same as `tern_result` but discard the returned value.
76    fn void_tern_result(self) -> TernResult<()>;
77
78    /// Convert `E` to an [`Error`] that has a given migration in the error
79    /// type's source.
80    fn tern_migration_result<M: Migration + ?Sized>(
81        self,
82        migration: &M,
83    ) -> TernResult<T>;
84
85    /// Same as `tern_migration_result` but discard the returned value.
86    fn void_tern_migration_result<M: Migration + ?Sized>(
87        self,
88        migration: &M,
89    ) -> TernResult<()>;
90
91    /// Attach an array of `MigrationResult`, representing a partially successful
92    /// migration operation, to the error.
93    fn with_report(self, report: &[MigrationResult]) -> TernResult<T>;
94}
95
96impl<T, E> DatabaseError<T, E> for Result<T, E>
97where
98    E: StdError + Send + Sync + 'static,
99{
100    fn void_tern_result(self) -> TernResult<()> {
101        match self {
102            Err(e) => Err(Error::Execute(Box::new(e))),
103            _ => Ok(()),
104        }
105    }
106
107    fn void_tern_migration_result<M: Migration + ?Sized>(
108        self,
109        migration: &M,
110    ) -> TernResult<()> {
111        match self {
112            Err(e) => Err(Error::ExecuteMigration(
113                Box::new(e),
114                migration.migration_id(),
115                migration.no_tx(),
116            )),
117            _ => Ok(()),
118        }
119    }
120
121    fn tern_result(self) -> TernResult<T> {
122        match self {
123            Ok(v) => Ok(v),
124            Err(e) => Err(Error::Execute(Box::new(e))),
125        }
126    }
127
128    fn tern_migration_result<M: Migration + ?Sized>(
129        self,
130        migration: &M,
131    ) -> TernResult<T> {
132        match self {
133            Ok(v) => Ok(v),
134            Err(e) => Err(Error::ExecuteMigration(
135                Box::new(e),
136                migration.migration_id(),
137                migration.no_tx(),
138            )),
139        }
140    }
141
142    fn with_report(self, migrations: &[MigrationResult]) -> TernResult<T> {
143        match self {
144            Ok(v) => Ok(v),
145            Err(e) => Err(Error::Partial {
146                source: Box::new(e),
147                report: Report::new(migrations.to_vec()),
148            }),
149        }
150    }
151}