diff options
author | Bryan Newbold <bnewbold@robocracy.org> | 2019-01-09 23:52:37 -0800 |
---|---|---|
committer | Bryan Newbold <bnewbold@robocracy.org> | 2019-01-09 23:52:37 -0800 |
commit | 67c3460d251a4e559a1126b5fe66fe996f840010 (patch) | |
tree | 89f0a5f37809a9b439435b9245844d1da18dc483 | |
parent | a192b0b84e46179a8f28218dfcbb5eb4e28dbf9d (diff) | |
download | fatcat-67c3460d251a4e559a1126b5fe66fe996f840010.tar.gz fatcat-67c3460d251a4e559a1126b5fe66fe996f840010.zip |
HUGE refactor of error types (to use failure)
-rw-r--r-- | rust/Cargo.lock | 13 | ||||
-rw-r--r-- | rust/Cargo.toml | 4 | ||||
-rw-r--r-- | rust/src/auth.rs | 41 | ||||
-rw-r--r-- | rust/src/bin/fatcat-export.rs | 16 | ||||
-rw-r--r-- | rust/src/editing.rs | 4 | ||||
-rw-r--r-- | rust/src/endpoint_handlers.rs | 14 | ||||
-rw-r--r-- | rust/src/endpoints.rs | 955 | ||||
-rw-r--r-- | rust/src/entity_crud.rs | 36 | ||||
-rw-r--r-- | rust/src/errors.rs | 243 | ||||
-rw-r--r-- | rust/src/identifiers.rs | 112 | ||||
-rw-r--r-- | rust/src/lib.rs | 4 |
11 files changed, 550 insertions, 892 deletions
diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 473ef8ce..5070dfb2 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -479,14 +479,6 @@ dependencies = [ ] [[package]] -name = "error-chain" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "failure" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -518,7 +510,7 @@ dependencies = [ "diesel_migrations 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "fatcat-api-spec 0.1.0", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1734,7 +1726,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "httpdate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "im 12.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2630,7 +2622,6 @@ dependencies = [ "checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e" "checksum error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" -"checksum error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07e791d3be96241c77c43846b665ef1384606da2cd2a48730abe606a12906e02" "checksum error-chain 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6930e04918388a9a2e41d518c25cf679ccafe26733fb4127dbf21993f2575d46" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a8e0e827..77eb962a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -15,7 +15,6 @@ diesel = { version = "1.3", features = ["postgres", "uuid", "serde_json", "chron diesel_migrations = "1.3" dotenv = "0.9.0" clap = "2" -error-chain = "0.12" uuid = "0.6" log = "*" data-encoding = "2.1" @@ -24,6 +23,7 @@ lazy_static = "1.0" sha1 = { version = "0.6", features = ["std"] } macaroon = { git = "https://github.com/bnewbold/libmacaroon-rs", branch = "bnewbold-broken" } rand = "*" +failure = "*" # API server chrono = { version = "0.4", features = ["serde"] } @@ -37,7 +37,7 @@ slog = "^2.0" slog-term = "*" slog-async = "*" serde_json = "1.0" -sentry = { version = "^0.12", default-features = false, features = ["with_client_implementation", "with_backtrace", "with_panic", "with_log", "with_rust_info", "with_error_chain"] } +sentry = { version = "^0.12", default-features = false, features = ["with_client_implementation", "with_backtrace", "with_panic", "with_log", "with_rust_info", "with_failure"] } #cadence = "^0.16" # Command-line tools diff --git a/rust/src/auth.rs b/rust/src/auth.rs index d86e5ba8..e7526389 100644 --- a/rust/src/auth.rs +++ b/rust/src/auth.rs @@ -58,11 +58,10 @@ impl AuthContext { pub fn require_role(&self, role: FatcatRole) -> Result<()> { match self.has_role(role) { true => Ok(()), - false => Err(ErrorKind::InsufficientPrivileges(format!( + false => Err(FatcatError::InsufficientPrivileges(format!( "doesn't have required role: {:?}", role - )) - .into()), + )))?, } } @@ -75,10 +74,10 @@ impl AuthContext { .get_result(conn)?; match editgroup.editor_id == self.editor_id.to_uuid() { true => Ok(()), - false => Err(ErrorKind::InsufficientPrivileges( - "editor does not own this editgroup".to_string(), - ) - .into()), + false => Err(FatcatError::InsufficientPrivileges(format!( + "editor does not own this editgroup ({})", + editgroup_id + )))?, } } } @@ -245,7 +244,7 @@ impl AuthConfectionary { Ok(m) => m, Err(e) => { // TODO: should be "chaining" here - return Err(ErrorKind::InvalidCredentials(format!( + return Err(FatcatError::InvalidCredentials(format!( "macaroon deserialize error: {:?}", e )) @@ -256,7 +255,7 @@ impl AuthConfectionary { Ok(m) => m, Err(e) => { // TODO: should be "chaining" here - return Err(ErrorKind::InvalidCredentials(format!( + return Err(FatcatError::InvalidCredentials(format!( "macaroon validate error: {:?}", e )) @@ -274,7 +273,7 @@ impl AuthConfectionary { let editor_id = match editor_id { Some(id) => id, None => { - return Err(ErrorKind::InvalidCredentials( + return Err(FatcatError::InvalidCredentials( "expected an editor_id caveat".to_string(), ) .into()); @@ -299,7 +298,7 @@ impl AuthConfectionary { let created = match created { Some(c) => c, None => { - return Err(ErrorKind::InvalidCredentials( + return Err(FatcatError::InvalidCredentials( "expected a 'created' (time >) caveat".to_string(), ) .into()); @@ -313,7 +312,7 @@ impl AuthConfectionary { let auth_epoch = DateTime::<Utc>::from_utc(editor.auth_epoch, Utc); // allow a second of wiggle room for precision and, eg, tests if created < (auth_epoch - chrono::Duration::seconds(1)) { - return Err(ErrorKind::InvalidCredentials( + return Err(FatcatError::InvalidCredentials( "token created before current auth_epoch (was probably revoked by editor)" .to_string(), ) @@ -333,7 +332,7 @@ impl AuthConfectionary { let verify_key = match self.root_keys.get(mac.identifier()) { Some(key) => key, None => { - return Err(ErrorKind::InvalidCredentials(format!( + return Err(FatcatError::InvalidCredentials(format!( "no valid auth signing key for identifier: {}", mac.identifier() )) @@ -343,16 +342,18 @@ impl AuthConfectionary { match mac.verify(verify_key, &mut verifier) { Ok(true) => (), Ok(false) => { - return Err(ErrorKind::InvalidCredentials( + return Err(FatcatError::InvalidCredentials( "auth token (macaroon) not valid (signature and/or caveats failed)".to_string(), ) .into()); } Err(e) => { // TODO: chain - return Err( - ErrorKind::InvalidCredentials(format!("token parsing failed: {:?}", e)).into(), - ); + return Err(FatcatError::InvalidCredentials(format!( + "token parsing failed: {:?}", + e + )) + .into()); } } Ok(editor) @@ -369,7 +370,7 @@ impl AuthConfectionary { let header: Vec<String> = header.split_whitespace().map(|s| s.to_string()).collect(); if !(header.len() == 2 && header[0] == "Bearer") { - return Err(ErrorKind::InvalidCredentials( + return Err(FatcatError::InvalidCredentials( "invalid Bearer Auth HTTP header".to_string(), ) .into()); @@ -378,7 +379,7 @@ impl AuthConfectionary { } None => None, _ => { - return Err(ErrorKind::InvalidCredentials( + return Err(FatcatError::InvalidCredentials( "Authentication HTTP Header should either be empty or a Beaerer API key" .to_string(), ) @@ -404,7 +405,7 @@ impl AuthConfectionary { ) -> Result<AuthContext> { match self.parse_swagger(conn, auth_data, endpoint)? { Some(auth) => Ok(auth), - None => Err(ErrorKind::InvalidCredentials("no token supplied".to_string()).into()), + None => Err(FatcatError::InvalidCredentials("no token supplied".to_string()).into()), } } diff --git a/rust/src/bin/fatcat-export.rs b/rust/src/bin/fatcat-export.rs index d438c00a..82f40048 100644 --- a/rust/src/bin/fatcat-export.rs +++ b/rust/src/bin/fatcat-export.rs @@ -6,9 +6,9 @@ #[macro_use] extern crate clap; #[macro_use] -extern crate error_chain; -#[macro_use] extern crate log; +#[macro_use] +extern crate failure; use clap::{App, Arg}; @@ -20,7 +20,6 @@ use fatcat_api_spec::models::*; use std::str::FromStr; use uuid::Uuid; -use error_chain::ChainedError; use std::thread; //use std::io::{Stdout,StdoutLock}; use crossbeam_channel as channel; @@ -63,22 +62,19 @@ macro_rules! generic_loop_work { db_conn, row.rev_id.expect("valid, non-deleted row"), HideFlags::none(), - ) - .chain_err(|| "reading entity from database")?; - //let mut entity = ReleaseEntity::db_get_rev(db_conn, row.rev_id.expect("valid, non-deleted row"))?; + )?; // .chain_err(|| "reading entity from database")?; entity.state = Some("active".to_string()); // XXX entity.ident = Some(row.ident_id.to_string()); if let Some(expand) = expand { - entity - .db_expand(db_conn, expand) - .chain_err(|| "expanding sub-entities from database")?; + entity.db_expand(db_conn, expand)? + // chain_err(|| "expanding sub-entities from database")?; } output_sender.send(serde_json::to_string(&entity)?); } Ok(()) })(); if let Err(ref e) = result { - error!("{}", e.display_chain()) + error!("{}", e); // e.display_chain()) } result.unwrap() } diff --git a/rust/src/editing.rs b/rust/src/editing.rs index 6a2495c5..2dfabc75 100644 --- a/rust/src/editing.rs +++ b/rust/src/editing.rs @@ -24,7 +24,7 @@ impl EditContext { .count() .get_result(conn)?; if count > 0 { - return Err(ErrorKind::EditgroupAlreadyAccepted(self.editgroup_id.to_string()).into()); + return Err(FatcatError::EditgroupAlreadyAccepted(self.editgroup_id.to_string()).into()); } Ok(()) } @@ -104,7 +104,7 @@ pub fn accept_editgroup(conn: &DbConn, editgroup_id: FatcatId) -> Result<Changel .count() .get_result(conn)?; if count > 0 { - return Err(ErrorKind::EditgroupAlreadyAccepted(editgroup_id.to_string()).into()); + return Err(FatcatError::EditgroupAlreadyAccepted(editgroup_id.to_string()).into()); } // copy edit columns to ident table diff --git a/rust/src/endpoint_handlers.rs b/rust/src/endpoint_handlers.rs index 4dc528bd..78044054 100644 --- a/rust/src/endpoint_handlers.rs +++ b/rust/src/endpoint_handlers.rs @@ -129,7 +129,9 @@ impl Server { .first(conn)? } _ => { - return Err(ErrorKind::MissingOrMultipleExternalId("in lookup".to_string()).into()); + return Err( + FatcatError::MissingOrMultipleExternalId("in lookup".to_string()).into(), + ); } }; @@ -166,7 +168,9 @@ impl Server { .first(conn)? } _ => { - return Err(ErrorKind::MissingOrMultipleExternalId("in lookup".to_string()).into()); + return Err( + FatcatError::MissingOrMultipleExternalId("in lookup".to_string()).into(), + ); } }; @@ -234,7 +238,9 @@ impl Server { .first(conn)? } _ => { - return Err(ErrorKind::MissingOrMultipleExternalId("in lookup".to_string()).into()); + return Err( + FatcatError::MissingOrMultipleExternalId("in lookup".to_string()).into(), + ); } }; @@ -313,7 +319,7 @@ impl Server { } _ => { return Err( - ErrorKind::MissingOrMultipleExternalId("in lookup".to_string()).into(), + FatcatError::MissingOrMultipleExternalId("in lookup".to_string()).into(), ); } }; diff --git a/rust/src/endpoints.rs b/rust/src/endpoints.rs index a66f5d51..1bed3c56 100644 --- a/rust/src/endpoints.rs +++ b/rust/src/endpoints.rs @@ -19,9 +19,44 @@ use fatcat_api_spec::models; use fatcat_api_spec::models::*; use fatcat_api_spec::*; use futures::{self, Future}; -use sentry::integrations::error_chain::capture_error_chain; +use sentry::integrations::failure::capture_fail; use std::str::FromStr; -use uuid::Uuid; +use uuid::{self, Uuid}; + +// This makes response matching below *much* more terse +use crate::errors::FatcatError::*; + +// I heard you liked macros, so I use macros in your macros +macro_rules! generic_auth_err_responses { + ($val:ident, $resp_type:ident) => { + //use crate::errors::FatcatError::*; + match $val { + NotFound(_, _) => $resp_type::NotFound($val.into()), + InvalidCredentials(_) | InsufficientPrivileges(_) => $resp_type::Forbidden($val.into()), + DatabaseError(_) | InternalError(_) => { + error!("{}", $val); + capture_fail(&$val); + $resp_type::GenericError($val.into()) + } + _ => $resp_type::BadRequest($val.into()), + } + }; +} + +macro_rules! generic_err_responses { + ($val:ident, $resp_type:ident) => { + //use crate::errors::FatcatError::*; + match $val { + NotFound(_, _) => $resp_type::NotFound($val.into()), + DatabaseError(_) | InternalError(_) => { + error!("{}", $val); + capture_fail(&$val); + $resp_type::GenericError($val.into()) + } + _ => $resp_type::BadRequest($val.into()), + } + }; +} /// Helper for generating wrappers (which return "Box::new(futures::done(Ok(BLAH)))" like the /// codegen fatcat-api-spec code wants) that call through to actual helpers (which have simple @@ -62,28 +97,10 @@ macro_rules! wrap_entity_handlers { Ok(entity) }, } - })() { + })().map_err(|e| FatcatError::from(e)) { Ok(entity) => $get_resp::FoundEntity(entity), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $get_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("No such entity {}: {}", stringify!($model), ident) }), - Err(Error(ErrorKind::Uuid(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "uuid".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidFatcatId(e), _)) => - $get_resp::BadRequest(ErrorResponse { - success: false, error: "fatcat-id".to_string(), - message: ErrorKind::InvalidFatcatId(e).to_string() }), - Err(Error(ErrorKind::MalformedExternalId(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "editgroup".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $get_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_err_responses!(fe, $get_resp), }; Box::new(futures::done(Ok(ret))) } @@ -103,37 +120,10 @@ macro_rules! wrap_entity_handlers { let edit_context = make_edit_context(&conn, auth_context.editor_id, Some(editgroup_id), false)?; edit_context.check(&conn)?; entity.db_create(&conn, &edit_context)?.into_model() - }) { + }).map_err(|e| FatcatError::from(e)) { Ok(edit) => $post_resp::CreatedEntity(edit), - Err(Error(ErrorKind::Diesel(e), _)) => - // TODO: needs refinement; what kind of Diesel error? - $post_resp::BadRequest(ErrorResponse { success: false, error: "database".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::Uuid(e), _)) => - $post_resp::BadRequest(ErrorResponse { success: false, error: "uuid".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidFatcatId(e), _)) => - $post_resp::BadRequest(ErrorResponse { - success: false, error: "fatcat-id".to_string(), - message: ErrorKind::InvalidFatcatId(e).to_string() }), - Err(Error(ErrorKind::MalformedExternalId(e), _)) => - $post_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::MalformedChecksum(e), _)) => - $post_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::NotInControlledVocabulary(e), _)) => - $post_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => - $post_resp::BadRequest(ErrorResponse { success: false, error: "editgroup".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidCredentials(e), _)) => - $post_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => - $post_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $post_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $post_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_auth_err_responses!(fe, $post_resp), }; Box::new(futures::done(Ok(ret))) } @@ -155,37 +145,10 @@ macro_rules! wrap_entity_handlers { Some(eg_id) } else { None }; self.$post_batch_handler(&conn, entity_list, autoaccept.unwrap_or(false), auth_context.editor_id, editgroup_id) - }) { + }).map_err(|e| FatcatError::from(e)) { Ok(edit) => $post_batch_resp::CreatedEntities(edit), - Err(Error(ErrorKind::Diesel(e), _)) => - $post_batch_resp::BadRequest(ErrorResponse { success: false, error: "database".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::Uuid(e), _)) => - $post_batch_resp::BadRequest(ErrorResponse { success: false, error: "uuid".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidFatcatId(e), _)) => - $post_batch_resp::BadRequest(ErrorResponse { - success: false, error: "fatcat-id".to_string(), - message: ErrorKind::InvalidFatcatId(e).to_string() }), - Err(Error(ErrorKind::MalformedExternalId(e), _)) => - $post_batch_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::MalformedChecksum(e), _)) => - $post_batch_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::NotInControlledVocabulary(e), _)) => - $post_batch_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => - $post_batch_resp::BadRequest(ErrorResponse { success: false, error: "editgroup".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidCredentials(e), _)) => - // TODO: why can't I NotAuthorized here? - $post_batch_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => - $post_batch_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $post_batch_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $post_batch_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_auth_err_responses!(fe, $post_batch_resp), }; Box::new(futures::done(Ok(ret))) } @@ -207,40 +170,10 @@ macro_rules! wrap_entity_handlers { let edit_context = make_edit_context(&conn, auth_context.editor_id, Some(editgroup_id), false)?; edit_context.check(&conn)?; entity.db_update(&conn, &edit_context, entity_id)?.into_model() - }) { + }).map_err(|e| FatcatError::from(e)) { Ok(edit) => $update_resp::UpdatedEntity(edit), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $update_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("No such entity {}: {}", stringify!($model), ident) }), - Err(Error(ErrorKind::Diesel(e), _)) => - $update_resp::BadRequest(ErrorResponse { success: false, error: "database".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::Uuid(e), _)) => - $update_resp::BadRequest(ErrorResponse { success: false, error: "uuid".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidFatcatId(e), _)) => - $update_resp::BadRequest(ErrorResponse { - success: false, error: "fatcat-id".to_string(), - message: ErrorKind::InvalidFatcatId(e).to_string() }), - Err(Error(ErrorKind::MalformedExternalId(e), _)) => - $update_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::MalformedChecksum(e), _)) => - $update_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::NotInControlledVocabulary(e), _)) => - $update_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => - $update_resp::BadRequest(ErrorResponse { success: false, error: "editgroup".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidEntityStateTransform(e), _)) => - $update_resp::BadRequest(ErrorResponse { success: false, error: "entity-state".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $update_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidCredentials(e), _)) => - $update_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => - $update_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $update_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_auth_err_responses!(fe, $update_resp), }; Box::new(futures::done(Ok(ret))) } @@ -261,36 +194,10 @@ macro_rules! wrap_entity_handlers { let edit_context = make_edit_context(&conn, auth_context.editor_id, Some(editgroup_id), false)?; edit_context.check(&conn)?; $model::db_delete(&conn, &edit_context, entity_id)?.into_model() - }) { + }).map_err(|e| FatcatError::from(e)) { Ok(edit) => $delete_resp::DeletedEntity(edit), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $delete_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("No such entity {}: {}", stringify!($model), ident) }), - Err(Error(ErrorKind::Diesel(e), _)) => - $delete_resp::BadRequest(ErrorResponse { success: false, error: "database".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::Uuid(e), _)) => - $delete_resp::BadRequest(ErrorResponse { success: false, error: "uuid".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidFatcatId(e), _)) => - $delete_resp::BadRequest(ErrorResponse { - success: false, error: "fatcat-id".to_string(), - message: ErrorKind::InvalidFatcatId(e).to_string() }), - Err(Error(ErrorKind::MalformedExternalId(e), _)) => - $delete_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => - $delete_resp::BadRequest(ErrorResponse { success: false, error: "editgroup".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidEntityStateTransform(e), _)) => - $delete_resp::BadRequest(ErrorResponse { success: false, error: "entity-state".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $delete_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidCredentials(e), _)) => - $delete_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => - $delete_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $delete_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_auth_err_responses!(fe, $delete_resp), }; Box::new(futures::done(Ok(ret))) } @@ -306,24 +213,10 @@ macro_rules! wrap_entity_handlers { let ret = match (|| { let entity_id = FatcatId::from_str(&ident)?; $model::db_get_history(&conn, entity_id, limit) - })() { + })().map_err(|e| FatcatError::from(e)) { Ok(history) => $get_history_resp::FoundEntityHistory(history), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $get_history_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("No such entity {}: {}", stringify!($model), ident) }), - Err(Error(ErrorKind::Uuid(e), _)) => - $get_history_resp::BadRequest(ErrorResponse { success: false, error: "uuid".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidFatcatId(e), _)) => - $get_history_resp::BadRequest(ErrorResponse { - success: false, error: "fatcat-id".to_string(), - message: ErrorKind::InvalidFatcatId(e).to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $get_history_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $get_history_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_err_responses!(fe, $get_history_resp), }; Box::new(futures::done(Ok(ret))) } @@ -352,22 +245,10 @@ macro_rules! wrap_entity_handlers { Ok(entity) }, } - })() { + })().map_err(|e| FatcatError::from(e)) { Ok(entity) => $get_rev_resp::FoundEntityRevision(entity), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $get_rev_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("No such entity revision {}: {}", stringify!($model), rev_id) }), - Err(Error(ErrorKind::Uuid(e), _)) => - $get_rev_resp::BadRequest(ErrorResponse { success: false, error: "uuid".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::MalformedExternalId(e), _)) => - $get_rev_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $get_rev_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $get_rev_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_err_responses!(fe, $get_rev_resp), }; Box::new(futures::done(Ok(ret))) } @@ -382,18 +263,10 @@ macro_rules! wrap_entity_handlers { let ret = match (|| { let edit_id = Uuid::from_str(&edit_id)?; $model::db_get_edit(&conn, edit_id)?.into_model() - })() { + })().map_err(|e| FatcatError::from(e)) { Ok(edit) => $get_edit_resp::FoundEdit(edit), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $get_edit_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("No such {} entity edit: {}", stringify!($model), edit_id) }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $get_edit_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $get_edit_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_err_responses!(fe, $get_edit_resp), }; Box::new(futures::done(Ok(ret))) } @@ -411,29 +284,13 @@ macro_rules! wrap_entity_handlers { let edit = $model::db_get_edit(&conn, edit_id)?; auth_context.require_editgroup(&conn, FatcatId::from_uuid(&edit.editgroup_id))?; $model::db_delete_edit(&conn, edit_id) - }) { + }).map_err(|e| FatcatError::from(e)) { Ok(()) => $delete_edit_resp::DeletedEdit(Success { success: true, message: format!("Successfully deleted work-in-progress {} edit: {}", stringify!($model), edit_id) }), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $delete_edit_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("No such {} edit: {}", stringify!($model), edit_id) }), - Err(Error(ErrorKind::Diesel(e), _)) => - $delete_edit_resp::BadRequest(ErrorResponse { success: false, error: "database".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => - $delete_edit_resp::BadRequest(ErrorResponse { success: false, error: "editgroup".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $delete_edit_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidCredentials(e), _)) => - $delete_edit_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => - $delete_edit_resp::Forbidden(ErrorResponse { success: false, error: "auth".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $delete_edit_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_auth_err_responses!(fe, $delete_edit_resp), }; Box::new(futures::done(Ok(ret))) } @@ -449,24 +306,10 @@ macro_rules! wrap_entity_handlers { let entity_id = FatcatId::from_str(&ident)?; let redirects: Vec<FatcatId> = $model::db_get_redirects(&conn, entity_id)?; Ok(redirects.into_iter().map(|fcid| fcid.to_string()).collect()) - })() { + })().map_err(|e: Error| FatcatError::from(e)) { Ok(redirects) => $get_redirects_resp::FoundEntityRedirects(redirects), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $get_redirects_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("No such entity {}: {}", stringify!($model), ident) }), - Err(Error(ErrorKind::Uuid(e), _)) => - $get_redirects_resp::BadRequest(ErrorResponse { success: false, error: "uuid".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::InvalidFatcatId(e), _)) => - $get_redirects_resp::BadRequest(ErrorResponse { - success: false, error: "fatcat-id".to_string(), - message: ErrorKind::InvalidFatcatId(e).to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $get_redirects_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $get_redirects_resp::GenericError(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_err_responses!(fe, $get_redirects_resp), }; Box::new(futures::done(Ok(ret))) } @@ -494,24 +337,10 @@ macro_rules! wrap_lookup_handler { Some(param) => HideFlags::from_str(¶m).unwrap(), }; // No transaction for GET - let ret = match self.$get_handler(&conn, &$idname, &wikidata_qid, expand_flags, hide_flags) { + let ret = match self.$get_handler(&conn, &$idname, &wikidata_qid, expand_flags, hide_flags).map_err(|e| FatcatError::from(e)) { Ok(entity) => $get_resp::FoundEntity(entity), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $get_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("Not found: {:?} / {:?}", $idname, wikidata_qid) }), - Err(Error(ErrorKind::MalformedExternalId(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::MalformedChecksum(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::MissingOrMultipleExternalId(e), _)) => { - $get_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string(), }) }, - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $get_resp::BadRequest(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_err_responses!(fe, $get_resp), }; Box::new(futures::done(Ok(ret))) } @@ -530,22 +359,10 @@ macro_rules! wrap_fcid_handler { let ret = match (|| { let fcid = FatcatId::from_str(&id)?; self.$get_handler(&conn, fcid) - })() { + })().map_err(|e| FatcatError::from(e)) { Ok(entity) => $get_resp::Found(entity), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $get_resp::NotFound(ErrorResponse { success: false, error: "not-found".to_string(), message: format!("Not found: {}", id) }), - Err(Error(ErrorKind::MalformedExternalId(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::NotInControlledVocabulary(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $get_resp::BadRequest(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_err_responses!(fe, $get_resp), }; Box::new(futures::done(Ok(ret))) } @@ -569,24 +386,10 @@ macro_rules! wrap_fcid_hide_handler { Some(param) => HideFlags::from_str(¶m)?, }; self.$get_handler(&conn, fcid, hide_flags) - })() { + })().map_err(|e| FatcatError::from(e)) { Ok(entity) => $get_resp::Found(entity), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => - $get_resp::NotFound(ErrorResponse { success: false, - error: "not-found".to_string(), - message: format!("Not found: {}", id) }), - Err(Error(ErrorKind::MalformedExternalId(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::NotInControlledVocabulary(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "field-syntax".to_string(), message: e.to_string() }), - Err(Error(ErrorKind::OtherBadRequest(e), _)) => - $get_resp::BadRequest(ErrorResponse { success: false, error: "other".to_string(), message: e.to_string() }), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - $get_resp::BadRequest(ErrorResponse { success: false, error: "internal".to_string(), message: e.to_string() }) - }, + Err(fe) => generic_err_responses!(fe, $get_resp), }; Box::new(futures::done(Ok(ret))) } @@ -828,47 +631,13 @@ impl Api for Server { Some(param) => HideFlags::from_str(¶m).unwrap(), }; // No transaction for GET - let ret = - match self.lookup_file_handler(&conn, &md5, &sha1, &sha256, expand_flags, hide_flags) { - Ok(entity) => LookupFileResponse::FoundEntity(entity), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => { - LookupFileResponse::NotFound(ErrorResponse { - success: false, - error: "not-found".to_string(), - message: format!("Not found: {:?} / {:?} / {:?}", md5, sha1, sha256), - }) - } - Err(Error(ErrorKind::MalformedExternalId(e), _)) => { - LookupFileResponse::BadRequest(ErrorResponse { - success: false, - error: "field-syntax".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::MalformedChecksum(e), _)) => { - LookupFileResponse::BadRequest(ErrorResponse { - success: false, - error: "field-syntax".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::MissingOrMultipleExternalId(e), _)) => { - LookupFileResponse::BadRequest(ErrorResponse { - success: false, - error: "field-syntax".to_string(), - message: e.to_string(), - }) - } - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - LookupFileResponse::BadRequest(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }) - } - }; + let ret = match self + .lookup_file_handler(&conn, &md5, &sha1, &sha256, expand_flags, hide_flags) + .map_err(|e| FatcatError::from(e)) + { + Ok(entity) => LookupFileResponse::FoundEntity(entity), + Err(fe) => generic_err_responses!(fe, LookupFileResponse), + }; Box::new(futures::done(Ok(ret))) } @@ -894,51 +663,24 @@ impl Api for Server { Some(param) => HideFlags::from_str(¶m).unwrap(), }; // No transaction for GET - let ret = match self.lookup_release_handler( - &conn, - &doi, - &wikidata_qid, - &isbn13, - &pmid, - &pmcid, - &core_id, - expand_flags, - hide_flags, - ) { + let ret = match self + .lookup_release_handler( + &conn, + &doi, + &wikidata_qid, + &isbn13, + &pmid, + &pmcid, + &core_id, + expand_flags, + hide_flags, + ) + .map_err(|e| FatcatError::from(e)) + { Ok(entity) => LookupReleaseResponse::FoundEntity(entity), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => { - LookupReleaseResponse::NotFound(ErrorResponse { - success: false, - error: "not-found".to_string(), - message: format!( - "Not found: {:?} / {:?} / {:?} / {:?} / {:?} / {:?}", - doi, wikidata_qid, isbn13, pmid, pmcid, core_id - ), - }) - } - Err(Error(ErrorKind::MalformedExternalId(e), _)) => { - LookupReleaseResponse::BadRequest(ErrorResponse { - success: false, - error: "field-syntax".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::MissingOrMultipleExternalId(e), _)) => { - LookupReleaseResponse::BadRequest(ErrorResponse { - success: false, - error: "field-syntax".to_string(), - message: e.to_string(), - }) - } - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - LookupReleaseResponse::BadRequest(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }) - } + // TODO: ensure good 'Not Found" error message here + // (was: "Not found: {:?} / {:?} / {:?} / {:?} / {:?} / {:?}", doi, wikidata_qid, isbn13, pmid, pmcid, core_id + Err(fe) => generic_err_responses!(fe, LookupReleaseResponse), }; Box::new(futures::done(Ok(ret))) } @@ -951,85 +693,33 @@ impl Api for Server { context: &Context, ) -> Box<Future<Item = UpdateEditorResponse, Error = ApiError> + Send> { let conn = self.db_pool.get().expect("db_pool error"); - let ret = match conn.transaction(|| { - if Some(editor_id.clone()) != editor.editor_id { - return Err( - ErrorKind::OtherBadRequest("editor_id doesn't match".to_string()).into(), - ); - } - let auth_context = self.auth_confectionary.require_auth( - &conn, - &context.auth_data, - Some("update_editor"), - )?; - let editor_id = FatcatId::from_str(&editor_id)?; - // DANGER! these permissions are for username updates only! - if editor_id == auth_context.editor_id { - // self edit of username allowed - auth_context.require_role(FatcatRole::Editor)?; - } else { - // admin can update any username - auth_context.require_role(FatcatRole::Admin)?; - }; - update_editor_username(&conn, editor_id, editor.username).map(|e| e.into_model()) - }) { + let ret = match conn + .transaction(|| { + if Some(editor_id.clone()) != editor.editor_id { + return Err( + FatcatError::OtherBadRequest("editor_id doesn't match".to_string()).into(), + ); + } + let auth_context = self.auth_confectionary.require_auth( + &conn, + &context.auth_data, + Some("update_editor"), + )?; + let editor_id = FatcatId::from_str(&editor_id)?; + // DANGER! these permissions are for username updates only! + if editor_id == auth_context.editor_id { + // self edit of username allowed + auth_context.require_role(FatcatRole::Editor)?; + } else { + // admin can update any username + auth_context.require_role(FatcatRole::Admin)?; + }; + update_editor_username(&conn, editor_id, editor.username).map(|e| e.into_model()) + }) + .map_err(|e| FatcatError::from(e)) + { Ok(editor) => UpdateEditorResponse::UpdatedEditor(editor), - Err(Error(ErrorKind::Diesel(e), _)) => { - UpdateEditorResponse::BadRequest(ErrorResponse { - success: false, - error: "database".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::Uuid(e), _)) => UpdateEditorResponse::BadRequest(ErrorResponse { - success: false, - error: "uuid".to_string(), - message: e.to_string(), - }), - Err(Error(ErrorKind::InvalidFatcatId(e), _)) => { - UpdateEditorResponse::BadRequest(ErrorResponse { - success: false, - error: "fatcat-id".to_string(), - message: ErrorKind::InvalidFatcatId(e).to_string(), - }) - } - Err(Error(ErrorKind::MalformedExternalId(e), _)) => { - UpdateEditorResponse::BadRequest(ErrorResponse { - success: false, - error: "field-syntax".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::InvalidCredentials(e), _)) => { - UpdateEditorResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => { - UpdateEditorResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::OtherBadRequest(e), _)) => { - UpdateEditorResponse::BadRequest(ErrorResponse { - success: false, - error: "other".to_string(), - message: e.to_string(), - }) - } - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - UpdateEditorResponse::GenericError(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }) - } + Err(fe) => generic_err_responses!(fe, UpdateEditorResponse), }; Box::new(futures::done(Ok(ret))) } @@ -1040,55 +730,26 @@ impl Api for Server { context: &Context, ) -> Box<Future<Item = AcceptEditgroupResponse, Error = ApiError> + Send> { let conn = self.db_pool.get().expect("db_pool error"); - let ret = match conn.transaction(|| { - let editgroup_id = FatcatId::from_str(&editgroup_id)?; - let auth_context = self.auth_confectionary.require_auth( - &conn, - &context.auth_data, - Some("accept_editgroup"), - )?; - auth_context.require_role(FatcatRole::Admin)?; - // NOTE: this is currently redundant, but zero-cost - auth_context.require_editgroup(&conn, editgroup_id)?; - self.accept_editgroup_handler(&conn, editgroup_id) - }) { + let ret = match conn + .transaction(|| { + let editgroup_id = FatcatId::from_str(&editgroup_id)?; + let auth_context = self.auth_confectionary.require_auth( + &conn, + &context.auth_data, + Some("accept_editgroup"), + )?; + auth_context.require_role(FatcatRole::Admin)?; + // NOTE: this is currently redundant, but zero-cost + auth_context.require_editgroup(&conn, editgroup_id)?; + self.accept_editgroup_handler(&conn, editgroup_id) + }) + .map_err(|e| FatcatError::from(e)) + { Ok(()) => AcceptEditgroupResponse::MergedSuccessfully(Success { success: true, message: "horray!".to_string(), }), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => { - AcceptEditgroupResponse::NotFound(ErrorResponse { - success: false, - error: "not-found".to_string(), - message: format!("No such editgroup: {}", editgroup_id), - }) - } - Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => { - AcceptEditgroupResponse::BadRequest(ErrorResponse { - success: false, - error: "editgroup".to_string(), - message: ErrorKind::EditgroupAlreadyAccepted(e).to_string(), - }) - } - Err(Error(ErrorKind::InvalidCredentials(e), _)) => { - AcceptEditgroupResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => { - AcceptEditgroupResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(e) => AcceptEditgroupResponse::GenericError(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }), + Err(fe) => generic_auth_err_responses!(fe, AcceptEditgroupResponse), }; Box::new(futures::done(Ok(ret))) } @@ -1099,27 +760,15 @@ impl Api for Server { _context: &Context, ) -> Box<Future<Item = GetEditgroupResponse, Error = ApiError> + Send> { let conn = self.db_pool.get().expect("db_pool error"); - let ret = match conn.transaction(|| { - let editgroup_id = FatcatId::from_str(&editgroup_id)?; - self.get_editgroup_handler(&conn, editgroup_id) - }) { + let ret = match conn + .transaction(|| { + let editgroup_id = FatcatId::from_str(&editgroup_id)?; + self.get_editgroup_handler(&conn, editgroup_id) + }) + .map_err(|e| FatcatError::from(e)) + { Ok(entity) => GetEditgroupResponse::Found(entity), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => { - GetEditgroupResponse::NotFound(ErrorResponse { - success: false, - error: "not-found".to_string(), - message: format!("No such editgroup: {}", editgroup_id), - }) - } - Err(e) => - // TODO: dig in to error type here - { - GetEditgroupResponse::GenericError(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }) - } + Err(fe) => generic_err_responses!(fe, GetEditgroupResponse), }; Box::new(futures::done(Ok(ret))) } @@ -1130,52 +779,41 @@ impl Api for Server { context: &Context, ) -> Box<Future<Item = CreateEditgroupResponse, Error = ApiError> + Send> { let conn = self.db_pool.get().expect("db_pool error"); - let ret = match conn.transaction(|| { - let auth_context = self.auth_confectionary.require_auth( - &conn, - &context.auth_data, - Some("create_editgroup"), - )?; - auth_context.require_role(FatcatRole::Editor)?; - let mut entity = entity.clone(); - match entity.editor_id.clone() { - Some(editor_id) => { - if editor_id != auth_context.editor_id.to_string() - && !auth_context.has_role(FatcatRole::Admin) - { - bail!("not authorized to create editgroups in others' names"); + let ret = match conn + .transaction(|| { + let auth_context = self.auth_confectionary.require_auth( + &conn, + &context.auth_data, + Some("create_editgroup"), + )?; + auth_context.require_role(FatcatRole::Editor)?; + let mut entity = entity.clone(); + match entity.editor_id.clone() { + Some(editor_id) => { + if editor_id != auth_context.editor_id.to_string() + && !auth_context.has_role(FatcatRole::Admin) + { + bail!("not authorized to create editgroups in others' names"); + } } - } - None => { - entity.editor_id = Some(auth_context.editor_id.to_string()); - } - }; - self.create_editgroup_handler(&conn, entity) - }) { + None => { + entity.editor_id = Some(auth_context.editor_id.to_string()); + } + }; + self.create_editgroup_handler(&conn, entity) + }) + .map_err(|e| FatcatError::from(e)) + { Ok(eg) => CreateEditgroupResponse::SuccessfullyCreated(eg), - Err(Error(ErrorKind::InvalidCredentials(e), _)) => { - CreateEditgroupResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => { - CreateEditgroupResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(e) => - // TODO: dig in to error type here - { - CreateEditgroupResponse::GenericError(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }) - } + Err(fe) => match fe { + NotFound(_, _) => CreateEditgroupResponse::NotFound(fe.into()), + DatabaseError(_) | InternalError(_) => { + error!("{}", fe); + capture_fail(&fe); + CreateEditgroupResponse::GenericError(fe.into()) + } + _ => CreateEditgroupResponse::BadRequest(fe.into()), + }, }; Box::new(futures::done(Ok(ret))) } @@ -1187,17 +825,19 @@ impl Api for Server { ) -> Box<Future<Item = GetChangelogResponse, Error = ApiError> + Send> { let conn = self.db_pool.get().expect("db_pool error"); // No transaction for GET - let ret = match self.get_changelog_handler(&conn, limit) { + let ret = match self + .get_changelog_handler(&conn, limit) + .map_err(|e| FatcatError::from(e)) + { Ok(changelog) => GetChangelogResponse::Success(changelog), - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - GetChangelogResponse::GenericError(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }) - } + Err(fe) => match fe { + DatabaseError(_) | InternalError(_) => { + error!("{}", fe); + capture_fail(&fe); + GetChangelogResponse::GenericError(fe.into()) + } + _ => GetChangelogResponse::BadRequest(fe.into()), + }, }; Box::new(futures::done(Ok(ret))) } @@ -1209,24 +849,12 @@ impl Api for Server { ) -> Box<Future<Item = GetChangelogEntryResponse, Error = ApiError> + Send> { let conn = self.db_pool.get().expect("db_pool error"); // No transaction for GET - let ret = match self.get_changelog_entry_handler(&conn, id) { + let ret = match self + .get_changelog_entry_handler(&conn, id) + .map_err(|e| FatcatError::from(e)) + { Ok(entry) => GetChangelogEntryResponse::FoundChangelogEntry(entry), - Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) => { - GetChangelogEntryResponse::NotFound(ErrorResponse { - success: false, - error: "not-found".to_string(), - message: format!("No such changelog entry: {}", id), - }) - } - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - GetChangelogEntryResponse::GenericError(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }) - } + Err(fe) => generic_err_responses!(fe, GetChangelogEntryResponse), }; Box::new(futures::done(Ok(ret))) } @@ -1237,101 +865,35 @@ impl Api for Server { context: &Context, ) -> Box<Future<Item = AuthOidcResponse, Error = ApiError> + Send> { let conn = self.db_pool.get().expect("db_pool error"); - let ret = match conn.transaction(|| { - let auth_context = self.auth_confectionary.require_auth( - &conn, - &context.auth_data, - Some("auth_oidc"), - )?; - auth_context.require_role(FatcatRole::Superuser)?; - let (editor, created) = self.auth_oidc_handler(&conn, params)?; - // create an auth token with 31 day duration - let token = self.auth_confectionary.create_token( - FatcatId::from_str(&editor.editor_id.clone().unwrap())?, - Some(chrono::Duration::days(31)), - )?; - let result = AuthOidcResult { editor, token }; - Ok((result, created)) - }) { + let ret = match conn + .transaction(|| { + let auth_context = self.auth_confectionary.require_auth( + &conn, + &context.auth_data, + Some("auth_oidc"), + )?; + auth_context.require_role(FatcatRole::Superuser)?; + let (editor, created) = self.auth_oidc_handler(&conn, params)?; + // create an auth token with 31 day duration + let token = self.auth_confectionary.create_token( + FatcatId::from_str(&editor.editor_id.clone().unwrap())?, + Some(chrono::Duration::days(31)), + )?; + let result = AuthOidcResult { editor, token }; + Ok((result, created)) + }) + .map_err(|e: Error| FatcatError::from(e)) + { Ok((result, true)) => AuthOidcResponse::Created(result), Ok((result, false)) => AuthOidcResponse::Found(result), - Err(Error(ErrorKind::Diesel(e), _)) => AuthOidcResponse::BadRequest(ErrorResponse { - success: false, - error: "database".to_string(), - message: e.to_string(), - }), - Err(Error(ErrorKind::Uuid(e), _)) => AuthOidcResponse::BadRequest(ErrorResponse { - success: false, - error: "uuid".to_string(), - message: e.to_string(), - }), - Err(Error(ErrorKind::InvalidFatcatId(e), _)) => { - AuthOidcResponse::BadRequest(ErrorResponse { - success: false, - error: "fatcat-id".to_string(), - message: ErrorKind::InvalidFatcatId(e).to_string(), - }) - } - Err(Error(ErrorKind::MalformedExternalId(e), _)) => { - AuthOidcResponse::BadRequest(ErrorResponse { - success: false, - error: "field-syntax".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::MalformedChecksum(e), _)) => { - AuthOidcResponse::BadRequest(ErrorResponse { - success: false, - error: "field-syntax".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::NotInControlledVocabulary(e), _)) => { - AuthOidcResponse::BadRequest(ErrorResponse { - success: false, - error: "field-syntax".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::EditgroupAlreadyAccepted(e), _)) => { - AuthOidcResponse::BadRequest(ErrorResponse { - success: false, - error: "editgroup".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::InvalidCredentials(e), _)) => - // TODO: why can't I NotAuthorized here? - { - AuthOidcResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => { - AuthOidcResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::OtherBadRequest(e), _)) => { - AuthOidcResponse::BadRequest(ErrorResponse { - success: false, - error: "other".to_string(), - message: e.to_string(), - }) - } - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - AuthOidcResponse::GenericError(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }) - } + Err(fe) => match fe { + DatabaseError(_) | InternalError(_) => { + error!("{}", fe); + capture_fail(&fe); + AuthOidcResponse::GenericError(fe.into()) + } + _ => AuthOidcResponse::BadRequest(fe.into()), + }, }; Box::new(futures::done(Ok(ret))) } @@ -1342,70 +904,41 @@ impl Api for Server { context: &Context, ) -> Box<Future<Item = AuthCheckResponse, Error = ApiError> + Send> { let conn = self.db_pool.get().expect("db_pool error"); - let ret = match conn.transaction(|| { - let auth_context = self.auth_confectionary.require_auth( - &conn, - &context.auth_data, - Some("auth_check"), - )?; - if let Some(role) = role { - let role = match role.to_lowercase().as_ref() { - "superuser" => FatcatRole::Superuser, - "admin" => FatcatRole::Admin, - "editor" => FatcatRole::Editor, - "bot" => FatcatRole::Bot, - "human" => FatcatRole::Human, - "public" => FatcatRole::Public, - _ => bail!("unknown auth role: {}", role), + let ret = match conn + .transaction(|| { + let auth_context = self.auth_confectionary.require_auth( + &conn, + &context.auth_data, + Some("auth_check"), + )?; + if let Some(role) = role { + let role = match role.to_lowercase().as_ref() { + "superuser" => FatcatRole::Superuser, + "admin" => FatcatRole::Admin, + "editor" => FatcatRole::Editor, + "bot" => FatcatRole::Bot, + "human" => FatcatRole::Human, + "public" => FatcatRole::Public, + _ => bail!("unknown auth role: {}", role), + }; + auth_context.require_role(role)?; }; - auth_context.require_role(role)?; - }; - Ok(()) - }) { + Ok(()) + }) + .map_err(|e| FatcatError::from(e)) + { Ok(()) => AuthCheckResponse::Success(Success { success: true, message: "auth check successful!".to_string(), }), - Err(Error(ErrorKind::Diesel(e), _)) => AuthCheckResponse::BadRequest(ErrorResponse { - success: false, - error: "database".to_string(), - message: e.to_string(), - }), - Err(Error(ErrorKind::Uuid(e), _)) => AuthCheckResponse::BadRequest(ErrorResponse { - success: false, - error: "uuid".to_string(), - message: e.to_string(), - }), - Err(Error(ErrorKind::InvalidCredentials(e), _)) => { - AuthCheckResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => { - AuthCheckResponse::Forbidden(ErrorResponse { - success: false, - error: "auth".to_string(), - message: e.to_string(), - }) - } - Err(Error(ErrorKind::OtherBadRequest(e), _)) => { - AuthCheckResponse::BadRequest(ErrorResponse { - success: false, - error: "other".to_string(), - message: e.to_string(), - }) - } - Err(e) => { - error!("{}", e); - capture_error_chain(&e); - AuthCheckResponse::GenericError(ErrorResponse { - success: false, - error: "internal".to_string(), - message: e.to_string(), - }) - } + Err(fe) => match fe { + DatabaseError(_) | InternalError(_) => { + error!("{}", fe); + capture_fail(&fe); + AuthCheckResponse::GenericError(fe.into()) + } + _ => AuthCheckResponse::BadRequest(fe.into()), + }, }; Box::new(futures::done(Ok(ret))) } diff --git a/rust/src/entity_crud.rs b/rust/src/entity_crud.rs index 618bd2ff..43ed2083 100644 --- a/rust/src/entity_crud.rs +++ b/rust/src/entity_crud.rs @@ -308,7 +308,7 @@ macro_rules! generic_db_create { ($ident_table: ident, $edit_table: ident) => { fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow> { if self.redirect.is_some() { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "can't create an entity that redirects from the start".to_string()).into()); } let rev_id = self.db_insert_rev(conn)?; @@ -336,7 +336,7 @@ macro_rules! generic_db_create_batch { models: &[&Self], ) -> Result<Vec<Self::EditRow>> { if models.iter().any(|m| m.redirect.is_some()) { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "can't create an entity that redirects from the start".to_string(), ) .into()); @@ -383,7 +383,7 @@ macro_rules! generic_db_update { let no_redirect: Option<Uuid> = None; // TODO: is this actually true? or should we allow updates in the same editgroup? if current.is_live != true { - return Err(ErrorKind::InvalidEntityStateTransform( + return Err(FatcatError::InvalidEntityStateTransform( "can't update an entity that doesn't exist yet".to_string()).into()); } // Don't set prev_rev if current status is redirect @@ -395,14 +395,14 @@ macro_rules! generic_db_update { if self.state.is_none() { if Some(ident.to_string()) == self.redirect { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "tried to redirect entity to itself".to_string()).into()); } // special case: redirect to another entity if let Some(ref redirect_ident) = self.redirect { let redirect_ident = FatcatId::from_str(&redirect_ident)?.to_uuid(); if Some(redirect_ident) == current.redirect_id { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "redundantly redirecting entity to it's current target currently isn't supported".to_string()).into()); } // TODO: if we get a diesel not-found here, should be a special error response? @@ -411,7 +411,7 @@ macro_rules! generic_db_update { // there is no race condition on this check because WIP -> is_live=true is // a one-way operation // XXX: - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "attempted to redirect to a WIP entity".to_string()).into()); } // Note: there is a condition where the target is already a redirect, but we @@ -433,7 +433,7 @@ macro_rules! generic_db_update { if let Some(ref rev_id) = self.revision { let rev_id = Uuid::from_str(&rev_id)?; if Some(rev_id) == current.rev_id { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "reverted entity to it's current state; this isn't currently supported".to_string()).into()); } let edit: Self::EditRow = insert_into($edit_table::table) @@ -476,14 +476,14 @@ macro_rules! generic_db_delete { ) -> Result<Self::EditRow> { let current: Self::IdentRow = $ident_table::table.find(ident.to_uuid()).first(conn)?; if current.is_live != true { - return Err(ErrorKind::InvalidEntityStateTransform( + return Err(FatcatError::InvalidEntityStateTransform( "can't update an entity that doesn't exist yet; delete edit object instead" .to_string(), ) .into()); } if current.state()? == EntityState::Deleted { - return Err(ErrorKind::InvalidEntityStateTransform( + return Err(FatcatError::InvalidEntityStateTransform( "entity was already deleted".to_string(), ) .into()); @@ -556,7 +556,7 @@ macro_rules! generic_db_delete_edit { .limit(1) .get_results(conn)?; if accepted_rows.len() != 0 { - return Err(ErrorKind::EditgroupAlreadyAccepted( + return Err(FatcatError::EditgroupAlreadyAccepted( "attempted to delete an already accepted edit".to_string(), ) .into()); @@ -636,7 +636,7 @@ macro_rules! generic_db_accept_edits_batch { .get_result(conn)?; if forward_recursive_redirects != 0 { // TODO: revert transaction? - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "one or more (forward) recurisve redirects".to_string(), ) .into()); @@ -655,7 +655,7 @@ macro_rules! generic_db_accept_edits_batch { .get_result(conn)?; if backward_recursive_redirects != 0 { // TODO: revert transaction? - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "one or more (backward) recurisve redirects".to_string(), ) .into()); @@ -830,7 +830,7 @@ impl EntityCrud for ContainerEntity { } if models.iter().any(|m| m.name.is_none()) { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "name is required for all Container entities".to_string(), ) .into()); @@ -941,7 +941,7 @@ impl EntityCrud for CreatorEntity { } if models.iter().any(|m| m.display_name.is_none()) { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "display_name is required for all Creator entities".to_string(), ) .into()); @@ -1469,7 +1469,7 @@ impl EntityCrud for WebcaptureEntity { } } if entity.timestamp.is_none() || entity.original_url.is_none() { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "timestamp and original_url are required for webcapture entities".to_string(), ) .into()); @@ -1678,7 +1678,7 @@ impl EntityCrud for ReleaseEntity { fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow> { if self.redirect.is_some() { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "can't create an entity that redirects from the start".to_string(), ) .into()); @@ -1696,7 +1696,7 @@ impl EntityCrud for ReleaseEntity { // This isn't the generic implementation because we need to create Work entities for each // of the release entities passed (at least in the common case) if models.iter().any(|m| m.redirect.is_some()) { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "can't create an entity that redirects from the start".to_string(), ) .into()); @@ -1923,7 +1923,7 @@ impl EntityCrud for ReleaseEntity { } if models.iter().any(|m| m.title.is_none()) { - return Err(ErrorKind::OtherBadRequest( + return Err(FatcatError::OtherBadRequest( "title is required for all Release entities".to_string(), ) .into()); diff --git a/rust/src/errors.rs b/rust/src/errors.rs index 0b966e93..80535fef 100644 --- a/rust/src/errors.rs +++ b/rust/src/errors.rs @@ -1,55 +1,198 @@ -//! Crate-specific Result, Error, and ErrorKind types (using `error_chain`) - -error_chain! { - foreign_links { Fmt(::std::fmt::Error); - Diesel(::diesel::result::Error); - R2d2(::diesel::r2d2::Error); - Uuid(::uuid::ParseError); - Io(::std::io::Error) #[cfg(unix)]; - Serde(::serde_json::Error); - Utf8Decode(::std::string::FromUtf8Error); - StringDecode(::data_encoding::DecodeError); - } - errors { - InvalidFatcatId(id: String) { - description("invalid fatcat identifier syntax") - display("invalid fatcat identifier (expect 26-char base32 encoded): {}", id) - } - MalformedExternalId(id: String) { - description("external identifier doesn't match required pattern") - display("external identifier doesn't match required pattern: {}", id) - } - MalformedChecksum(hash: String) { - description("checksum doesn't match required pattern (hex encoding)") - display("checksum doesn't match required pattern (hex encoding): {}", hash) - } - NotInControlledVocabulary(word: String) { - description("word or type not correct for controlled vocabulary") - display("word or type not correct for controlled vocabulary") - } - EditgroupAlreadyAccepted(id: String) { - description("editgroup was already accepted") - display("attempted to accept or mutate an editgroup which was already accepted: {}", id) - } - MissingOrMultipleExternalId(message: String) { - description("external identifiers missing or multiple specified") - display("external identifiers missing or multiple specified; please supply exactly one") - } - InvalidEntityStateTransform(message: String) { - description("Invalid Entity State Transform") - display("tried to mutate an entity which was not in an appropriate state: {}", message) - } - InvalidCredentials(message: String) { - description("auth token was missing, expired, revoked, or corrupt") - display("auth token was missing, expired, revoked, or corrupt: {}", message) +//! Crate-specific errors (using `failure`) +//! +//! The justification for this complexity is that we need to return correct HTTP error types as +//! well as helpful error messages in API responses. + +// Design goals: +// - be able to call '?' on random things and collect them in endpoint handlers +// - be able to 'bail!()' and 'ensure!()' and have those end up as InternalErrors +// - do conversion into ErrorResponse model in this file, not endpoint handlers, and ErrorReponse +// should have good context about the error +// +// Plan: +// - use .map_err() to convert to the correct type +// - map to + +pub use failure::Error; +use failure::Fail; +use fatcat_api_spec::models; +use std::result; + +/// A type alias for handling errors throughout this crate +pub type Result<T> = result::Result<T, Error>; + +#[derive(Debug, Fail, Clone)] +pub enum FatcatError { + #[fail(display = "no such {} found: {}", _0, _1)] + NotFound(String, String), + + #[fail( + display = "invalid fatcat identifier (expect 26-char base32 encoded): {}", + _0 + )] + InvalidFatcatId(String), + + #[fail( + display = "external identifier doesn't match required pattern for a {}: {}", + _0, _1 + )] + MalformedExternalId(String, String), + + #[fail( + display = "checksum doesn't match required pattern ({} in hex encoding): {}", + _0, _1 + )] + MalformedChecksum(String, String), + + #[fail(display = "not a valid UUID: {}", _0)] + MalformedUuid(String), + + #[fail(display = "'{}' is not a an known/acceptable '{}' value", _1, _0)] + NotInControlledVocabulary(String, String), // vocab, word + + #[fail( + display = "attempted to accept or mutate an editgroup which was already accepted: {}", + _0 + )] + EditgroupAlreadyAccepted(String), + + #[fail( + display = "external identifiers missing or multiple specified; please supply exactly one: {}", + _0 + )] + MissingOrMultipleExternalId(String), + + #[fail(display = "tried to mutate an entity into impossible state: {}", _0)] + InvalidEntityStateTransform(String), + + #[fail( + display = "auth token was missing, expired, revoked, or corrupt: {}", + _0 + )] + InvalidCredentials(String), + + #[fail(display = "editor account doesn't have authorization: {}", _0)] + InsufficientPrivileges(String), + + #[fail( + display = "broke a constraint or made an otherwise invalid request: {}", + _0 + )] + // Utf8Decode, StringDecode, Uuid + BadRequest(String), + + #[fail(display = "unexpected database error: {}", _0)] + // Diesel constraint that we think is a user error + ConstraintViolation(String), + + // TODO: can these hold context instead of Inner? + #[fail(display = "unexpected database error: {}", _0)] + // other Diesel, R2d2 errors which we don't think are user errors (eg, connection failure) + DatabaseError(String), + + #[fail(display = "unexpected internal error: {}", _0)] + // Fmt, Io, Serde, + InternalError(String), + + // TODO: backwards compat; need to refactor out + #[fail(display = "unexpected internal error: {}", _0)] + OtherBadRequest(String), // NOTE: this enum is not exhaustive and shouldn't be matched over! +} + +impl Into<models::ErrorResponse> for FatcatError { + /// Format an error as an API response (ErrorResponse model, used by all HTTP 4xx and 5xx + /// responses) + fn into(self) -> models::ErrorResponse { + // TODO: something more complex? context? + models::ErrorResponse { + success: false, + error: self.name().unwrap_or("other").to_string(), + message: self.to_string(), } - InsufficientPrivileges(message: String) { - description("editor account doesn't have authorization") - display("editor account doesn't have authorization: {}", message) + } +} + +impl From<diesel::result::Error> for FatcatError { + /// The "not found" case should be handled in user code + fn from(inner: diesel::result::Error) -> FatcatError { + FatcatError::DatabaseError(inner.to_string()) + } +} + +impl From<std::fmt::Error> for FatcatError { + fn from(inner: std::fmt::Error) -> FatcatError { + FatcatError::InternalError(inner.to_string()) + } +} + +impl From<diesel::r2d2::Error> for FatcatError { + fn from(inner: diesel::r2d2::Error) -> FatcatError { + FatcatError::InternalError(inner.to_string()) + } +} + +impl From<uuid::ParseError> for FatcatError { + fn from(inner: uuid::ParseError) -> FatcatError { + FatcatError::MalformedUuid(inner.to_string()) + } +} + +impl From<serde_json::Error> for FatcatError { + fn from(inner: serde_json::Error) -> FatcatError { + FatcatError::InternalError(inner.to_string()) + } +} + +impl From<std::string::FromUtf8Error> for FatcatError { + fn from(inner: std::string::FromUtf8Error) -> FatcatError { + FatcatError::InternalError(inner.to_string()) + } +} + +impl From<data_encoding::DecodeError> for FatcatError { + fn from(inner: data_encoding::DecodeError) -> FatcatError { + FatcatError::InternalError(inner.to_string()) + } +} + +// A big catchall! +impl From<failure::Error> for FatcatError { + fn from(error: failure::Error) -> FatcatError { + // TODO: I think it should be possible to match here? regardless, this is *super* janky + if let Some(_) = error.downcast_ref::<FatcatError>() { + return error.downcast::<FatcatError>().unwrap(); } - OtherBadRequest(message: String) { - description("catch-all error for bad or unallowed requests") - display("broke a constraint or made an otherwise invalid request: {}", message) + if let Some(_) = error.downcast_ref::<std::fmt::Error>() { + return error.downcast::<std::fmt::Error>().unwrap().into(); } + // TODO: more downcast catching? + FatcatError::InternalError(error.to_string()) } } + +/* +// Allows adding more context via a String +impl From<Context<String>> for FatcatError { + fn from(inner: Context<String>) -> FatcatError { + FatcatError::InternalError(inner.context()) + } +} + +// Allows adding more context via a &str +impl From<Context<&'static str>> for FatcatError { + fn from(inner: Context<&'static str>) -> FatcatError { + FatcatError::InternalError(inner.context().to_string()) + } +} +*/ + +/* XXX: +Fmt(::std::fmt::Error); +Diesel(::diesel::result::Error); +R2d2(::diesel::r2d2::Error); +Uuid(::uuid::ParseError); +Io(::std::io::Error) #[cfg(unix)]; +Serde(::serde_json::Error); +Utf8Decode(::std::string::FromUtf8Error); +StringDecode(::data_encoding::DecodeError); +*/ diff --git a/rust/src/identifiers.rs b/rust/src/identifiers.rs index 073464aa..bf19a243 100644 --- a/rust/src/identifiers.rs +++ b/rust/src/identifiers.rs @@ -53,12 +53,12 @@ impl FatcatId { /// Convert fatcat IDs (base32 strings) to UUID pub fn fcid2uuid(fcid: &str) -> Result<Uuid> { if fcid.len() != 26 { - return Err(ErrorKind::InvalidFatcatId(fcid.to_string()).into()); + return Err(FatcatError::InvalidFatcatId(fcid.to_string()).into()); } let mut raw = vec![0; 16]; BASE32_NOPAD .decode_mut(fcid.to_uppercase().as_bytes(), &mut raw) - .map_err(|_dp| ErrorKind::InvalidFatcatId(fcid.to_string()))?; + .map_err(|_dp| FatcatError::InvalidFatcatId(fcid.to_string()))?; // unwrap() is safe here, because we know raw is always 16 bytes Ok(Uuid::from_bytes(&raw).unwrap()) } @@ -76,11 +76,10 @@ pub fn check_username(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedExternalId(format!( - "not a valid username: '{}' (expected, eg, 'AcidBurn')", - raw - )) - .into()) + Err(FatcatError::MalformedExternalId( + "username (expected, eg, 'AcidBurn')".to_string(), + raw.to_string(), + ))? } } @@ -112,11 +111,10 @@ pub fn check_pmcid(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedExternalId(format!( - "not a valid PubMed Central ID (PMCID): '{}' (expected, eg, 'PMC12345')", - raw - )) - .into()) + Err(FatcatError::MalformedExternalId( + "PubMed Central ID (PMCID) (expected, eg, 'PMC12345')".to_string(), + raw.to_string(), + ))? } } @@ -127,11 +125,10 @@ pub fn check_pmid(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedExternalId(format!( - "not a valid PubMed ID (PMID): '{}' (expected, eg, '1234')", - raw - )) - .into()) + Err(FatcatError::MalformedExternalId( + "PubMed ID (PMID) (expected, eg, '1234')".to_string(), + raw.to_string(), + ))? } } @@ -142,11 +139,10 @@ pub fn check_wikidata_qid(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedExternalId(format!( - "not a valid Wikidata QID: '{}' (expected, eg, 'Q1234')", - raw - )) - .into()) + Err(FatcatError::MalformedExternalId( + "Wikidata QID (expected, eg, 'Q1234')".to_string(), + raw.to_string(), + ))? } } @@ -157,11 +153,10 @@ pub fn check_doi(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedExternalId(format!( - "not a valid DOI: '{}' (expected, eg, '10.1234/aksjdfh')", - raw - )) - .into()) + Err(FatcatError::MalformedExternalId( + "DOI (expected, eg, '10.1234/aksjdfh')".to_string(), + raw.to_string(), + ))? } } @@ -172,11 +167,10 @@ pub fn check_issn(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedExternalId(format!( - "not a valid ISSN: '{}' (expected, eg, '1234-5678')", - raw - )) - .into()) + Err(FatcatError::MalformedExternalId( + "ISSN (expected, eg, '1234-5678')".to_string(), + raw.to_string(), + ))? } } @@ -187,11 +181,10 @@ pub fn check_orcid(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedExternalId(format!( - "not a valid ORCID: '{}' (expected, eg, '0123-4567-3456-6789')", - raw - )) - .into()) + Err(FatcatError::MalformedExternalId( + "ORCID (expected, eg, '0123-4567-3456-6789')".to_string(), + raw.to_string(), + ))? } } @@ -210,11 +203,10 @@ pub fn check_md5(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedChecksum(format!( - "not a valid MD5: '{}' (expected lower-case hex, eg, '1b39813549077b2347c0f370c3864b40')", - raw - )) - .into()) + Err(FatcatError::MalformedChecksum( + "MD5".to_string(), + raw.to_string(), + ))? } } @@ -234,11 +226,10 @@ pub fn check_sha1(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedChecksum(format!( - "not a valid SHA-1: '{}' (expected lower-case hex, eg, 'e9dd75237c94b209dc3ccd52722de6931a310ba3')", - raw - )) - .into()) + Err(FatcatError::MalformedChecksum( + "SHA-1".to_string(), + raw.to_string(), + ))? } } @@ -258,11 +249,10 @@ pub fn check_sha256(raw: &str) -> Result<()> { if RE.is_match(raw) { Ok(()) } else { - Err(ErrorKind::MalformedChecksum(format!( - "not a valid SHA-256: '{}' (expected lower-case hex, eg, 'cb1c378f464d5935ddaa8de28446d82638396c61f042295d7fb85e3cccc9e452')", - raw - )) - .into()) + Err(FatcatError::MalformedChecksum( + "SHA-256".to_string(), + raw.to_string(), + ))? } } @@ -333,11 +323,10 @@ pub fn check_release_type(raw: &str) -> Result<()> { return Ok(()); } } - Err(ErrorKind::NotInControlledVocabulary(format!( - "not a valid release_type: '{}' (expected a CSL type, eg, 'article-journal', 'book')", - raw - )) - .into()) + Err(FatcatError::NotInControlledVocabulary( + "release_type (should be valid CSL, like 'article-journal')".to_string(), + raw.to_string(), + ))? } #[test] @@ -375,11 +364,10 @@ pub fn check_contrib_role(raw: &str) -> Result<()> { return Ok(()); } } - Err(ErrorKind::NotInControlledVocabulary(format!( - "not a valid contrib.role: '{}' (expected a CSL type, eg, 'author', 'editor')", - raw - )) - .into()) + Err(FatcatError::NotInControlledVocabulary( + "contrib.role (should be valid CSL, like 'author', 'editor')".to_string(), + raw.to_string(), + ))? } #[test] diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 90a1f250..eeb7e83f 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2,13 +2,13 @@ #![recursion_limit = "128"] #[macro_use] -extern crate error_chain; -#[macro_use] extern crate diesel; #[macro_use] extern crate log; #[macro_use] extern crate lazy_static; +#[macro_use] +extern crate failure; pub mod auth; pub mod database_models; |