about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2018-12-13T13·40+0100
committerVincent Ambo <mail@tazj.in>2018-12-13T13·40+0100
commit68060fea13cdc26568b1a51e1bf4326885c23d63 (patch)
tree5351252428d9a0c5f43b37fad68d17006462a1c9
parent43f71ae82fa6d68abb0486601d9477b82fb42628 (diff)
feat(postgres): Introduce chained error variants
Introduces error variants for external crate errors and internal
errors.

Additional context can be provided at sites where errors occur using a
simple `.context()` call.
-rw-r--r--finito-postgres/src/error.rs62
-rw-r--r--finito-postgres/src/lib.rs62
2 files changed, 91 insertions, 33 deletions
diff --git a/finito-postgres/src/error.rs b/finito-postgres/src/error.rs
index 0fb30a99dcd7..aacc219f0418 100644
--- a/finito-postgres/src/error.rs
+++ b/finito-postgres/src/error.rs
@@ -2,8 +2,68 @@
 //! occur while dealing with persisted state machines.
 
 use std::result;
+use std::fmt::Display;
+use uuid::Uuid;
+
+// errors to chain:
+use serde_json::Error as JsonError;
+use postgres::Error as PgError;
 
 pub type Result<T> = result::Result<T, Error>;
 
 #[derive(Debug)]
-pub enum Error { SomeError }
+pub struct Error {
+    pub kind: ErrorKind,
+    pub context: Option<String>,
+}
+
+#[derive(Debug)]
+pub enum ErrorKind {
+    /// Errors occuring during JSON serialization of FSM types.
+    Serialization(String),
+
+    /// Errors occuring during communication with the database.
+    Database(String),
+
+    /// State machine could not be found.
+    FSMNotFound(Uuid),
+
+    /// Action could not be found.
+    ActionNotFound(Uuid),
+}
+
+impl <E: Into<ErrorKind>> From<E> for Error {
+    fn from(err: E) -> Error {
+        Error {
+            kind: err.into(),
+            context: None,
+        }
+    }
+}
+
+impl From<JsonError> for ErrorKind {
+    fn from(err: JsonError) -> ErrorKind {
+        ErrorKind::Serialization(err.to_string())
+    }
+}
+
+impl From<PgError> for ErrorKind {
+    fn from(err: PgError) -> ErrorKind {
+        ErrorKind::Database(err.to_string())
+    }
+}
+
+/// Helper trait that makes it possible to supply contextual
+/// information with an error.
+pub trait ResultExt<T> {
+    fn context<C: Display>(self, ctx: C) -> Result<T>;
+}
+
+impl <T, E: Into<Error>> ResultExt<T> for result::Result<T, E> {
+    fn context<C: Display>(self, ctx: C) -> Result<T> {
+        self.map_err(|err| Error {
+            context: Some(format!("{}", ctx)),
+            .. err.into()
+        })
+    }
+}
diff --git a/finito-postgres/src/lib.rs b/finito-postgres/src/lib.rs
index 46309a04c00a..844e8f79fee3 100644
--- a/finito-postgres/src/lib.rs
+++ b/finito-postgres/src/lib.rs
@@ -17,8 +17,9 @@ extern crate uuid;
 #[cfg(test)] extern crate finito_door;
 
 mod error;
-pub use error::{Result, Error};
+pub use error::{Result, Error, ErrorKind};
 
+use error::ResultExt;
 use chrono::prelude::{DateTime, Utc};
 use finito::{FSM, FSMBackend};
 use postgres::{Connection, GenericConnection};
@@ -118,9 +119,9 @@ impl <State: 'static> FSMBackend<State> for FinitoPostgres<State> {
 
         let id = Uuid::new_v4();
         let fsm = S::FSM_NAME.to_string();
-        let state = serde_json::to_value(initial).expect("TODO");
+        let state = serde_json::to_value(initial).context("failed to serialise FSM")?;
 
-        self.conn.execute(query, &[&id, &fsm, &state]).expect("TODO");
+        self.conn.execute(query, &[&id, &fsm, &state]).context("failed to insert FSM")?;
 
         return Ok(id);
 
@@ -146,7 +147,7 @@ impl <State: 'static> FSMBackend<State> for FinitoPostgres<State> {
           S::State: From<&'a State>,
           S::Event: Serialize + DeserializeOwned,
           S::Action: Serialize + DeserializeOwned {
-        let tx = self.conn.transaction().expect("TODO");
+        let tx = self.conn.transaction().context("could not begin transaction")?;
         let state = get_machine_internal(&tx, key, true)?;
 
         // Advancing the FSM consumes the event, so it is persisted first:
@@ -163,8 +164,8 @@ impl <State: 'static> FSMBackend<State> for FinitoPostgres<State> {
         }
 
         // And finally the state is updated:
-        update_state(&tx, key, &new_state).expect("TODO");
-        tx.commit().expect("TODO");
+        update_state(&tx, key, &new_state)?;
+        tx.commit().context("could not commit transaction")?;
 
         self.run_actions::<S>(key, action_ids);
 
@@ -211,9 +212,9 @@ pub fn insert_machine<C, S>(conn: &C, initial: S) -> Result<Uuid> where
 
     let id = Uuid::new_v4();
     let fsm = S::FSM_NAME.to_string();
-    let state = serde_json::to_value(initial).expect("TODO");
+    let state = serde_json::to_value(initial).context("failed to serialize FSM")?;
 
-    conn.execute(query, &[&id, &fsm, &state]).expect("TODO");
+    conn.execute(query, &[&id, &fsm, &state])?;
 
     return Ok(id);
 }
@@ -233,9 +234,10 @@ where
 
     let id = Uuid::new_v4();
     let fsm = S::FSM_NAME.to_string();
-    let event_value = serde_json::to_value(event).expect("TODO");
+    let event_value = serde_json::to_value(event)
+        .context("failed to serialize event")?;
 
-    conn.execute(query, &[&id, &fsm, &fsm_id, &event_value]).expect("TODO");
+    conn.execute(query, &[&id, &fsm, &fsm_id, &event_value])?;
     return Ok(id)
 }
 
@@ -254,12 +256,13 @@ fn insert_action<C, S>(conn: &C,
 
     let id = Uuid::new_v4();
     let fsm = S::FSM_NAME.to_string();
-    let action_value = serde_json::to_value(action).expect("TODO");
+    let action_value = serde_json::to_value(action)
+        .context("failed to serialize action")?;
 
     conn.execute(
         query,
         &[&id, &fsm, &fsm_id, &event_id, &action_value, &ActionStatus::Pending]
-    ).expect("TODO");
+    )?;
 
     return Ok(id)
 }
@@ -274,13 +277,11 @@ fn update_state<C, S>(conn: &C,
       UPDATE machines SET state = $1 WHERE id = $2
     "#;
 
-    let state_value = serde_json::to_value(state).expect("TODO");
-    let res_count = conn.execute(query, &[&state_value, &fsm_id])
-        .expect("TODO");
+    let state_value = serde_json::to_value(state).context("failed to serialize FSM")?;
+    let res_count = conn.execute(query, &[&state_value, &fsm_id])?;
 
     if res_count != 1 {
-        // TODO: not found error!
-        unimplemented!()
+        Err(ErrorKind::FSMNotFound(fsm_id).into())
     } else {
         Ok(())
     }
@@ -307,13 +308,12 @@ fn get_machine_internal<C, S>(conn: &C,
       SELECT state FROM machines WHERE id = $1
     "#);
 
-    let rows = conn.query(&query, &[&id]).expect("TODO");
+    let rows = conn.query(&query, &[&id]).context("failed to retrieve FSM")?;
 
     if let Some(row) = rows.into_iter().next() {
-        Ok(serde_json::from_value(row.get(0)).expect("TODO"))
+        Ok(serde_json::from_value(row.get(0)).context("failed to deserialize FSM")?)
     } else {
-        // TODO: return appropriate not found error
-        Err(Error::SomeError)
+        Err(ErrorKind::FSMNotFound(id).into())
     }
 }
 
@@ -328,14 +328,14 @@ fn get_action<C, S>(conn: &C, id: Uuid) -> Result<(ActionStatus, S::Action)> whe
       WHERE id = $1 AND fsm = $2
     "#);
 
-    let rows = conn.query(&query, &[&id, &S::FSM_NAME]).expect("TODO");
+    let rows = conn.query(&query, &[&id, &S::FSM_NAME])?;
 
     if let Some(row) = rows.into_iter().next() {
-        let action = serde_json::from_value(row.get(1)).expect("TODO");
+        let action = serde_json::from_value(row.get(1))
+            .context("failed to deserialize FSM action")?;
         Ok((row.get(0), action))
     } else {
-        // TODO: return appropriate not found error
-        Err(Error::SomeError)
+        Err(ErrorKind::ActionNotFound(id).into())
     }
 }
 
@@ -352,15 +352,13 @@ fn update_action_status<C, S>(conn: &C,
       WHERE id = $3 AND fsm = $4
     "#;
 
-    let result = conn.execute(&query, &[&status, &error, &id, &S::FSM_NAME])
-        .expect("TODO");
+    let result = conn.execute(&query, &[&status, &error, &id, &S::FSM_NAME])?;
 
     if result != 1 {
-        // TODO: Fail in the most gruesome way!
-        unimplemented!()
+        Err(ErrorKind::ActionNotFound(id).into())
+    } else {
+        Ok(())
     }
-
-    Ok(())
 }
 
 /// Execute a single action in case it is pending or retryable. Holds
@@ -408,6 +406,6 @@ fn run_action<S>(tx: Transaction, id: Uuid, state: &S::State, _fsm: PhantomData<
         },
     };
 
-    tx.commit().expect("TODO");
+    tx.commit().context("failed to commit transaction")?;
     Ok(result)
 }