aboutsummaryrefslogtreecommitdiffstats
path: root/rust
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2021-10-12 19:56:08 -0700
committerBryan Newbold <bnewbold@robocracy.org>2021-10-13 16:21:30 -0700
commit11fdff350e0549d46a8a7b5e74451e08ce067cb2 (patch)
tree20f48fb619e4267bb066340e249acc14649ae3da /rust
parent8d1f8d02f2e43c13d35b57ff3a625ab5de6c51c7 (diff)
downloadfatcat-11fdff350e0549d46a8a7b5e74451e08ce067cb2.tar.gz
fatcat-11fdff350e0549d46a8a7b5e74451e08ce067cb2.zip
rust: implement scheman and API changes
Diffstat (limited to 'rust')
-rw-r--r--rust/src/editing_crud.rs17
-rw-r--r--rust/src/endpoint_handlers.rs194
-rw-r--r--rust/src/endpoints.rs71
-rw-r--r--rust/src/entity_crud.rs34
-rw-r--r--rust/src/identifiers.rs75
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(&param).unwrap(),
+ };
+ let hide_flags = match hide {
+ None => HideFlags::none(),
+ Some(param) => HideFlags::from_str(&param).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());
+}