aboutsummaryrefslogtreecommitdiffstats
path: root/rust/src
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src')
-rw-r--r--rust/src/auth.rs41
-rw-r--r--rust/src/bin/fatcat-export.rs16
-rw-r--r--rust/src/editing.rs4
-rw-r--r--rust/src/endpoint_handlers.rs14
-rw-r--r--rust/src/endpoints.rs955
-rw-r--r--rust/src/entity_crud.rs36
-rw-r--r--rust/src/errors.rs243
-rw-r--r--rust/src/identifiers.rs112
-rw-r--r--rust/src/lib.rs4
9 files changed, 546 insertions, 879 deletions
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(&param).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(&param)?,
};
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(&param).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(&param).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;