diff options
| author | Bryan Newbold <bnewbold@robocracy.org> | 2021-10-12 19:56:08 -0700 | 
|---|---|---|
| committer | Bryan Newbold <bnewbold@robocracy.org> | 2021-10-13 16:21:30 -0700 | 
| commit | 11fdff350e0549d46a8a7b5e74451e08ce067cb2 (patch) | |
| tree | 20f48fb619e4267bb066340e249acc14649ae3da | |
| parent | 8d1f8d02f2e43c13d35b57ff3a625ab5de6c51c7 (diff) | |
| download | fatcat-11fdff350e0549d46a8a7b5e74451e08ce067cb2.tar.gz fatcat-11fdff350e0549d46a8a7b5e74451e08ce067cb2.zip | |
rust: implement scheman and API changes
| -rw-r--r-- | rust/src/editing_crud.rs | 17 | ||||
| -rw-r--r-- | rust/src/endpoint_handlers.rs | 194 | ||||
| -rw-r--r-- | rust/src/endpoints.rs | 71 | ||||
| -rw-r--r-- | rust/src/entity_crud.rs | 34 | ||||
| -rw-r--r-- | rust/src/identifiers.rs | 75 | 
5 files changed, 353 insertions, 38 deletions
| diff --git a/rust/src/editing_crud.rs b/rust/src/editing_crud.rs index 8da3cabd..fa39fdfb 100644 --- a/rust/src/editing_crud.rs +++ b/rust/src/editing_crud.rs @@ -28,6 +28,7 @@ use uuid::Uuid;  pub trait EditorCrud {      fn db_get(conn: &DbConn, editor_id: FatcatId) -> Result<EditorRow>; +    fn db_get_username(conn: &DbConn, username: &str) -> Result<EditorRow>;      fn db_create(&self, conn: &DbConn) -> Result<EditorRow>;      fn db_update_username(&self, conn: &DbConn, editor_id: FatcatId) -> Result<EditorRow>;  } @@ -45,6 +46,22 @@ impl EditorCrud for Editor {          Ok(editor)      } +    fn db_get_username(conn: &DbConn, username: &str) -> Result<EditorRow> { +        let editor: EditorRow = match editor::table +            .filter(editor::username.eq(username)) +            .get_result(conn) +        { +            Ok(ed) => ed, +            Err(diesel::result::Error::NotFound) => { +                return Err( +                    FatcatError::NotFound("editor".to_string(), username.to_string()).into(), +                ); +            } +            other => other?, +        }; +        Ok(editor) +    } +      fn db_create(&self, conn: &DbConn) -> Result<EditorRow> {          identifiers::check_username(&self.username)?;          let is_admin = self.is_admin.unwrap_or(false); diff --git a/rust/src/endpoint_handlers.rs b/rust/src/endpoint_handlers.rs index 141c414e..9a76652b 100644 --- a/rust/src/endpoint_handlers.rs +++ b/rust/src/endpoint_handlers.rs @@ -104,35 +104,71 @@ impl Server {          &self,          conn: &DbConn,          issnl: &Option<String>, +        issne: &Option<String>, +        issnp: &Option<String>, +        issn: &Option<String>,          wikidata_qid: &Option<String>,          expand_flags: ExpandFlags,          hide_flags: HideFlags,      ) -> Result<ContainerEntity> { -        let (ident, rev): (ContainerIdentRow, ContainerRevRow) = match (issnl, wikidata_qid) { -            (Some(issnl), None) => { -                check_issn(issnl)?; -                container_ident::table -                    .inner_join(container_rev::table) -                    .filter(container_rev::issnl.eq(&issnl)) -                    .filter(container_ident::is_live.eq(true)) -                    .filter(container_ident::redirect_id.is_null()) -                    .first(conn)? -            } -            (None, Some(wikidata_qid)) => { -                check_wikidata_qid(wikidata_qid)?; -                container_ident::table -                    .inner_join(container_rev::table) -                    .filter(container_rev::wikidata_qid.eq(&wikidata_qid)) -                    .filter(container_ident::is_live.eq(true)) -                    .filter(container_ident::redirect_id.is_null()) -                    .first(conn)? -            } -            _ => { -                return Err( -                    FatcatError::MissingOrMultipleExternalId("in lookup".to_string()).into(), -                ); -            } -        }; +        let (ident, rev): (ContainerIdentRow, ContainerRevRow) = +            match (issnl, issnp, issne, issn, wikidata_qid) { +                (Some(issnl), None, None, None, None) => { +                    check_issn(issnl)?; +                    container_ident::table +                        .inner_join(container_rev::table) +                        .filter(container_rev::issnl.eq(&issnl)) +                        .filter(container_ident::is_live.eq(true)) +                        .filter(container_ident::redirect_id.is_null()) +                        .first(conn)? +                } +                (None, Some(issnp), None, None, None) => { +                    check_issn(issnp)?; +                    container_ident::table +                        .inner_join(container_rev::table) +                        .filter(container_rev::issnp.eq(&issnp)) +                        .filter(container_ident::is_live.eq(true)) +                        .filter(container_ident::redirect_id.is_null()) +                        .first(conn)? +                } +                (None, None, Some(issne), None, None) => { +                    check_issn(issne)?; +                    container_ident::table +                        .inner_join(container_rev::table) +                        .filter(container_rev::issne.eq(&issne)) +                        .filter(container_ident::is_live.eq(true)) +                        .filter(container_ident::redirect_id.is_null()) +                        .first(conn)? +                } +                (None, None, None, Some(issn), None) => { +                    check_issn(issn)?; +                    container_ident::table +                        .inner_join(container_rev::table) +                        .filter( +                            container_rev::issnl +                                .eq(&issn) +                                .or(container_rev::issnp.eq(&issn)) +                                .or(container_rev::issne.eq(&issn)), +                        ) +                        .filter(container_ident::is_live.eq(true)) +                        .filter(container_ident::redirect_id.is_null()) +                        .first(conn)? +                } +                (None, None, None, None, Some(wikidata_qid)) => { +                    check_wikidata_qid(wikidata_qid)?; +                    container_ident::table +                        .inner_join(container_rev::table) +                        .filter(container_rev::wikidata_qid.eq(&wikidata_qid)) +                        .filter(container_ident::is_live.eq(true)) +                        .filter(container_ident::redirect_id.is_null()) +                        .first(conn)? +                } +                _ => { +                    return Err( +                        FatcatError::MissingOrMultipleExternalId("in lookup".to_string()).into(), +                    ); +                } +            };          let mut entity = ContainerEntity::db_from_row(conn, rev, Some(ident), hide_flags)?;          entity.db_expand(&conn, expand_flags)?; @@ -264,6 +300,7 @@ impl Server {          doaj: &Option<String>,          dblp: &Option<String>,          oai: &Option<String>, +        hdl: &Option<String>,          expand_flags: ExpandFlags,          hide_flags: HideFlags,      ) -> Result<ReleaseEntity> { @@ -281,8 +318,24 @@ impl Server {              doaj,              dblp,              oai, +            hdl,          ) { -            (Some(doi), None, None, None, None, None, None, None, None, None, None, None, None) => { +            ( +                Some(doi), +                None, +                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)?; @@ -307,6 +360,7 @@ impl Server {                  None,                  None,                  None, +                None,              ) => {                  check_wikidata_qid(wikidata_qid)?;                  release_ident::table @@ -330,6 +384,7 @@ impl Server {                  None,                  None,                  None, +                None,              ) => {                  check_isbn13(isbn13)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) = @@ -357,6 +412,7 @@ impl Server {                  None,                  None,                  None, +                None,              ) => {                  check_pmid(pmid)?;                  release_ident::table @@ -380,6 +436,7 @@ impl Server {                  None,                  None,                  None, +                None,              ) => {                  check_pmcid(pmcid)?;                  release_ident::table @@ -403,6 +460,7 @@ impl Server {                  None,                  None,                  None, +                None,              ) => {                  check_core_id(core)?;                  release_ident::table @@ -426,6 +484,7 @@ impl Server {                  None,                  None,                  None, +                None,              ) => {                  // TODO: this allows only lookup by full, versioned arxiv identifier. Probably also                  // want to allow lookup by "work" style identifier? @@ -455,6 +514,7 @@ impl Server {                  None,                  None,                  None, +                None,              ) => {                  check_jstor_id(jstor)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) = @@ -468,7 +528,22 @@ 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, +                None, +                None, +                None, +                Some(ark), +                None, +                None, +                None, +                None, +                None, +            ) => {                  check_ark_id(ark)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) =                      release_rev::table @@ -481,7 +556,22 @@ 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, +                None, +                None, +                None, +                Some(mag), +                None, +                None, +                None, +                None, +            ) => {                  check_mag_id(mag)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) =                      release_rev::table @@ -508,6 +598,7 @@ impl Server {                  Some(doaj),                  None,                  None, +                None,              ) => {                  check_doaj_id(doaj)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) = @@ -535,6 +626,7 @@ impl Server {                  None,                  Some(dblp),                  None, +                None,              ) => {                  check_dblp_key(dblp)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) = @@ -548,7 +640,22 @@ impl Server {                          .first(conn)?;                  (ident, rev)              } -            (None, None, None, None, None, None, None, None, None, None, None, None, Some(oai)) => { +            ( +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                Some(oai), +                None, +            ) => {                  check_oai_id(oai)?;                  let (rev, ident, _extid): (ReleaseRevRow, ReleaseIdentRow, ReleaseExtidRow) =                      release_rev::table @@ -561,6 +668,35 @@ impl Server {                          .first(conn)?;                  (ident, rev)              } +            ( +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                None, +                Some(hdl), +            ) => { +                let hdl = hdl.to_lowercase(); +                check_hdl(&hdl)?; +                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("hdl".to_string())) +                        .filter(release_rev_extid::value.eq(hdl)) +                        .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 0dd232c6..e04e7315 100644 --- a/rust/src/endpoints.rs +++ b/rust/src/endpoints.rs @@ -8,7 +8,7 @@  #![allow(clippy::redundant_closure_call)]  use crate::auth::FatcatRole; -use crate::database_models::EntityEditRow; +use crate::database_models::{EditorRow, EntityEditRow};  use crate::editing::*;  use crate::editing_crud::{EditgroupAnnotationCrud, EditgroupCrud, EditorCrud};  use crate::entity_crud::{EntityCrud, ExpandFlags, HideFlags}; @@ -657,12 +657,6 @@ impl Api for Server {      );      wrap_lookup_handler!( -        lookup_container, -        lookup_container_handler, -        LookupContainerResponse, -        issnl -    ); -    wrap_lookup_handler!(          lookup_creator,          lookup_creator_handler,          LookupCreatorResponse, @@ -696,6 +690,46 @@ impl Api for Server {      );      wrap_fcid_handler!(get_editor, get_editor_handler, GetEditorResponse); +    fn lookup_container( +        &self, +        issnl: Option<String>, +        issne: Option<String>, +        issnp: Option<String>, +        issn: Option<String>, +        wikidata_qid: Option<String>, +        expand: Option<String>, +        hide: Option<String>, +        _context: &Context, +    ) -> Box<dyn Future<Item = LookupContainerResponse, Error = ApiError> + Send> { +        let conn = self.db_pool.get().expect("db_pool error"); +        let expand_flags = match expand { +            None => ExpandFlags::none(), +            Some(param) => ExpandFlags::from_str(¶m).unwrap(), +        }; +        let hide_flags = match hide { +            None => HideFlags::none(), +            Some(param) => HideFlags::from_str(¶m).unwrap(), +        }; +        // No transaction for GET +        let ret = match self +            .lookup_container_handler( +                &conn, +                &issnl, +                &issne, +                &issnp, +                &issn, +                &wikidata_qid, +                expand_flags, +                hide_flags, +            ) +            .map_err(|e| FatcatError::from(e)) +        { +            Ok(entity) => LookupContainerResponse::FoundEntity(entity), +            Err(fe) => generic_err_responses!(fe, LookupContainerResponse), +        }; +        Box::new(futures::done(Ok(ret))) +    } +      fn lookup_file(          &self,          md5: Option<String>, @@ -740,6 +774,7 @@ impl Api for Server {          doaj: Option<String>,          dblp: Option<String>,          oai: Option<String>, +        hdl: Option<String>,          expand: Option<String>,          hide: Option<String>,          _context: &Context, @@ -770,6 +805,7 @@ impl Api for Server {                  &doaj,                  &dblp,                  &oai, +                &hdl,                  expand_flags,                  hide_flags,              ) @@ -882,6 +918,27 @@ impl Api for Server {          Box::new(futures::done(Ok(ret)))      } +    fn lookup_editor( +        &self, +        username: Option<String>, +        _context: &Context, +    ) -> Box<dyn Future<Item = LookupEditorResponse, Error = ApiError> + Send> { +        let conn = self.db_pool.get().expect("db_pool error"); +        let ret = match conn.transaction(|| match username { +            Some(username) => { +                let row: EditorRow = Editor::db_get_username(&conn, &username)?; +                Ok(row.into_model()) +            } +            None => Err(FatcatError::MissingOrMultipleExternalId( +                "in lookup".to_string(), +            )), +        }) { +            Ok(editor) => LookupEditorResponse::Found(editor), +            Err(fe) => generic_err_responses!(fe, LookupEditorResponse), +        }; +        Box::new(futures::done(Ok(ret))) +    } +      fn accept_editgroup(          &self,          editgroup_id: String, diff --git a/rust/src/entity_crud.rs b/rust/src/entity_crud.rs index b3b97c73..cbf9592b 100644 --- a/rust/src/entity_crud.rs +++ b/rust/src/entity_crud.rs @@ -797,10 +797,13 @@ impl EntityCrud for ContainerEntity {          Ok(ContainerEntity {              issnl: None, +            issne: None, +            issnp: None,              wikidata_qid: None,              publisher: None,              name: None,              container_type: None, +            publication_status: None,              state: Some(ident_row.state().unwrap().shortname()),              ident: Some(FatcatId::from_uuid(&ident_row.id).to_string()),              revision: ident_row.rev_id.map(|u| u.to_string()), @@ -829,10 +832,13 @@ impl EntityCrud for ContainerEntity {          Ok(ContainerEntity {              issnl: rev_row.issnl, +            issne: rev_row.issne, +            issnp: rev_row.issnp,              wikidata_qid: rev_row.wikidata_qid,              publisher: rev_row.publisher,              name: Some(rev_row.name),              container_type: rev_row.container_type, +            publication_status: rev_row.publication_status,              state,              ident: ident_id,              revision: Some(rev_row.id.to_string()), @@ -851,6 +857,16 @@ impl EntityCrud for ContainerEntity {              if let Some(ref extid) = entity.issnl {                  check_issn(extid)?;              } +            if let Some(ref extid) = entity.issne { +                check_issn(extid)?; +            } +            if let Some(ref extid) = entity.issnp { +                check_issn(extid)?; +            } + +            if let Some(ref status) = entity.publication_status { +                check_publication_status(status)?; +            }          }          if models.iter().any(|m| m.name.is_none()) { @@ -868,8 +884,11 @@ impl EntityCrud for ContainerEntity {                          name: model.name.clone().unwrap(), // unwrap checked above                          publisher: model.publisher.clone(),                          issnl: model.issnl.clone(), +                        issnp: model.issnp.clone(), +                        issne: model.issne.clone(),                          wikidata_qid: model.wikidata_qid.clone(),                          container_type: model.container_type.clone(), +                        publication_status: model.publication_status.clone(),                          extra_json: model.extra.clone(),                      })                      .collect::<Vec<ContainerRevNewRow>>(), @@ -1292,6 +1311,7 @@ impl EntityCrud for FilesetEntity {                  md5: r.md5,                  sha1: r.sha1,                  sha256: r.sha256, +                mimetype: r.mimetype,                  extra: r.extra_json,              })              .collect(); @@ -1374,6 +1394,7 @@ impl EntityCrud for FilesetEntity {                              md5: f.md5.clone(),                              sha1: f.sha1.clone(),                              sha256: f.sha256.clone(), +                            mimetype: f.mimetype.clone(),                              extra_json: f.extra.clone(),                          })                          .collect(); @@ -1747,6 +1768,7 @@ impl EntityCrud for ReleaseEntity {                  doaj: None,                  dblp: None,                  oai: None, +                hdl: None,              },              refs: None,              contribs: None, @@ -2026,6 +2048,7 @@ impl EntityCrud for ReleaseEntity {              doaj: None,              dblp: None,              oai: None, +            hdl: None,          };          let extid_rows: Vec<ReleaseExtidRow> = release_rev_extid::table @@ -2041,6 +2064,7 @@ impl EntityCrud for ReleaseEntity {                  "doaj" => ext_ids.doaj = Some(extid_row.value),                  "dblp" => ext_ids.dblp = Some(extid_row.value),                  "oai" => ext_ids.oai = Some(extid_row.value), +                "hdl" => ext_ids.hdl = Some(extid_row.value),                  _ => (),              }          } @@ -2128,6 +2152,9 @@ impl EntityCrud for ReleaseEntity {              if let Some(ref extid) = entity.ext_ids.oai {                  check_oai_id(extid)?;              } +            if let Some(ref extid) = entity.ext_ids.hdl { +                check_hdl(extid)?; +            }              if let Some(ref release_type) = entity.release_type {                  check_release_type(release_type)?; @@ -2335,6 +2362,13 @@ impl EntityCrud for ReleaseEntity {                      value: extid.clone(),                  });              }; +            if let Some(extid) = &model.ext_ids.hdl { +                release_extid_rows.push(ReleaseExtidRow { +                    release_rev: *rev_id, +                    extid_type: "hdl".to_string(), +                    value: extid.to_lowercase(), +                }); +            };          }          for (model, rev_id) in models.iter().zip(rev_ids.iter()) { diff --git a/rust/src/identifiers.rs b/rust/src/identifiers.rs index 9d3734c9..3f5fc2cf 100644 --- a/rust/src/identifiers.rs +++ b/rust/src/identifiers.rs @@ -445,8 +445,8 @@ fn test_check_oai_id() {      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 +                                                            // 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()); @@ -457,6 +457,46 @@ fn test_check_oai_id() {      assert!(check_oai_id("oai:wibble.org:ab?cd").is_ok());  } +pub fn check_hdl(raw: &str) -> Result<()> { +    // currently strict about only allowing a fixed set of prefixes +    // should explicitly not allow DOIs, even though DOIs are themselves handles +    lazy_static! { +        static ref RE: Regex = Regex::new(r"^(20|11|21|84).\d{1,6}(.\d{1,6})?/\S+$").unwrap(); +    } +    if raw.is_ascii() && RE.is_match(raw) { +        Ok(()) +    } else { +        Err(FatcatError::MalformedExternalId( +            "Handle (expected, eg, '20.500.23456/ABC/trs12')".to_string(), +            raw.to_string(), +        ))? +    } +} + +#[test] +fn test_check_hdl() { +    assert!(check_hdl("20.500.23456/ABC/DUMMY").is_ok()); +    assert!(check_hdl("20.500.12690/RIN/IDDOAH/BTNH25").is_ok()); +    assert!(check_hdl("20.500.12690/rin/iddoah/btnh25").is_ok()); +    assert!(check_hdl("20.1234/aksjdfh").is_ok()); +    assert!(check_hdl("21.1234/aksjdfh").is_ok()); +    assert!(check_hdl("11.1234/aksjdfh").is_ok()); +    assert!(check_hdl("20.500.23456/ABC/trs12").is_ok()); +    assert!(check_hdl("20.500/ABC/trs12").is_ok()); + +    assert!(check_hdl("10.1234/aksjdfh").is_err()); +    assert!(check_hdl("0.1234/aksjdfh").is_err()); +    assert!(check_hdl("20.1234/ßs").is_err()); +    assert!(check_hdl("20.1234/aksjdfh ").is_err()); +    assert!(check_hdl("20.1234/ak sjdfh").is_err()); +    assert!(check_hdl("20.1234/aks\tjdfh").is_err()); +    assert!(check_hdl("20.1234/ ").is_err()); +    assert!(check_hdl("20.1234.sdf").is_err()); +    assert!(check_hdl("20.1234/\naksjdfh").is_err()); +    assert!(check_hdl("20.1234").is_err()); +    assert!(check_hdl("20.1234/").is_err()); +} +  pub fn check_issn(raw: &str) -> Result<()> {      lazy_static! {          static ref RE: Regex = Regex::new(r"^\d{4}-\d{3}[0-9X]$").unwrap(); @@ -767,3 +807,34 @@ fn test_check_contrib_role() {      assert!(check_contrib_role("EDITOR").is_err());      assert!(check_contrib_role("editor ").is_err());  } + +pub fn check_publication_status(raw: &str) -> Result<()> { +    let valid_types = vec![ +        // Didn't have a controlled vocab so made one up +        "active", +        "suspended", +        "discontinued", +        "vanished", +        "never", +        "one-time", +    ]; +    for good in valid_types { +        if raw == good { +            return Ok(()); +        } +    } +    Err(FatcatError::NotInControlledVocabulary( +        "publication_status (controlled vocabulary)".to_string(), +        raw.to_string(), +    ))? +} + +#[test] +fn test_check_publication_status() { +    assert!(check_publication_status("active").is_ok()); +    assert!(check_publication_status("discontinued").is_ok()); +    assert!(check_publication_status("boondogle").is_err()); +    assert!(check_publication_status("").is_err()); +    assert!(check_publication_status("active ").is_err()); +    assert!(check_publication_status("Active").is_err()); +} | 
