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 /rust | |
| parent | a192b0b84e46179a8f28218dfcbb5eb4e28dbf9d (diff) | |
| download | fatcat-67c3460d251a4e559a1126b5fe66fe996f840010.tar.gz fatcat-67c3460d251a4e559a1126b5fe66fe996f840010.zip | |
HUGE refactor of error types (to use failure)
Diffstat (limited to 'rust')
| -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; | 
