diff options
| author | Bryan Newbold <bnewbold@robocracy.org> | 2018-07-25 12:18:34 -0700 | 
|---|---|---|
| committer | Bryan Newbold <bnewbold@robocracy.org> | 2018-07-25 12:18:34 -0700 | 
| commit | c4618dd2fb070eefcdb38bca7b92fe32da766702 (patch) | |
| tree | c874372bb6574e92db1e7bb98029ee0ee186326f /rust/src | |
| parent | a4e45f77b5431e0211337afc67ee586c553590d3 (diff) | |
| download | fatcat-c4618dd2fb070eefcdb38bca7b92fe32da766702.tar.gz fatcat-c4618dd2fb070eefcdb38bca7b92fe32da766702.zip  | |
more external ident handling
Diffstat (limited to 'rust/src')
| -rw-r--r-- | rust/src/api_helpers.rs | 84 | ||||
| -rw-r--r-- | rust/src/api_server.rs | 55 | ||||
| -rw-r--r-- | rust/src/api_wrappers.rs | 8 | ||||
| -rw-r--r-- | rust/src/lib.rs | 7 | 
4 files changed, 144 insertions, 10 deletions
diff --git a/rust/src/api_helpers.rs b/rust/src/api_helpers.rs index f0f56a6b..ef07ee55 100644 --- a/rust/src/api_helpers.rs +++ b/rust/src/api_helpers.rs @@ -5,6 +5,8 @@ use diesel;  use diesel::prelude::*;  use errors::*;  use uuid::Uuid; +use regex::Regex; +  pub fn get_or_create_editgroup(editor_id: Uuid, conn: &PgConnection) -> Result<Uuid> {      // check for current active @@ -109,3 +111,85 @@ pub fn uuid2fcid(id: &Uuid) -> String {      let raw = id.as_bytes();      BASE32_NOPAD.encode(raw).to_lowercase()  } + +pub fn check_pmcid(raw: &str) -> Result<()> { +    lazy_static! { +        static ref RE: Regex = Regex::new(r"^PMC\d+$").unwrap(); +    } +    if RE.is_match(raw) { +        Ok(()) +    } else { +        Err(ErrorKind::MalformedExternalId( +            format!("not a valid PubMed Central ID (PMCID): '{}' (expected, eg, 'PMC12345')", raw) +        ).into()) +    } +} + +pub fn check_pmid(raw: &str) -> Result<()> { +    lazy_static! { +        static ref RE: Regex  = Regex::new(r"^\d+$").unwrap(); +    } +    if RE.is_match(raw) { +        Ok(()) +    } else { +        Err(ErrorKind::MalformedExternalId( +            format!("not a valid PubMed ID (PMID): '{}' (expected, eg, '1234')", raw) +        ).into()) +    } +} + +pub fn check_wikidata_qid(raw: &str) -> Result<()> { +    lazy_static! { +        static ref RE: Regex  = Regex::new(r"^Q\d+$").unwrap(); +    } +    if RE.is_match(raw) { +        Ok(()) +    } else { +        Err(ErrorKind::MalformedExternalId( +            format!("not a valid Wikidata QID: '{}' (expected, eg, 'Q1234')", raw) +        ).into()) +    } +} + +pub fn check_doi(raw: &str) -> Result<()> { +    lazy_static! { +        static ref RE: Regex  = Regex::new(r"^10.\d{3,6}/.+$").unwrap(); +    } +    if RE.is_match(raw) { +        Ok(()) +    } else { +        Err(ErrorKind::MalformedExternalId( +            format!("not a valid DOI: '{}' (expected, eg, '10.1234/aksjdfh')", raw) +        ).into()) +    } +} + +pub fn check_issn(raw: &str) -> Result<()> { +    lazy_static! { +        static ref RE: Regex  = Regex::new(r"^\d{4}-\d{3}[0-9X]$").unwrap(); +    } +    if RE.is_match(raw) { +        Ok(()) +    } else { +        Err(ErrorKind::MalformedExternalId( +            format!("not a valid ISSN: '{}' (expected, eg, '1234-5678')", raw) +        ).into()) +    } +} + +pub fn check_orcid(raw: &str) -> Result<()> { +    lazy_static! { +        static ref RE: Regex  = Regex::new(r"^\d{4}-\d{4}-\d{4}-\d{4}$").unwrap(); +    } +    if RE.is_match(raw) { +        Ok(()) +    } else { +        Err(ErrorKind::MalformedExternalId( +            format!("not a valid ORCID: '{}' (expected, eg, '0123-4567-3456-6789')", raw) +        ).into()) +    } +} + +// TODO: make the above checks "more correct" +// TODO: check ISBN-13 +// TODO: check hashes (SHA-1, etc) diff --git a/rust/src/api_server.rs b/rust/src/api_server.rs index 5aa075dd..64c028be 100644 --- a/rust/src/api_server.rs +++ b/rust/src/api_server.rs @@ -1,6 +1,6 @@  //! API endpoint handlers -use api_helpers::{accept_editgroup, fcid2uuid, get_or_create_editgroup, uuid2fcid}; +use api_helpers::*;  use chrono;  use database_models::*;  use database_schema::{ @@ -95,6 +95,7 @@ fn container_row2entity(      };      Ok(ContainerEntity {          issnl: rev.issnl, +        wikidata_qid: rev.wikidata_qid,          publisher: rev.publisher,          name: rev.name,          abbrev: rev.abbrev, @@ -122,6 +123,7 @@ fn creator_row2entity(ident: Option<CreatorIdentRow>, rev: CreatorRevRow) -> Res          given_name: rev.given_name,          surname: rev.surname,          orcid: rev.orcid, +        wikidata_qid: rev.wikidata_qid,          state: state,          ident: ident_id,          revision: Some(rev.id.to_string()), @@ -249,6 +251,7 @@ fn release_row2entity(          pmid: rev.pmid,          pmcid: rev.pmcid,          isbn13: rev.isbn13, +        wikidata_qid: rev.wikidata_qid,          volume: rev.volume,          issue: rev.issue,          pages: rev.pages, @@ -304,6 +307,7 @@ impl Server {      pub fn lookup_container_handler(&self, issnl: &str) -> Result<ContainerEntity> {          let conn = self.db_pool.get().expect("db_pool error"); +        check_issn(issnl)?;          let (ident, rev): (ContainerIdentRow, ContainerRevRow) = container_ident::table              .inner_join(container_rev::table)              .filter(container_rev::issnl.eq(issnl)) @@ -329,6 +333,7 @@ impl Server {      pub fn lookup_creator_handler(&self, orcid: &str) -> Result<CreatorEntity> {          let conn = self.db_pool.get().expect("db_pool error"); +        check_orcid(orcid)?;          let (ident, rev): (CreatorIdentRow, CreatorRevRow) = creator_ident::table              .inner_join(creator_rev::table)              .filter(creator_rev::orcid.eq(orcid)) @@ -397,6 +402,7 @@ impl Server {      pub fn lookup_release_handler(&self, doi: &str) -> Result<ReleaseEntity> {          let conn = self.db_pool.get().expect("db_pool error"); +        check_doi(doi)?;          let (ident, rev): (ReleaseIdentRow, ReleaseRevRow) = release_ident::table              .inner_join(release_rev::table)              .filter(release_rev::doi.eq(doi)) @@ -472,20 +478,27 @@ impl Server {              None => get_or_create_editgroup(editor_id, &conn)?,              Some(param) => fcid2uuid(¶m)?,          }; +        if let Some(ref extid) = entity.wikidata_qid { +            check_wikidata_qid(extid)?; +        } +        if let Some(ref extid) = entity.issnl { +            check_issn(extid)?; +        }          let edit: ContainerEditRow = diesel::sql_query( -            "WITH rev AS ( INSERT INTO container_rev (name, publisher, issnl, abbrev, coden, extra_json) -                        VALUES ($1, $2, $3, $4, $5, $6) +            "WITH rev AS ( INSERT INTO container_rev (name, publisher, issnl, wikidata_qid, abbrev, coden, extra_json) +                        VALUES ($1, $2, $3, $4, $5, $6, $7)                          RETURNING id ),                  ident AS ( INSERT INTO container_ident (rev_id)                              VALUES ((SELECT rev.id FROM rev))                              RETURNING id )              INSERT INTO container_edit (editgroup_id, ident_id, rev_id) VALUES -                ($7, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev)) +                ($8, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))              RETURNING *",          ).bind::<diesel::sql_types::Text, _>(entity.name)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.publisher)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.issnl) +            .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.abbrev)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.coden)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra) @@ -514,21 +527,28 @@ impl Server {              None => get_or_create_editgroup(editor_id, &conn).expect("current editgroup"),              Some(param) => fcid2uuid(¶m)?,          }; +        if let Some(ref extid) = entity.orcid { +            check_orcid(extid)?; +        } +        if let Some(ref extid) = entity.wikidata_qid { +            check_wikidata_qid(extid)?; +        }          let edit: CreatorEditRow = diesel::sql_query( -            "WITH rev AS ( INSERT INTO creator_rev (display_name, given_name, surname, orcid, extra_json) -                        VALUES ($1, $2, $3, $4, $5) +            "WITH rev AS ( INSERT INTO creator_rev (display_name, given_name, surname, orcid, wikidata_qid, extra_json) +                        VALUES ($1, $2, $3, $4, $5, $6)                          RETURNING id ),                  ident AS ( INSERT INTO creator_ident (rev_id)                              VALUES ((SELECT rev.id FROM rev))                              RETURNING id )              INSERT INTO creator_edit (editgroup_id, ident_id, rev_id) VALUES -                ($6, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev)) +                ($7, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))              RETURNING *",          ).bind::<diesel::sql_types::Text, _>(entity.display_name)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.given_name)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.surname)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.orcid) +            .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)              .bind::<diesel::sql_types::Uuid, _>(editgroup_id)              .get_result(conn)?; @@ -644,6 +664,18 @@ impl Server {              None => get_or_create_editgroup(editor_id, &conn).expect("current editgroup"),              Some(param) => fcid2uuid(¶m)?,          }; +        if let Some(ref extid) = entity.doi { +            check_doi(extid)?; +        } +        if let Some(ref extid) = entity.pmid { +            check_pmid(extid)?; +        } +        if let Some(ref extid) = entity.pmcid { +            check_pmcid(extid)?; +        } +        if let Some(ref extid) = entity.wikidata_qid { +            check_wikidata_qid(extid)?; +        }          let work_id = match entity.work_id {              Some(work_id) => fcid2uuid(&work_id)?, @@ -668,14 +700,14 @@ impl Server {          };          let edit: ReleaseEditRow = diesel::sql_query( -            "WITH rev AS ( INSERT INTO release_rev (title, release_type, release_status, release_date, doi, isbn13, volume, issue, pages, work_ident_id, container_ident_id, publisher, language, extra_json) -                        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) +            "WITH rev AS ( INSERT INTO release_rev (title, release_type, release_status, release_date, doi, pmid, pmcid, wikidata_qid, isbn13, volume, issue, pages, work_ident_id, container_ident_id, publisher, language, extra_json) +                        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)                          RETURNING id ),                  ident AS ( INSERT INTO release_ident (rev_id)                              VALUES ((SELECT rev.id FROM rev))                              RETURNING id )              INSERT INTO release_edit (editgroup_id, ident_id, rev_id) VALUES -                ($15, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev)) +                ($18, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))              RETURNING *",          ).bind::<diesel::sql_types::Text, _>(entity.title)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.release_type) @@ -683,6 +715,9 @@ impl Server {              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Date>, _>(                  entity.release_date.map(|v| v.naive_utc().date()))              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.doi) +            .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pmid) +            .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pmcid) +            .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.isbn13)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.volume)              .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.issue) diff --git a/rust/src/api_wrappers.rs b/rust/src/api_wrappers.rs index 8be661e1..e66f3ccd 100644 --- a/rust/src/api_wrappers.rs +++ b/rust/src/api_wrappers.rs @@ -35,6 +35,8 @@ macro_rules! wrap_entity_handlers {                  Err(Error(ErrorKind::InvalidFatcatId(e), _)) =>                      $get_resp::BadRequest(ErrorResponse {                          message: ErrorKind::InvalidFatcatId(e).to_string() }), +                Err(Error(ErrorKind::MalformedExternalId(e), _)) => +                    $get_resp::BadRequest(ErrorResponse { message: e.to_string() }),                  Err(e) => {                      error!("{}", e);                      $get_resp::GenericError(ErrorResponse { message: e.to_string() }) @@ -58,6 +60,8 @@ macro_rules! wrap_entity_handlers {                  Err(Error(ErrorKind::InvalidFatcatId(e), _)) =>                      $post_resp::BadRequest(ErrorResponse {                          message: ErrorKind::InvalidFatcatId(e).to_string() }), +                Err(Error(ErrorKind::MalformedExternalId(e), _)) => +                    $post_resp::BadRequest(ErrorResponse { message: e.to_string() }),                  Err(e) => {                      error!("{}", e);                      $post_resp::GenericError(ErrorResponse { message: e.to_string() }) @@ -81,6 +85,8 @@ macro_rules! wrap_entity_handlers {                  Err(Error(ErrorKind::InvalidFatcatId(e), _)) =>                      $post_batch_resp::BadRequest(ErrorResponse {                          message: ErrorKind::InvalidFatcatId(e).to_string() }), +                Err(Error(ErrorKind::MalformedExternalId(e), _)) => +                    $post_batch_resp::BadRequest(ErrorResponse { message: e.to_string() }),                  Err(e) => {                      error!("{}", e);                      $post_batch_resp::GenericError(ErrorResponse { message: e.to_string() }) @@ -127,6 +133,8 @@ macro_rules! wrap_lookup_handler {                      $get_resp::FoundEntity(entity),                  Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) =>                      $get_resp::NotFound(ErrorResponse { message: format!("Not found: {}", $idname) }), +                Err(Error(ErrorKind::MalformedExternalId(e), _)) => +                    $get_resp::BadRequest(ErrorResponse { message: e.to_string() }),                  Err(e) => {                      error!("{}", e);                      $get_resp::BadRequest(ErrorResponse { message: e.to_string() }) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index fd871f55..eff487b3 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -17,6 +17,9 @@ extern crate serde_json;  #[macro_use]  extern crate log;  extern crate data_encoding; +extern crate regex; +#[macro_use] +extern crate lazy_static;  pub mod api_helpers;  pub mod api_server; @@ -39,6 +42,10 @@ mod errors {                  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") +            }          }      }  }  | 
