diff options
Diffstat (limited to 'rust/src')
| -rw-r--r-- | rust/src/bin/fatcatd.rs | 3 | ||||
| -rw-r--r-- | rust/src/endpoint_handlers.rs | 194 | ||||
| -rw-r--r-- | rust/src/endpoints.rs | 212 | ||||
| -rw-r--r-- | rust/src/entity_crud.rs | 38 | ||||
| -rw-r--r-- | rust/src/identifiers.rs | 87 | 
5 files changed, 438 insertions, 96 deletions
diff --git a/rust/src/bin/fatcatd.rs b/rust/src/bin/fatcatd.rs index 6f5610f0..b27ff911 100644 --- a/rust/src/bin/fatcatd.rs +++ b/rust/src/bin/fatcatd.rs @@ -35,8 +35,7 @@ impl AfterMiddleware for XClacksOverheadMiddleware {  /// Create custom server, wire it to the autogenerated router,  /// and pass it to the web server.  fn main() -> Result<()> { -    let _matches = App::new("server") -        .get_matches(); +    let _matches = App::new("server").get_matches();      dotenv::dotenv().ok(); diff --git a/rust/src/endpoint_handlers.rs b/rust/src/endpoint_handlers.rs index 64b6ed62..91ea2393 100644 --- a/rust/src/endpoint_handlers.rs +++ b/rust/src/endpoint_handlers.rs @@ -26,7 +26,6 @@ macro_rules! entity_auto_batch_handler {              entity_list: &[models::$model],              editor_id: FatcatId,          ) -> Result<Editgroup> { -              let editgroup_row = editgroup.db_create(conn, true)?;              let editgroup_id = FatcatId::from_uuid(&editgroup_row.id);              let edit_context = make_edit_context(editor_id, editgroup_id, true)?; @@ -39,7 +38,7 @@ macro_rules! entity_auto_batch_handler {                  .get_result(conn)?;              self.get_editgroup_handler(conn, editgroup_id)          } -    } +    };  }  pub fn get_release_files( @@ -262,6 +261,9 @@ impl Server {          jstor: &Option<String>,          ark: &Option<String>,          mag: &Option<String>, +        doaj: &Option<String>, +        dblp: &Option<String>, +        oai: &Option<String>,          expand_flags: ExpandFlags,          hide_flags: HideFlags,      ) -> Result<ReleaseEntity> { @@ -276,8 +278,11 @@ impl Server {              jstor,              ark,              mag, +            doaj, +            dblp, +            oai,          ) { -            (Some(doi), None, None, None, None, None, None, None, None, None) => { +            (Some(doi), None, None, None, None, None, None, None, None, None, None, None, None) => {                  // DOIs always stored lower-case; lookups are case-insensitive                  let doi = doi.to_lowercase();                  check_doi(&doi)?; @@ -288,7 +293,21 @@ impl Server {                      .filter(release_ident::redirect_id.is_null())                      .first(conn)?              } -            (None, Some(wikidata_qid), None, None, None, None, None, None, None, None) => { +            ( +                None, +                Some(wikidata_qid), +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +            ) => {                  check_wikidata_qid(wikidata_qid)?;                  release_ident::table                      .inner_join(release_rev::table) @@ -297,7 +316,21 @@ impl Server {                      .filter(release_ident::redirect_id.is_null())                      .first(conn)?              } -            (None, None, Some(isbn13), None, None, None, None, None, None, None) => { +            ( +                None, +                None, +                Some(isbn13), +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +            ) => {                  check_isbn13(isbn13)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) =                      release_rev::table @@ -310,7 +343,21 @@ impl Server {                          .first(conn)?;                  (ident, rev)              } -            (None, None, None, Some(pmid), None, None, None, None, None, None) => { +            ( +                None, +                None, +                None, +                Some(pmid), +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +            ) => {                  check_pmid(pmid)?;                  release_ident::table                      .inner_join(release_rev::table) @@ -319,7 +366,21 @@ impl Server {                      .filter(release_ident::redirect_id.is_null())                      .first(conn)?              } -            (None, None, None, None, Some(pmcid), None, None, None, None, None) => { +            ( +                None, +                None, +                None, +                None, +                Some(pmcid), +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +            ) => {                  check_pmcid(pmcid)?;                  release_ident::table                      .inner_join(release_rev::table) @@ -328,7 +389,21 @@ impl Server {                      .filter(release_ident::redirect_id.is_null())                      .first(conn)?              } -            (None, None, None, None, None, Some(core), None, None, None, None) => { +            ( +                None, +                None, +                None, +                None, +                None, +                Some(core), +                None, +                None, +                None, +                None, +                None, +                None, +                None, +            ) => {                  check_core_id(core)?;                  release_ident::table                      .inner_join(release_rev::table) @@ -337,7 +412,21 @@ impl Server {                      .filter(release_ident::redirect_id.is_null())                      .first(conn)?              } -            (None, None, None, None, None, None, Some(arxiv), None, None, None) => { +            ( +                None, +                None, +                None, +                None, +                None, +                None, +                Some(arxiv), +                None, +                None, +                None, +                None, +                None, +                None, +            ) => {                  // TODO: this allows only lookup by full, versioned arxiv identifier. Probably also                  // want to allow lookup by "work" style identifier?                  check_arxiv_id(arxiv)?; @@ -352,7 +441,21 @@ impl Server {                          .first(conn)?;                  (ident, rev)              } -            (None, None, None, None, None, None, None, Some(jstor), None, None) => { +            ( +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                Some(jstor), +                None, +                None, +                None, +                None, +                None, +            ) => {                  check_jstor_id(jstor)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) =                      release_rev::table @@ -365,7 +468,7 @@ impl Server {                          .first(conn)?;                  (ident, rev)              } -            (None, None, None, None, None, None, None, None, Some(ark), None) => { +            (None, None, None, None, None, None, None, None, Some(ark), None, None, None, None) => {                  check_ark_id(ark)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) =                      release_rev::table @@ -378,7 +481,7 @@ impl Server {                          .first(conn)?;                  (ident, rev)              } -            (None, None, None, None, None, None, None, None, None, Some(mag)) => { +            (None, None, None, None, None, None, None, None, None, Some(mag), None, None, None) => {                  check_mag_id(mag)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) =                      release_rev::table @@ -391,6 +494,73 @@ impl Server {                          .first(conn)?;                  (ident, rev)              } +            ( +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                Some(doaj), +                None, +                None, +            ) => { +                check_doaj_id(doaj)?; +                let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) = +                    release_rev::table +                        .inner_join(release_ident::table) +                        .inner_join(release_rev_extid::table) +                        .filter(release_rev_extid::extid_type.eq("doaj".to_string())) +                        .filter(release_rev_extid::value.eq(doaj)) +                        .filter(release_ident::is_live.eq(true)) +                        .filter(release_ident::redirect_id.is_null()) +                        .first(conn)?; +                (ident, rev) +            } +            ( +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                Some(dblp), +                None, +            ) => { +                check_dblp_id(dblp)?; +                let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) = +                    release_rev::table +                        .inner_join(release_ident::table) +                        .inner_join(release_rev_extid::table) +                        .filter(release_rev_extid::extid_type.eq("dblp".to_string())) +                        .filter(release_rev_extid::value.eq(dblp)) +                        .filter(release_ident::is_live.eq(true)) +                        .filter(release_ident::redirect_id.is_null()) +                        .first(conn)?; +                (ident, rev) +            } +            (None, None, None, None, None, None, None, None, None, None, None, None, Some(oai)) => { +                check_oai_id(oai)?; +                let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) = +                    release_rev::table +                        .inner_join(release_ident::table) +                        .inner_join(release_rev_extid::table) +                        .filter(release_rev_extid::extid_type.eq("oai".to_string())) +                        .filter(release_rev_extid::value.eq(oai)) +                        .filter(release_ident::is_live.eq(true)) +                        .filter(release_ident::redirect_id.is_null()) +                        .first(conn)?; +                (ident, rev) +            }              _ => {                  return Err(                      FatcatError::MissingOrMultipleExternalId("in lookup".to_string()).into(), diff --git a/rust/src/endpoints.rs b/rust/src/endpoints.rs index 0dd69efd..0dd232c6 100644 --- a/rust/src/endpoints.rs +++ b/rust/src/endpoints.rs @@ -76,7 +76,6 @@ macro_rules! wrap_entity_handlers {      $delete_edit_fn:ident, $delete_edit_resp:ident, $get_rev_fn:ident, $get_rev_resp:ident,      $get_redirects_fn:ident, $get_redirects_resp:ident,      $model:ident) => { -          fn $get_fn(              &self,              ident: String, @@ -99,11 +98,12 @@ macro_rules! wrap_entity_handlers {                          let mut entity = $model::db_get(&conn, entity_id, hide_flags)?;                          entity.db_expand(&conn, expand_flags)?;                          Ok(entity) -                    }, +                    }                  } -            })().map_err(|e| FatcatError::from(e)) { -                Ok(entity) => -                    $get_resp::FoundEntity(entity), +            })() +            .map_err(|e| FatcatError::from(e)) +            { +                Ok(entity) => $get_resp::FoundEntity(entity),                  Err(fe) => generic_err_responses!(fe, $get_resp),              };              Box::new(futures::done(Ok(ret))) @@ -116,19 +116,27 @@ macro_rules! wrap_entity_handlers {              context: &Context,          ) -> Box<dyn Future<Item = $post_resp, 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(stringify!($post_fn)))?; -                auth_context.require_role(FatcatRole::Editor)?; -                auth_context.require_editgroup(&conn, editgroup_id)?; -                let edit_context = make_edit_context(auth_context.editor_id, editgroup_id, false)?; -                edit_context.check(&conn)?; -                entity.db_create(&conn, &edit_context)?.into_model() -            }).map_err(|e| FatcatError::from(e)) { +            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(stringify!($post_fn)), +                    )?; +                    auth_context.require_role(FatcatRole::Editor)?; +                    auth_context.require_editgroup(&conn, editgroup_id)?; +                    let edit_context = +                        make_edit_context(auth_context.editor_id, editgroup_id, false)?; +                    edit_context.check(&conn)?; +                    entity.db_create(&conn, &edit_context)?.into_model() +                }) +                .map_err(|e| FatcatError::from(e)) +            {                  Ok(edit) => {                      self.metrics.incr("entities.created").ok();                      $post_resp::CreatedEntity(edit) -                }, +                }                  Err(fe) => generic_auth_err_responses!(fe, $post_resp),              };              Box::new(futures::done(Ok(ret))) @@ -141,7 +149,11 @@ macro_rules! wrap_entity_handlers {          ) -> Box<dyn Future<Item = $post_auto_batch_resp, 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(stringify!($post_auto_batch_fn)))?; +                let auth_context = self.auth_confectionary.require_auth( +                    &conn, +                    &context.auth_data, +                    Some(stringify!($post_auto_batch_fn)), +                )?;                  auth_context.require_role(FatcatRole::Admin)?;                  let mut editgroup = auto_batch.editgroup.clone();                  // TODO: this is duplicated code with create_editgroup() @@ -151,16 +163,21 @@ macro_rules! wrap_entity_handlers {                              && !auth_context.has_role(FatcatRole::Admin)                          {                              return Err(FatcatError::InsufficientPrivileges( -                                "not authorized to create editgroups in others' names".to_string() -                            )) +                                "not authorized to create editgroups in others' names".to_string(), +                            ));                          }                      }                      None => {                          editgroup.editor_id = Some(auth_context.editor_id.to_string());                      }                  }; -                self.$post_auto_batch_handler(&conn, editgroup, &auto_batch.entity_list, auth_context.editor_id) -                    .map_err(|e| FatcatError::from(e)) +                self.$post_auto_batch_handler( +                    &conn, +                    editgroup, +                    &auto_batch.entity_list, +                    auth_context.editor_id, +                ) +                .map_err(|e| FatcatError::from(e))              }) {                  Ok(editgroup) => {                      // TODO: need a count helper on editgroup @@ -168,7 +185,7 @@ macro_rules! wrap_entity_handlers {                      self.metrics.incr("editgroup.created").ok();                      self.metrics.incr("editgroup.accepted").ok();                      $post_auto_batch_resp::CreatedEditgroup(editgroup) -                }, +                }                  Err(fe) => generic_auth_err_responses!(fe, $post_auto_batch_resp),              };              Box::new(futures::done(Ok(ret))) @@ -182,20 +199,30 @@ macro_rules! wrap_entity_handlers {              context: &Context,          ) -> Box<dyn Future<Item = $update_resp, 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(stringify!($update_fn)))?; -                auth_context.require_role(FatcatRole::Editor)?; -                let entity_id = FatcatId::from_str(&ident)?; -                auth_context.require_editgroup(&conn, editgroup_id)?; -                let edit_context = make_edit_context(auth_context.editor_id, editgroup_id, false)?; -                edit_context.check(&conn)?; -                entity.db_update(&conn, &edit_context, entity_id)?.into_model() -            }).map_err(|e| FatcatError::from(e)) { +            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(stringify!($update_fn)), +                    )?; +                    auth_context.require_role(FatcatRole::Editor)?; +                    let entity_id = FatcatId::from_str(&ident)?; +                    auth_context.require_editgroup(&conn, editgroup_id)?; +                    let edit_context = +                        make_edit_context(auth_context.editor_id, 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) => {                      self.metrics.incr("entities.updated").ok();                      $update_resp::UpdatedEntity(edit) -                }, +                }                  Err(fe) => generic_auth_err_responses!(fe, $update_resp),              };              Box::new(futures::done(Ok(ret))) @@ -208,20 +235,28 @@ macro_rules! wrap_entity_handlers {              context: &Context,          ) -> Box<dyn Future<Item = $delete_resp, 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(stringify!($delete_fn)))?; -                auth_context.require_role(FatcatRole::Editor)?; -                let entity_id = FatcatId::from_str(&ident)?; -                auth_context.require_editgroup(&conn, editgroup_id)?; -                let edit_context = make_edit_context(auth_context.editor_id, editgroup_id, false)?; -                edit_context.check(&conn)?; -                $model::db_delete(&conn, &edit_context, entity_id)?.into_model() -            }).map_err(|e| FatcatError::from(e)) { +            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(stringify!($delete_fn)), +                    )?; +                    auth_context.require_role(FatcatRole::Editor)?; +                    let entity_id = FatcatId::from_str(&ident)?; +                    auth_context.require_editgroup(&conn, editgroup_id)?; +                    let edit_context = +                        make_edit_context(auth_context.editor_id, 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) => {                      self.metrics.incr("entities.deleted").ok();                      $delete_resp::DeletedEntity(edit) -                }, +                }                  Err(fe) => generic_auth_err_responses!(fe, $delete_resp),              };              Box::new(futures::done(Ok(ret))) @@ -238,9 +273,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), +            })() +            .map_err(|e| FatcatError::from(e)) +            { +                Ok(history) => $get_history_resp::FoundEntityHistory(history),                  Err(fe) => generic_err_responses!(fe, $get_history_resp),              };              Box::new(futures::done(Ok(ret))) @@ -268,11 +304,12 @@ macro_rules! wrap_entity_handlers {                          let mut entity = $model::db_get_rev(&conn, rev_id, hide_flags)?;                          entity.db_expand(&conn, expand_flags)?;                          Ok(entity) -                    }, +                    }                  } -            })().map_err(|e| FatcatError::from(e)) { -                Ok(entity) => -                    $get_rev_resp::FoundEntityRevision(entity), +            })() +            .map_err(|e| FatcatError::from(e)) +            { +                Ok(entity) => $get_rev_resp::FoundEntityRevision(entity),                  Err(fe) => generic_err_responses!(fe, $get_rev_resp),              };              Box::new(futures::done(Ok(ret))) @@ -288,9 +325,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), +            })() +            .map_err(|e| FatcatError::from(e)) +            { +                Ok(edit) => $get_edit_resp::FoundEdit(edit),                  Err(fe) => generic_err_responses!(fe, $get_edit_resp),              };              Box::new(futures::done(Ok(ret))) @@ -306,23 +344,29 @@ macro_rules! wrap_entity_handlers {              let ret = match conn.transaction(|| {                  let editgroup_id = FatcatId::from_str(&editgroup_id)?;                  let edit_id = Uuid::from_str(&edit_id)?; -                let auth_context = self.auth_confectionary.require_auth(&conn, &context.auth_data, Some(stringify!($delete_edit_fn)))?; +                let auth_context = self.auth_confectionary.require_auth( +                    &conn, +                    &context.auth_data, +                    Some(stringify!($delete_edit_fn)), +                )?;                  auth_context.require_role(FatcatRole::Editor)?;                  let edit = $model::db_get_edit(&conn, edit_id)?;                  if !(edit.editgroup_id == editgroup_id.to_uuid()) {                      return Err(FatcatError::BadRequest( -                        "editgroup_id parameter didn't match that of the edit".to_string() -                    )) +                        "editgroup_id parameter didn't match that of the edit".to_string(), +                    ));                  }                  auth_context.require_editgroup(&conn, editgroup_id)?;                  // check for editgroup being deleted happens in db_delete_edit() -                $model::db_delete_edit(&conn, edit_id) -                    .map_err(|e| FatcatError::from(e)) +                $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) +                Ok(()) => $delete_edit_resp::DeletedEdit(Success { +                    success: true, +                    message: format!( +                        "Successfully deleted work-in-progress {} edit: {}", +                        stringify!($model), +                        edit_id +                    ),                  }),                  Err(fe) => generic_auth_err_responses!(fe, $delete_edit_resp),              }; @@ -340,15 +384,15 @@ 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), +            })() +            .map_err(|e: Error| FatcatError::from(e)) +            { +                Ok(redirects) => $get_redirects_resp::FoundEntityRedirects(redirects),                  Err(fe) => generic_err_responses!(fe, $get_redirects_resp),              };              Box::new(futures::done(Ok(ret)))          } - -    } +    };  }  macro_rules! wrap_lookup_handler { @@ -371,14 +415,16 @@ 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).map_err(|e| FatcatError::from(e)) { -                Ok(entity) => -                    $get_resp::FoundEntity(entity), +            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(fe) => generic_err_responses!(fe, $get_resp),              };              Box::new(futures::done(Ok(ret)))          } -    } +    };  }  macro_rules! wrap_fcid_handler { @@ -393,14 +439,15 @@ 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), +            })() +            .map_err(|e| FatcatError::from(e)) +            { +                Ok(entity) => $get_resp::Found(entity),                  Err(fe) => generic_err_responses!(fe, $get_resp),              };              Box::new(futures::done(Ok(ret)))          } -    } +    };  }  macro_rules! wrap_fcid_hide_handler { @@ -420,14 +467,15 @@ 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), +            })() +            .map_err(|e| FatcatError::from(e)) +            { +                Ok(entity) => $get_resp::Found(entity),                  Err(fe) => generic_err_responses!(fe, $get_resp),              };              Box::new(futures::done(Ok(ret)))          } -    } +    };  }  impl Api for Server { @@ -689,6 +737,9 @@ impl Api for Server {          jstor: Option<String>,          ark: Option<String>,          mag: Option<String>, +        doaj: Option<String>, +        dblp: Option<String>, +        oai: Option<String>,          expand: Option<String>,          hide: Option<String>,          _context: &Context, @@ -716,6 +767,9 @@ impl Api for Server {                  &jstor,                  &ark,                  &mag, +                &doaj, +                &dblp, +                &oai,                  expand_flags,                  hide_flags,              ) diff --git a/rust/src/entity_crud.rs b/rust/src/entity_crud.rs index 83dd26c9..0d72788d 100644 --- a/rust/src/entity_crud.rs +++ b/rust/src/entity_crud.rs @@ -334,7 +334,9 @@ macro_rules! generic_db_create {          fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow> {              if self.redirect.is_some() {                  return Err(FatcatError::BadRequest( -                    "can't create an entity that redirects from the start".to_string()).into()); +                    "can't create an entity that redirects from the start".to_string(), +                ) +                .into());              }              let rev_id = self.db_insert_rev(conn)?;              let ident: Uuid = insert_into($ident_table::table) @@ -351,7 +353,7 @@ macro_rules! generic_db_create {                  .get_result(conn)?;              Ok(edit)          } -    } +    };  }  macro_rules! generic_db_create_batch { @@ -764,7 +766,7 @@ macro_rules! generic_db_insert_rev {          fn db_insert_rev(&self, conn: &DbConn) -> Result<Uuid> {              Self::db_insert_revs(conn, &[self]).map(|id_list| id_list[0])          } -    } +    };  }  impl EntityCrud for ContainerEntity { @@ -1742,6 +1744,9 @@ impl EntityCrud for ReleaseEntity {                  jstor: None,                  ark: None,                  mag: None, +                doaj: None, +                dblp: None, +                oai: None,              },              refs: None,              contribs: None, @@ -2018,6 +2023,9 @@ impl EntityCrud for ReleaseEntity {              jstor: None,              ark: None,              mag: None, +            doaj: None, +            dblp: None, +            oai: None,          };          let extid_rows: Vec<ReleaseExtidRow> = release_rev_extid::table @@ -2030,6 +2038,9 @@ impl EntityCrud for ReleaseEntity {                  "jstor" => ext_ids.jstor = Some(extid_row.value),                  "ark" => ext_ids.ark = Some(extid_row.value),                  "mag" => ext_ids.mag = Some(extid_row.value), +                "doaj" => ext_ids.doaj = Some(extid_row.value), +                "dblp" => ext_ids.dblp = Some(extid_row.value), +                "oai" => ext_ids.oai = Some(extid_row.value),                  _ => (),              }          } @@ -2290,6 +2301,27 @@ impl EntityCrud for ReleaseEntity {                      value: extid.clone(),                  });              }; +            if let Some(extid) = &model.ext_ids.doaj { +                release_extid_rows.push(ReleaseExtidRow { +                    release_rev: *rev_id, +                    extid_type: "doaj".to_string(), +                    value: extid.clone(), +                }); +            }; +            if let Some(extid) = &model.ext_ids.dblp { +                release_extid_rows.push(ReleaseExtidRow { +                    release_rev: *rev_id, +                    extid_type: "dblp".to_string(), +                    value: extid.clone(), +                }); +            }; +            if let Some(extid) = &model.ext_ids.oai { +                release_extid_rows.push(ReleaseExtidRow { +                    release_rev: *rev_id, +                    extid_type: "oai".to_string(), +                    value: extid.clone(), +                }); +            };          }          for (model, rev_id) in models.iter().zip(rev_ids.iter()) { diff --git a/rust/src/identifiers.rs b/rust/src/identifiers.rs index 180dc43b..76f978f9 100644 --- a/rust/src/identifiers.rs +++ b/rust/src/identifiers.rs @@ -362,6 +362,93 @@ fn test_check_isbn13() {      assert!(check_isbn13("9781566199094").is_err());  } +pub fn check_doaj_id(raw: &str) -> Result<()> { +    lazy_static! { +        static ref RE: Regex = Regex::new(r"^[a-f0-9]{32}$").unwrap(); +    } +    if raw.is_ascii() && RE.is_match(raw) { +        Ok(()) +    } else { +        Err(FatcatError::MalformedChecksum( +            "DOAJ Article Identifier (expected, eg, 'e58f08a11ecb495ead55a44ad4f89808')" +                .to_string(), +            raw.to_string(), +        ))? +    } +} + +#[test] +fn test_check_doaj_id() { +    assert!(check_doaj_id("e58f08a11ecb495ead55a44ad4f89808").is_ok()); +    assert!(check_doaj_id("1b39813549077b2347c0f370c3864b40").is_ok()); +    assert!(check_doaj_id("1b39813549077b2347c0f370c3864b40 ").is_err()); +    assert!(check_doaj_id("1g39813549077b2347c0f370c3864b40").is_err()); +    assert!(check_doaj_id("1B39813549077B2347C0F370c3864b40").is_err()); +    assert!(check_doaj_id("1b39813549077b2347c0f370c3864b4").is_err()); +    assert!(check_doaj_id("1b39813549077b2347c0f370c3864b411").is_err()); +} + +pub fn check_dblp_id(raw: &str) -> Result<()> { +    lazy_static! { +        // TODO: what should this actually be? more or less restrictive? +        static ref RE: Regex = Regex::new(r"^[a-z]+/[a-zA-Z0-9]+/[a-zA-Z0-9/]+$").unwrap(); +    } +    if raw.is_ascii() && RE.is_match(raw) { +        Ok(()) +    } else { +        Err(FatcatError::MalformedChecksum( +            "dblp Article Key (expected, eg, 'journals/entcs/GoubaultM12')".to_string(), +            raw.to_string(), +        ))? +    } +} + +#[test] +fn test_check_dblp_id() { +    assert!(check_dblp_id("journals/entcs/GoubaultM12").is_ok()); +    assert!(check_dblp_id("journals/entcs/GoubaultM12").is_ok()); +    assert!(check_dblp_id("10.123*").is_err()); +    assert!(check_dblp_id("").is_err()); +} + +pub fn check_oai_id(raw: &str) -> Result<()> { +    lazy_static! { +        // http://www.openarchives.org/OAI/2.0/guidelines-oai-identifier.htm +        static ref RE: Regex = Regex::new(r"^oai:[a-zA-Z][a-zA-Z0-9\-]*(\.[a-zA-Z][a-zA-Z0-9\-]*)+:[a-zA-Z0-9\-_\.!~\*'\(\);/\?:@&=\+$,%]+$").unwrap(); +    } +    if raw.is_ascii() && RE.is_match(raw) { +        Ok(()) +    } else { +        Err(FatcatError::MalformedChecksum( +            "OAI-PMH identifier (expected, eg, 'oai:foo.org:some-local-id-54')".to_string(), +            raw.to_string(), +        ))? +    } +} + +#[test] +fn test_check_oai_id() { +    assert!(check_oai_id("journals/entcs/GoubaultM12").is_err()); +    assert!(check_oai_id("10.123*").is_err()); +    assert!(check_oai_id("").is_err()); +    assert!(check_oai_id("something:arXiv.org:hep-th/9901001").is_err()); // bad schema +    assert!(check_oai_id("oai:999:abc123").is_err()); // namespace-identifier must not start with digit +    assert!(check_oai_id("oai:wibble:abc123").is_err()); // namespace-identifier must be domain name +    assert!(check_oai_id("oai:wibble.org:ab cd").is_err()); // space not permitted (must be escaped as %20) +    assert!(check_oai_id("oai:wibble.org:ab#cd").is_err()); // # not permitted +    assert!(check_oai_id("oai:wibble.org:ab<cd").is_err()); // < not permitted +    // the "official" regex used above allows this case +    //assert!(check_oai_id("oai:wibble.org:ab%3ccd").is_err()); // < must be escaped at %3C not %3c + +    assert!(check_oai_id("oai:arXiv.org:hep-th/9901001").is_ok()); +    assert!(check_oai_id("oai:foo.org:some-local-id-53").is_ok()); +    assert!(check_oai_id("oai:FOO.ORG:some-local-id-53").is_ok()); +    assert!(check_oai_id("oai:foo.org:some-local-id-54").is_ok()); +    assert!(check_oai_id("oai:foo.org:Some-Local-Id-54").is_ok()); +    assert!(check_oai_id("oai:wibble.org:ab%20cd").is_ok()); +    assert!(check_oai_id("oai:wibble.org:ab?cd").is_ok()); +} +  pub fn check_issn(raw: &str) -> Result<()> {      lazy_static! {          static ref RE: Regex = Regex::new(r"^\d{4}-\d{3}[0-9X]$").unwrap();  | 
