From 662833f5b3ac35b227e2dfed0f6f407ebaa67eb9 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Tue, 2 Feb 2021 11:20:53 -0800 Subject: commit old work-in-progress --- rust/fatcat-cli/src/api.rs | 365 ++++++++++++++++++++++++++------ rust/fatcat-cli/src/entities.rs | 211 +++++++++++++------ rust/fatcat-cli/src/lib.rs | 107 ++++++---- rust/fatcat-cli/src/main.rs | 314 +++++++++++++++++++--------- rust/fatcat-cli/src/search.rs | 113 +++++----- rust/fatcat-cli/src/specifier.rs | 437 ++++++++++++++++++++++++++++----------- 6 files changed, 1110 insertions(+), 437 deletions(-) diff --git a/rust/fatcat-cli/src/api.rs b/rust/fatcat-cli/src/api.rs index 41718ea..3fa67e9 100644 --- a/rust/fatcat-cli/src/api.rs +++ b/rust/fatcat-cli/src/api.rs @@ -1,14 +1,18 @@ - -use hyper::client::ResponseFuture; -use fatcat_openapi::{ApiNoContext, ContextWrapperExt}; +use crate::{parse_macaroon_editor_id, ClientStatus, EntityType, Specifier}; +use anyhow::{anyhow, Context, Result}; use fatcat_openapi::client::Client; use fatcat_openapi::models; -use swagger::{AuthData, ContextBuilder, EmptyContext, Push, XSpanIdString, auth}; -use anyhow::{Result, anyhow, Context}; -use crate::{ClientStatus,parse_macaroon_editor_id,Specifier, EntityType}; +use fatcat_openapi::{ApiNoContext, ContextWrapperExt}; +use hyper::client::ResponseFuture; +use swagger::{auth, AuthData, ContextBuilder, EmptyContext, Push, XSpanIdString}; use tokio::runtime::current_thread::Runtime; -type FatcatApiContextType = swagger::make_context_ty!( ContextBuilder, EmptyContext, Option, XSpanIdString); +type FatcatApiContextType = swagger::make_context_ty!( + ContextBuilder, + EmptyContext, + Option, + XSpanIdString +); pub struct FatcatApiClient<'a> { pub api: fatcat_openapi::ContextWrapper<'a, Client, FatcatApiContextType>, @@ -19,11 +23,15 @@ pub struct FatcatApiClient<'a> { } impl<'a> FatcatApiClient<'a> { - - pub fn new(client: &'a fatcat_openapi::client::Client, api_host: String, api_token: Option) -> Result { - + pub fn new( + client: &'a fatcat_openapi::client::Client, + api_host: String, + api_token: Option, + ) -> Result { let auth_data = match api_token { - Some(ref token) => Some(AuthData::Bearer(auth::Bearer{ token: token.clone() })), + Some(ref token) => Some(AuthData::Bearer(auth::Bearer { + token: token.clone(), + })), None => None, }; //info!("{:?}", auth_data); @@ -34,14 +42,19 @@ impl<'a> FatcatApiClient<'a> { XSpanIdString::default() ); - let wrapped_client: fatcat_openapi::ContextWrapper, FatcatApiContextType> = client.with_context(context); + let wrapped_client: fatcat_openapi::ContextWrapper< + Client, + FatcatApiContextType, + > = client.with_context(context); let rt: Runtime = Runtime::new().expect("create tokio runtime"); let editor_id = match api_token { - Some(ref token) => Some(parse_macaroon_editor_id(token).context("parse API auth token")?), + Some(ref token) => { + Some(parse_macaroon_editor_id(token).context("parse API auth token")?) + } None => None, }; - + Ok(FatcatApiClient { api: wrapped_client, rt, @@ -53,20 +66,40 @@ impl<'a> FatcatApiClient<'a> { pub fn status(&mut self) -> Result { let last_changelog = match self.rt.block_on(self.api.get_changelog(Some(1))) { - Ok(fatcat_openapi::GetChangelogResponse::Success(entry_vec)) => Some(entry_vec[0].index), + Ok(fatcat_openapi::GetChangelogResponse::Success(entry_vec)) => { + Some(entry_vec[0].index) + } Ok(_) | Err(_) => None, }; let has_api_token = self.api_token.is_some(); let account: Option = if has_api_token && last_changelog.is_some() { - match self.rt.block_on(self.api.auth_check(None)).context("check auth token")? { + match self + .rt + .block_on(self.api.auth_check(None)) + .context("check auth token")? + { fatcat_openapi::AuthCheckResponse::Success(_) => Ok(()), - fatcat_openapi::AuthCheckResponse::Forbidden(err) => Err(anyhow!("Forbidden ({}): {}", err.error, err.message)), - fatcat_openapi::AuthCheckResponse::NotAuthorized{body: err, ..} => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), + fatcat_openapi::AuthCheckResponse::Forbidden(err) => { + Err(anyhow!("Forbidden ({}): {}", err.error, err.message)) + } + fatcat_openapi::AuthCheckResponse::NotAuthorized { body: err, .. } => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } resp => return Err(anyhow!("{:?}", resp)).context("auth check failed"), - }.context("check auth token")?; - match self.rt.block_on(self.api.get_editor(self.editor_id.as_ref().unwrap().to_string())).context("fetching editor account info")? { + } + .context("check auth token")?; + match self + .rt + .block_on( + self.api + .get_editor(self.editor_id.as_ref().unwrap().to_string()), + ) + .context("fetching editor account info")? + { fatcat_openapi::GetEditorResponse::Found(editor) => Some(editor), - fatcat_openapi::GetEditorResponse::NotFound(err) => return Err(anyhow!("Not Found: {}", err.message)), + fatcat_openapi::GetEditorResponse::NotFound(err) => { + return Err(anyhow!("Not Found: {}", err.message)) + } resp => return Err(anyhow!("{:?}", resp)).context("editor fetch failed"), } } else { @@ -80,18 +113,29 @@ impl<'a> FatcatApiClient<'a> { }) } - pub fn update_editgroup_submit(&mut self, editgroup_id: String, submit: bool) -> Result { - let result = self.rt.block_on( - self.api.get_editgroup(editgroup_id.clone()) - ).context("fetch editgroups")?; + pub fn update_editgroup_submit( + &mut self, + editgroup_id: String, + submit: bool, + ) -> Result { + let result = self + .rt + .block_on(self.api.get_editgroup(editgroup_id.clone())) + .context("fetch editgroups")?; let eg = match result { fatcat_openapi::GetEditgroupResponse::Found(eg) => eg, - other => return Err(anyhow!("{:?}", other)) - .with_context(|| format!("failed to fetch editgroup {}", editgroup_id)), + other => { + return Err(anyhow!("{:?}", other)) + .with_context(|| format!("failed to fetch editgroup {}", editgroup_id)) + } }; - let result = self.rt.block_on( - self.api.update_editgroup(editgroup_id.clone(), eg, Some(submit)) - ).context("submit editgroup")?; + let result = self + .rt + .block_on( + self.api + .update_editgroup(editgroup_id.clone(), eg, Some(submit)), + ) + .context("submit editgroup")?; match result { fatcat_openapi::UpdateEditgroupResponse::UpdatedEditgroup(eg) => Ok(eg), other => Err(anyhow!("{:?}", other)) @@ -99,11 +143,18 @@ impl<'a> FatcatApiClient<'a> { } } - pub fn delete_entity(&mut self, specifier: Specifier, editgroup_id: String) -> Result { + pub fn delete_entity( + &mut self, + specifier: Specifier, + editgroup_id: String, + ) -> Result { use Specifier::*; let specifier = specifier.into_entity_specifier(self)?; match specifier.clone() { - Release(fcid) => match self.rt.block_on(self.api.delete_release(editgroup_id, fcid))? { + Release(fcid) => match self + .rt + .block_on(self.api.delete_release(editgroup_id, fcid))? + { fatcat_openapi::DeleteReleaseResponse::DeletedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, @@ -111,11 +162,17 @@ impl<'a> FatcatApiClient<'a> { fatcat_openapi::DeleteWorkResponse::DeletedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - Container(fcid) => match self.rt.block_on(self.api.delete_container(editgroup_id, fcid))? { + Container(fcid) => match self + .rt + .block_on(self.api.delete_container(editgroup_id, fcid))? + { fatcat_openapi::DeleteContainerResponse::DeletedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - Creator(fcid) => match self.rt.block_on(self.api.delete_creator(editgroup_id, fcid))? { + Creator(fcid) => match self + .rt + .block_on(self.api.delete_creator(editgroup_id, fcid))? + { fatcat_openapi::DeleteCreatorResponse::DeletedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, @@ -123,104 +180,282 @@ impl<'a> FatcatApiClient<'a> { fatcat_openapi::DeleteFileResponse::DeletedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - FileSet(fcid) => match self.rt.block_on(self.api.delete_fileset(editgroup_id, fcid))? { + FileSet(fcid) => match self + .rt + .block_on(self.api.delete_fileset(editgroup_id, fcid))? + { fatcat_openapi::DeleteFilesetResponse::DeletedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - WebCapture(fcid) => match self.rt.block_on(self.api.delete_webcapture(editgroup_id, fcid))? { + WebCapture(fcid) => match self + .rt + .block_on(self.api.delete_webcapture(editgroup_id, fcid))? + { fatcat_openapi::DeleteWebcaptureResponse::DeletedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, Editgroup(..) | Editor(..) => unimplemented!("deletion for this entity type"), Changelog(..) => return Err(anyhow!("mutating this entity type doesn't make sense")), - EditorUsername(..) | ReleaseLookup(..) | ContainerLookup(..) | FileLookup(..) | CreatorLookup(..) => - return Err(anyhow!("into_entity_specifier() didn't work?")), - }.with_context(|| format!("failed to delete {:?}", specifier)) + EditorUsername(..) | ReleaseLookup(..) | ContainerLookup(..) | FileLookup(..) + | CreatorLookup(..) => return Err(anyhow!("into_entity_specifier() didn't work?")), + } + .with_context(|| format!("failed to delete {:?}", specifier)) } - pub fn create_entity_from_json(&mut self, entity_type: EntityType, json_str: &str, editgroup_id: String) -> Result { + pub fn create_entity_from_json( + &mut self, + entity_type: EntityType, + json_str: &str, + editgroup_id: String, + ) -> Result { match entity_type { EntityType::Release => { - match self.rt.block_on(self.api.create_release(editgroup_id, serde_json::from_str(&json_str)?))? { + match self.rt.block_on( + self.api + .create_release(editgroup_id, serde_json::from_str(&json_str)?), + )? { fatcat_openapi::CreateReleaseResponse::CreatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), } - }, + } EntityType::Work => { - match self.rt.block_on(self.api.create_work(editgroup_id, serde_json::from_str(&json_str)?))? { + match self.rt.block_on( + self.api + .create_work(editgroup_id, serde_json::from_str(&json_str)?), + )? { fatcat_openapi::CreateWorkResponse::CreatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), } - }, + } EntityType::Creator => { - match self.rt.block_on(self.api.create_creator(editgroup_id, serde_json::from_str(&json_str)?))? { + match self.rt.block_on( + self.api + .create_creator(editgroup_id, serde_json::from_str(&json_str)?), + )? { fatcat_openapi::CreateCreatorResponse::CreatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), } - }, + } EntityType::Container => { - match self.rt.block_on(self.api.create_container(editgroup_id, serde_json::from_str(&json_str)?))? { + match self.rt.block_on( + self.api + .create_container(editgroup_id, serde_json::from_str(&json_str)?), + )? { fatcat_openapi::CreateContainerResponse::CreatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), } - }, + } EntityType::File => { - match self.rt.block_on(self.api.create_file(editgroup_id, serde_json::from_str(&json_str)?))? { + match self.rt.block_on( + self.api + .create_file(editgroup_id, serde_json::from_str(&json_str)?), + )? { fatcat_openapi::CreateFileResponse::CreatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), } - }, + } EntityType::FileSet => { - match self.rt.block_on(self.api.create_fileset(editgroup_id, serde_json::from_str(&json_str)?))? { + match self.rt.block_on( + self.api + .create_fileset(editgroup_id, serde_json::from_str(&json_str)?), + )? { fatcat_openapi::CreateFilesetResponse::CreatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), } - }, + } EntityType::WebCapture => { - match self.rt.block_on(self.api.create_webcapture(editgroup_id, serde_json::from_str(&json_str)?))? { + match self.rt.block_on( + self.api + .create_webcapture(editgroup_id, serde_json::from_str(&json_str)?), + )? { fatcat_openapi::CreateWebcaptureResponse::CreatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), } + } + } + .with_context(|| format!("parsing and creating {:?} entity", entity_type)) + } + + pub fn existing_edit_in_editgroup( + &mut self, + editgroup: &models::Editgroup, + specifier: &Specifier, + ) -> Option { + use Specifier::*; + let (fcid, edit_list) = match specifier.clone() { + Release(fcid) => (fcid, editgroup.edits.as_ref().unwrap().releases.clone()), + Work(fcid) => (fcid, editgroup.edits.as_ref().unwrap().works.clone()), + Container(fcid) => (fcid, editgroup.edits.as_ref().unwrap().containers.clone()), + Creator(fcid) => (fcid, editgroup.edits.as_ref().unwrap().creators.clone()), + File(fcid) => (fcid, editgroup.edits.as_ref().unwrap().files.clone()), + FileSet(fcid) => (fcid, editgroup.edits.as_ref().unwrap().filesets.clone()), + WebCapture(fcid) => (fcid, editgroup.edits.as_ref().unwrap().webcaptures.clone()), + EditorUsername(..) | ReleaseLookup(..) | ContainerLookup(..) | FileLookup(..) + | CreatorLookup(..) | Editgroup(..) | Editor(..) | Changelog(..) => { + panic!("this entity type doesn't exist in editgroups") + } + }; + for entity_edit in edit_list.unwrap() { + if entity_edit.ident == fcid { + return Some(entity_edit); + } + } + None + } + + pub fn delete_editgroup_edit( + &mut self, + editgroup: &models::Editgroup, + specifier: &Specifier, + edit: &models::EntityEdit, + ) -> Result<()> { + use Specifier::*; + let editgroup_id = editgroup.editgroup_id.clone().unwrap(); + let edit_id = edit.edit_id.clone(); + match specifier.clone() { + Release(..) => match self + .rt + .block_on(self.api.delete_release_edit(editgroup_id, edit_id))? + { + fatcat_openapi::DeleteReleaseEditResponse::DeletedEdit(..) => Ok(()), + other => Err(anyhow!("{:?}", other)), + }, + Work(..) => match self + .rt + .block_on(self.api.delete_work_edit(editgroup_id, edit_id))? + { + fatcat_openapi::DeleteWorkEditResponse::DeletedEdit(..) => Ok(()), + other => Err(anyhow!("{:?}", other)), + }, + Container(..) => match self + .rt + .block_on(self.api.delete_container_edit(editgroup_id, edit_id))? + { + fatcat_openapi::DeleteContainerEditResponse::DeletedEdit(..) => Ok(()), + other => Err(anyhow!("{:?}", other)), + }, + Creator(..) => match self + .rt + .block_on(self.api.delete_creator_edit(editgroup_id, edit_id))? + { + fatcat_openapi::DeleteCreatorEditResponse::DeletedEdit(..) => Ok(()), + other => Err(anyhow!("{:?}", other)), + }, + File(..) => match self + .rt + .block_on(self.api.delete_file_edit(editgroup_id, edit_id))? + { + fatcat_openapi::DeleteFileEditResponse::DeletedEdit(..) => Ok(()), + other => Err(anyhow!("{:?}", other)), + }, + FileSet(..) => match self + .rt + .block_on(self.api.delete_fileset_edit(editgroup_id, edit_id))? + { + fatcat_openapi::DeleteFilesetEditResponse::DeletedEdit(..) => Ok(()), + other => Err(anyhow!("{:?}", other)), }, - }.with_context(|| format!("parsing and creating {:?} entity", entity_type)) + WebCapture(..) => match self + .rt + .block_on(self.api.delete_webcapture_edit(editgroup_id, edit_id))? + { + fatcat_openapi::DeleteWebcaptureEditResponse::DeletedEdit(..) => Ok(()), + other => Err(anyhow!("{:?}", other)), + }, + EditorUsername(..) | ReleaseLookup(..) | ContainerLookup(..) | FileLookup(..) + | CreatorLookup(..) | Editgroup(..) | Editor(..) | Changelog(..) => { + panic!("this entity type doesn't exist in editgroups") + } + } } - pub fn update_entity_from_json(&mut self, specifier: Specifier, json_str: &str, editgroup_id: String) -> Result { + pub fn update_entity_from_json( + &mut self, + specifier: Specifier, + json_str: &str, + editgroup_id: String, + ) -> Result { use Specifier::*; let specifier = specifier.into_entity_specifier(self)?; + let eg = match self + .rt + .block_on(self.api.get_editgroup(editgroup_id.clone()))? + { + fatcat_openapi::GetEditgroupResponse::Found(model) => Ok(model), + fatcat_openapi::GetEditgroupResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::GetEditgroupResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: editgroup_{:?}", editgroup_id)), + }?; + if let Some(entity_edit) = self.existing_edit_in_editgroup(&eg, &specifier) { + self.delete_editgroup_edit(&eg, &specifier, &entity_edit)?; + }; match specifier.clone() { - Release(fcid) => match self.rt.block_on(self.api.update_release(editgroup_id, fcid, serde_json::from_str(&json_str)?))? { + Release(fcid) => match self.rt.block_on(self.api.update_release( + editgroup_id, + fcid, + serde_json::from_str(&json_str)?, + ))? { fatcat_openapi::UpdateReleaseResponse::UpdatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - Work(fcid) => match self.rt.block_on(self.api.update_work(editgroup_id, fcid, serde_json::from_str(&json_str)?))? { + Work(fcid) => match self.rt.block_on(self.api.update_work( + editgroup_id, + fcid, + serde_json::from_str(&json_str)?, + ))? { fatcat_openapi::UpdateWorkResponse::UpdatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - Container(fcid) => match self.rt.block_on(self.api.update_container(editgroup_id, fcid, serde_json::from_str(&json_str)?))? { + Container(fcid) => match self.rt.block_on(self.api.update_container( + editgroup_id, + fcid, + serde_json::from_str(&json_str)?, + ))? { fatcat_openapi::UpdateContainerResponse::UpdatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - Creator(fcid) => match self.rt.block_on(self.api.update_creator(editgroup_id, fcid, serde_json::from_str(&json_str)?))? { + Creator(fcid) => match self.rt.block_on(self.api.update_creator( + editgroup_id, + fcid, + serde_json::from_str(&json_str)?, + ))? { fatcat_openapi::UpdateCreatorResponse::UpdatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - File(fcid) => match self.rt.block_on(self.api.update_file(editgroup_id, fcid, serde_json::from_str(&json_str)?))? { + File(fcid) => match self.rt.block_on(self.api.update_file( + editgroup_id, + fcid, + serde_json::from_str(&json_str)?, + ))? { fatcat_openapi::UpdateFileResponse::UpdatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - FileSet(fcid) => match self.rt.block_on(self.api.update_fileset(editgroup_id, fcid, serde_json::from_str(&json_str)?))? { + FileSet(fcid) => match self.rt.block_on(self.api.update_fileset( + editgroup_id, + fcid, + serde_json::from_str(&json_str)?, + ))? { fatcat_openapi::UpdateFilesetResponse::UpdatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, - WebCapture(fcid) => match self.rt.block_on(self.api.update_webcapture(editgroup_id, fcid, serde_json::from_str(&json_str)?))? { + WebCapture(fcid) => match self.rt.block_on(self.api.update_webcapture( + editgroup_id, + fcid, + serde_json::from_str(&json_str)?, + ))? { fatcat_openapi::UpdateWebcaptureResponse::UpdatedEntity(ee) => Ok(ee), other => Err(anyhow!("{:?}", other)), }, Editgroup(..) | Editor(..) => unimplemented!("updates for this entity type"), Changelog(..) => return Err(anyhow!("deleting this entity type doesn't make sense")), - EditorUsername(..) | ReleaseLookup(..) | ContainerLookup(..) | FileLookup(..) | CreatorLookup(..) => - return Err(anyhow!("into_entity_specifier() didn't work?")), - }.with_context(|| format!("failed to update {:?}", specifier)) + EditorUsername(..) | ReleaseLookup(..) | ContainerLookup(..) | FileLookup(..) + | CreatorLookup(..) => return Err(anyhow!("into_entity_specifier() didn't work?")), + } + .with_context(|| format!("failed to update {:?}", specifier)) } } diff --git a/rust/fatcat-cli/src/entities.rs b/rust/fatcat-cli/src/entities.rs index c606d17..eee3946 100644 --- a/rust/fatcat-cli/src/entities.rs +++ b/rust/fatcat-cli/src/entities.rs @@ -1,11 +1,9 @@ - -use std::str::FromStr; -use anyhow::{Result, anyhow}; +use crate::Specifier; +use anyhow::{anyhow, Result}; +use fatcat_openapi::models; use lazy_static::lazy_static; use regex::Regex; -use fatcat_openapi::models; -use crate::Specifier; - +use std::str::FromStr; #[derive(Debug, PartialEq, Clone)] pub struct Mutation { @@ -22,7 +20,6 @@ impl FromStr for Mutation { static ref MUTATE_ENTITY_RE: Regex = Regex::new(r"^([a-z_]+)=(.*)$").unwrap(); } if let Some(captures) = MUTATE_ENTITY_RE.captures(s) { - // XXX: Some() vs None for value return Ok(Mutation { field: captures[1].to_string(), value: match &captures[2] { @@ -48,8 +45,7 @@ impl FromStr for Mutation { * - get by specifier */ -pub trait ApiEntityModel: ApiModelSer+ApiModelIdent+ApiModelMutate { -} +pub trait ApiEntityModel: ApiModelSer + ApiModelIdent + ApiModelMutate {} impl ApiEntityModel for models::ReleaseEntity {} impl ApiEntityModel for models::ContainerEntity {} @@ -58,9 +54,9 @@ impl ApiEntityModel for models::WorkEntity {} impl ApiEntityModel for models::FileEntity {} impl ApiEntityModel for models::FilesetEntity {} impl ApiEntityModel for models::WebcaptureEntity {} -impl ApiEntityModel for models::Editor{} -impl ApiEntityModel for models::Editgroup{} -impl ApiEntityModel for models::ChangelogEntry{} +impl ApiEntityModel for models::Editor {} +impl ApiEntityModel for models::Editgroup {} +impl ApiEntityModel for models::ChangelogEntry {} pub trait ApiModelSer { fn to_json_string(&self) -> Result; @@ -68,7 +64,6 @@ pub trait ApiModelSer { } impl ApiModelSer for T { - fn to_json_string(&self) -> Result { Ok(serde_json::to_string(self)?) } @@ -85,20 +80,38 @@ pub trait ApiModelIdent { macro_rules! generic_entity_specifier { ($specifier_type:ident) => { fn specifier(&self) -> Specifier { - if let Some(fcid) = &self.ident { Specifier::$specifier_type(fcid.to_string()) } else { panic!("expected full entity") } + if let Some(fcid) = &self.ident { + Specifier::$specifier_type(fcid.to_string()) + } else { + panic!("expected full entity") + } } - } + }; } -impl ApiModelIdent for models::ReleaseEntity { generic_entity_specifier!(Release); } -impl ApiModelIdent for models::ContainerEntity { generic_entity_specifier!(Container); } -impl ApiModelIdent for models::CreatorEntity { generic_entity_specifier!(Creator); } -impl ApiModelIdent for models::WorkEntity { generic_entity_specifier!(Work); } -impl ApiModelIdent for models::FileEntity { generic_entity_specifier!(File); } -impl ApiModelIdent for models::FilesetEntity { generic_entity_specifier!(FileSet); } -impl ApiModelIdent for models::WebcaptureEntity { generic_entity_specifier!(WebCapture); } +impl ApiModelIdent for models::ReleaseEntity { + generic_entity_specifier!(Release); +} +impl ApiModelIdent for models::ContainerEntity { + generic_entity_specifier!(Container); +} +impl ApiModelIdent for models::CreatorEntity { + generic_entity_specifier!(Creator); +} +impl ApiModelIdent for models::WorkEntity { + generic_entity_specifier!(Work); +} +impl ApiModelIdent for models::FileEntity { + generic_entity_specifier!(File); +} +impl ApiModelIdent for models::FilesetEntity { + generic_entity_specifier!(FileSet); +} +impl ApiModelIdent for models::WebcaptureEntity { + generic_entity_specifier!(WebCapture); +} -impl ApiModelIdent for models::ChangelogEntry{ +impl ApiModelIdent for models::ChangelogEntry { fn specifier(&self) -> Specifier { Specifier::Changelog(self.index) } @@ -106,13 +119,21 @@ impl ApiModelIdent for models::ChangelogEntry{ impl ApiModelIdent for models::Editgroup { fn specifier(&self) -> Specifier { - if let Some(fcid) = &self.editgroup_id { Specifier::Editgroup(fcid.to_string()) } else { panic!("expected full entity") } + if let Some(fcid) = &self.editgroup_id { + Specifier::Editgroup(fcid.to_string()) + } else { + panic!("expected full entity") + } } } impl ApiModelIdent for models::Editor { fn specifier(&self) -> Specifier { - if let Some(fcid) = &self.editor_id { Specifier::Editor(fcid.to_string()) } else { panic!("expected full entity") } + if let Some(fcid) = &self.editor_id { + Specifier::Editor(fcid.to_string()) + } else { + panic!("expected full entity") + } } } @@ -124,19 +145,45 @@ impl ApiModelMutate for models::ReleaseEntity { fn mutate(&mut self, mutations: Vec) -> Result<()> { for m in mutations { match (m.field.as_str(), m.value) { - ("title", val) => { self.title = val; }, - ("subtitle", val) => { self.subtitle = val; }, - ("container_id", val) => { self.container_id = val; }, - ("work_id", val) => { self.work_id = val; }, - ("release_type", val) => { self.release_type = val; }, - ("release_stage", val) => { self.release_stage = val; }, - ("withdrawn_status", val) => { self.withdrawn_status = val; }, - ("license_slug", val) => { self.license_slug= val; }, - ("volume", val) => { self.volume = val; }, - ("issue", val) => { self.issue = val; }, - ("number", val) => { self.number = val; }, - ("publisher", val) => { self.publisher = val; }, - ("language", val) => { self.language = val; }, + ("title", val) => { + self.title = val; + } + ("subtitle", val) => { + self.subtitle = val; + } + ("container_id", val) => { + self.container_id = val; + } + ("work_id", val) => { + self.work_id = val; + } + ("release_type", val) => { + self.release_type = val; + } + ("release_stage", val) => { + self.release_stage = val; + } + ("withdrawn_status", val) => { + self.withdrawn_status = val; + } + ("license_slug", val) => { + self.license_slug = val; + } + ("volume", val) => { + self.volume = val; + } + ("issue", val) => { + self.issue = val; + } + ("number", val) => { + self.number = val; + } + ("publisher", val) => { + self.publisher = val; + } + ("language", val) => { + self.language = val; + } (field, _) => unimplemented!("setting field {} on a release", field), } } @@ -148,10 +195,18 @@ impl ApiModelMutate for models::ContainerEntity { fn mutate(&mut self, mutations: Vec) -> Result<()> { for m in mutations { match (m.field.as_str(), m.value) { - ("name", val) => { self.name = val; }, - ("container_type", val) => { self.container_type = val; }, - ("publisher", val) => { self.publisher = val; }, - ("issnl", val) => { self.issnl = val; }, + ("name", val) => { + self.name = val; + } + ("container_type", val) => { + self.container_type = val; + } + ("publisher", val) => { + self.publisher = val; + } + ("issnl", val) => { + self.issnl = val; + } (field, _) => unimplemented!("setting field {} on a container", field), } } @@ -163,9 +218,15 @@ impl ApiModelMutate for models::CreatorEntity { fn mutate(&mut self, mutations: Vec) -> Result<()> { for m in mutations { match (m.field.as_str(), m.value) { - ("display_name", val) => { self.display_name = val; }, - ("given_name", val) => { self.given_name = val; }, - ("surname", val) => { self.surname = val; }, + ("display_name", val) => { + self.display_name = val; + } + ("given_name", val) => { + self.given_name = val; + } + ("surname", val) => { + self.surname = val; + } (field, _) => unimplemented!("setting field {} on a creator", field), } } @@ -183,12 +244,24 @@ impl ApiModelMutate for models::FileEntity { fn mutate(&mut self, mutations: Vec) -> Result<()> { for m in mutations { match (m.field.as_str(), m.value) { - ("size", Some(val)) => { self.size = Some(i64::from_str(&val)?); }, - ("size", None) => { self.size = None; }, - ("md5", val) => { self.md5 = val; }, - ("sha1", val) => { self.sha1 = val; }, - ("sha256", val) => { self.sha256 = val; }, - ("mimetype", val) => { self.mimetype = val; }, + ("size", Some(val)) => { + self.size = Some(i64::from_str(&val)?); + } + ("size", None) => { + self.size = None; + } + ("md5", val) => { + self.md5 = val; + } + ("sha1", val) => { + self.sha1 = val; + } + ("sha256", val) => { + self.sha256 = val; + } + ("mimetype", val) => { + self.mimetype = val; + } (field, _) => unimplemented!("setting field {} on a file", field), } } @@ -212,7 +285,9 @@ impl ApiModelMutate for models::Editor { fn mutate(&mut self, mutations: Vec) -> Result<()> { for m in mutations { match (m.field.as_str(), m.value) { - ("username", Some(val)) => { self.username = val; }, + ("username", Some(val)) => { + self.username = val; + } (field, _) => unimplemented!("setting field {} on an editor", field), } } @@ -224,7 +299,9 @@ impl ApiModelMutate for models::Editgroup { fn mutate(&mut self, mutations: Vec) -> Result<()> { for m in mutations { match (m.field.as_str(), m.value) { - ("description", val) => { self.description = val; }, + ("description", val) => { + self.description = val; + } (field, _) => unimplemented!("setting field {} on an editgroup", field), } } @@ -245,12 +322,26 @@ mod tests { #[test] fn test_mutation_from_str() -> () { assert!(Mutation::from_str("release_asdf").is_err()); - assert_eq!(Mutation::from_str("title=blah").unwrap(), - Mutation { field: "title".to_string(), value: Some("blah".to_string()) }); - assert_eq!(Mutation::from_str("title=").unwrap(), - Mutation { field: "title".to_string(), value: None }); - assert_eq!(Mutation::from_str("title=string with spaces and stuff").unwrap(), - Mutation { field: "title".to_string(), value: Some("string with spaces and stuff".to_string()) }); + assert_eq!( + Mutation::from_str("title=blah").unwrap(), + Mutation { + field: "title".to_string(), + value: Some("blah".to_string()) + } + ); + assert_eq!( + Mutation::from_str("title=").unwrap(), + Mutation { + field: "title".to_string(), + value: None + } + ); + assert_eq!( + Mutation::from_str("title=string with spaces and stuff").unwrap(), + Mutation { + field: "title".to_string(), + value: Some("string with spaces and stuff".to_string()) + } + ); } - } diff --git a/rust/fatcat-cli/src/lib.rs b/rust/fatcat-cli/src/lib.rs index 1fffd50..fc9f209 100644 --- a/rust/fatcat-cli/src/lib.rs +++ b/rust/fatcat-cli/src/lib.rs @@ -1,28 +1,27 @@ - -use std::io::Read; -use std::path::PathBuf; -use std::io::BufRead; -use tabwriter::TabWriter; +use anyhow::{anyhow, Context, Result}; use chrono_humanize::HumanTime; -use anyhow::{Result, anyhow, Context}; -use std::io::Write; -use termcolor::{ColorChoice, StandardStream, Color, ColorSpec, WriteColor}; use data_encoding::BASE64; -use macaroon::{Macaroon, Verifier}; use fatcat_openapi::models; #[allow(unused_imports)] -use log::{self,info,debug}; +use log::{self, debug, info}; +use macaroon::{Macaroon, Verifier}; +use std::io::BufRead; +use std::io::Read; +use std::io::Write; +use std::path::PathBuf; use std::str::FromStr; +use tabwriter::TabWriter; +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; +mod api; mod entities; mod search; mod specifier; -mod api; -pub use entities::{ApiEntityModel,ApiModelSer,ApiModelIdent,Mutation}; -pub use specifier::Specifier; pub use api::FatcatApiClient; +pub use entities::{ApiEntityModel, ApiModelIdent, ApiModelSer, Mutation}; pub use search::crude_search; +pub use specifier::Specifier; // Want to show: // - whether api_token found @@ -39,16 +38,12 @@ pub struct ClientStatus { } impl ClientStatus { - pub fn pretty_print(self) -> Result<()> { - - let mut color_stdout = StandardStream::stdout( - if atty::is(atty::Stream::Stdout) { - ColorChoice::Auto - } else { - ColorChoice::Never - } - ); + let mut color_stdout = StandardStream::stdout(if atty::is(atty::Stream::Stdout) { + ColorChoice::Auto + } else { + ColorChoice::Never + }); let color_normal = ColorSpec::new(); let mut color_bold = ColorSpec::new(); color_bold.set_bold(true); @@ -69,7 +64,7 @@ impl ClientStatus { write!(&mut color_stdout, "{:>16}: ", "Last changelog")?; color_stdout.set_color(&color_bold)?; writeln!(&mut color_stdout, "{}", index)?; - }, + } None => { color_stdout.set_color(&color_sad)?; writeln!(&mut color_stdout, " [Failed to connect]")?; @@ -90,25 +85,32 @@ impl ClientStatus { color_stdout.set_color(&color_bold)?; write!(&mut color_stdout, "{}", editor.username)?; if editor.is_bot == Some(true) { - color_stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue)).set_bold(true))?; + color_stdout + .set_color(ColorSpec::new().set_fg(Some(Color::Blue)).set_bold(true))?; write!(&mut color_stdout, " [bot]")?; } if editor.is_admin == Some(true) { - color_stdout.set_color(ColorSpec::new().set_fg(Some(Color::Magenta)).set_bold(true))?; + color_stdout + .set_color(ColorSpec::new().set_fg(Some(Color::Magenta)).set_bold(true))?; write!(&mut color_stdout, " [admin]")?; } match editor.is_active { Some(true) => { color_stdout.set_color(&color_happy)?; writeln!(&mut color_stdout, " [active]")?; - }, + } Some(false) | None => { color_stdout.set_color(&color_sad)?; writeln!(&mut color_stdout, " [disabled]")?; - }, + } }; color_stdout.set_color(&color_normal)?; - writeln!(&mut color_stdout, "{:>16} editor_{}", "", editor.editor_id.unwrap())?; + writeln!( + &mut color_stdout, + "{:>16} editor_{}", + "", + editor.editor_id.unwrap() + )?; }; color_stdout.set_color(&color_normal)?; Ok(()) @@ -145,14 +147,25 @@ impl FromStr for EntityType { /// Takes a macaroon token (as base64-encoded string) and tries to parse out an editor id pub fn parse_macaroon_editor_id(s: &str) -> Result { - let raw = BASE64.decode(s.as_bytes()).context("macaroon parsing failed")?; - let mac = Macaroon::deserialize(&raw).map_err(|err| anyhow!("macaroon deserialization failed: {:?}", err))?; - let mac = mac.validate().map_err(|err| anyhow!("macaroon validation failed: {:?}", err))?; + let raw = BASE64 + .decode(s.as_bytes()) + .context("macaroon parsing failed")?; + let mac = Macaroon::deserialize(&raw) + .map_err(|err| anyhow!("macaroon deserialization failed: {:?}", err))?; + let mac = mac + .validate() + .map_err(|err| anyhow!("macaroon validation failed: {:?}", err))?; let mut verifier = Verifier::new(); let mut editor_id: Option = None; for caveat in mac.first_party_caveats() { if caveat.predicate().starts_with("editor_id = ") { - editor_id = Some(caveat.predicate().get(12..).context("parsing macaroon")?.to_string()); + editor_id = Some( + caveat + .predicate() + .get(12..) + .context("parsing macaroon")? + .to_string(), + ); break; } } @@ -171,14 +184,23 @@ pub fn print_editgroups(eg_list: Vec, json: bool) -> Result<( } } else { let mut tw = TabWriter::new(std::io::stdout()); - writeln!(tw, "editgroup_id\tchangelog_index\tcreated\tsubmitted\tdescription")?; + writeln!( + tw, + "editgroup_id\tchangelog_index\tcreated\tsubmitted\tdescription" + )?; for eg in eg_list { - writeln!(tw, "{}\t{}\t{}\t{}\t{}", + writeln!( + tw, + "{}\t{}\t{}\t{}\t{}", eg.editgroup_id.unwrap(), - eg.changelog_index.map_or("-".to_string(), |v| v.to_string()), - eg.created.map_or("-".to_string(), |v| HumanTime::from(v).to_string()), - eg.submitted.map_or("-".to_string(), |v| HumanTime::from(v).to_string()), - eg.description.unwrap_or_else(|| "-".to_string()))?; + eg.changelog_index + .map_or("-".to_string(), |v| v.to_string()), + eg.created + .map_or("-".to_string(), |v| HumanTime::from(v).to_string()), + eg.submitted + .map_or("-".to_string(), |v| HumanTime::from(v).to_string()), + eg.description.unwrap_or_else(|| "-".to_string()) + )?; } tw.flush()?; } @@ -196,22 +218,23 @@ pub fn read_entity_file(input_path: Option) -> Result { let mut line = String::new(); std::io::stdin().read_line(&mut line)?; Ok(line) - }, + } Some(path) if path.extension().map(|v| v.to_str()) == Some(Some("toml")) => { info!("reading {:?} as TOML", path); // as a hack, read TOML but then serialize it back to JSON let mut contents = String::new(); - let mut input_file = std::fs::File::open(path).context("reading entity from TOML file")?; + let mut input_file = + std::fs::File::open(path).context("reading entity from TOML file")?; input_file.read_to_string(&mut contents)?; let value: toml::Value = contents.parse().context("parsing TOML file")?; Ok(serde_json::to_string(&value)?) - }, + } Some(path) => { let mut line = String::new(); let input_file = std::fs::File::open(path)?; let mut buffered = std::io::BufReader::new(input_file); buffered.read_line(&mut line)?; Ok(line) - }, + } } } diff --git a/rust/fatcat-cli/src/main.rs b/rust/fatcat-cli/src/main.rs index 05b003d..ce12f9d 100644 --- a/rust/fatcat-cli/src/main.rs +++ b/rust/fatcat-cli/src/main.rs @@ -1,27 +1,36 @@ - -use std::path::PathBuf; +use anyhow::{anyhow, Context, Result}; use fatcat_cli::ApiModelSer; -use std::io::Write; -use termcolor::{ColorChoice, StandardStream, Color, ColorSpec, WriteColor}; -use anyhow::{Result, Context, anyhow}; -#[allow(unused_imports)] -use log::{self,info,debug}; -use structopt::StructOpt; use fatcat_cli::*; use fatcat_openapi::{client, models, ApiNoContext}; - +#[allow(unused_imports)] +use log::{self, debug, info}; +use std::io::Write; +use std::path::PathBuf; +use structopt::StructOpt; +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; #[derive(StructOpt)] -#[structopt(rename_all = "kebab-case", about = "CLI interface to Fatcat API" )] +#[structopt(rename_all = "kebab-case", about = "CLI interface to Fatcat API")] struct Opt { - - #[structopt(long = "--api-host", env = "FATCAT_API_HOST", default_value = "https://api.fatcat.wiki")] + #[structopt( + long = "--api-host", + env = "FATCAT_API_HOST", + default_value = "https://api.fatcat.wiki" + )] api_host: String, - #[structopt(long = "--api-token", env = "FATCAT_API_AUTH_TOKEN", hide_env_values = true)] + #[structopt( + long = "--api-token", + env = "FATCAT_API_AUTH_TOKEN", + hide_env_values = true + )] api_token: Option, - #[structopt(long = "--search-host", env = "FATCAT_SEARCH_HOST", default_value = "https://search.fatcat.wiki")] + #[structopt( + long = "--search-host", + env = "FATCAT_SEARCH_HOST", + default_value = "https://search.fatcat.wiki" + )] search_host: String, /// Pass many times for more log output @@ -97,7 +106,12 @@ enum Command { #[structopt(long = "--file", short = "-f", parse(from_os_str))] input_path: Option, - #[structopt(long = "--editgroup-id", short, env = "FATCAT_EDITGROUP", hide_env_values = true)] + #[structopt( + long = "--editgroup-id", + short, + env = "FATCAT_EDITGROUP", + hide_env_values = true + )] editgroup_id: String, }, Update { @@ -107,7 +121,12 @@ enum Command { #[structopt(long = "--file", short = "-f", parse(from_os_str))] input_path: Option, - #[structopt(long = "--editgroup-id", short, env = "FATCAT_EDITGROUP", hide_env_values = true)] + #[structopt( + long = "--editgroup-id", + short, + env = "FATCAT_EDITGROUP", + hide_env_values = true + )] editgroup_id: String, mutations: Vec, @@ -115,7 +134,12 @@ enum Command { Edit { specifier: Specifier, - #[structopt(long = "--editgroup-id", short, env = "FATCAT_EDITGROUP", hide_env_values = true)] + #[structopt( + long = "--editgroup-id", + short, + env = "FATCAT_EDITGROUP", + hide_env_values = true + )] editgroup_id: String, #[structopt(long)] @@ -127,7 +151,12 @@ enum Command { Delete { specifier: Specifier, - #[structopt(long = "--editgroup-id", short, env = "FATCAT_EDITGROUP", hide_env_values = true)] + #[structopt( + long = "--editgroup-id", + short, + env = "FATCAT_EDITGROUP", + hide_env_values = true + )] editgroup_id: String, }, Editgroup { @@ -138,7 +167,6 @@ enum Command { //Download //History Search { - entity_type: EntityType, terms: Vec, @@ -186,13 +214,11 @@ fn main() -> Result<()> { std::process::exit(0); } } - let mut color_stderr = StandardStream::stderr( - if atty::is(atty::Stream::Stderr) { - ColorChoice::Auto - } else { - ColorChoice::Never - } - ); + let mut color_stderr = StandardStream::stderr(if atty::is(atty::Stream::Stderr) { + ColorChoice::Auto + } else { + ColorChoice::Never + }); color_stderr.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?; eprintln!("Error: {:?}", err); color_stderr.set_color(&ColorSpec::new())?; @@ -212,46 +238,69 @@ fn run(opt: Opt) -> Result<()> { return Err(anyhow!("unsupported API Host prefix: {}", opt.api_host)); }; - let mut api_client = FatcatApiClient::new(&client, opt.api_host.clone(), opt.api_token.clone())?; + let mut api_client = + FatcatApiClient::new(&client, opt.api_host.clone(), opt.api_token.clone())?; match opt.cmd { - Command::Get {toml, specifier, expand, hide } => { + Command::Get { + toml, + specifier, + expand, + hide, + } => { let result = specifier.get_from_api(&mut api_client, expand, hide)?; if toml { writeln!(&mut std::io::stdout(), "{}", result.to_toml_string()?)? } else { writeln!(&mut std::io::stdout(), "{}", result.to_json_string()?)? } - }, - Command::Create { entity_type, input_path, editgroup_id } => { + } + Command::Create { + entity_type, + input_path, + editgroup_id, + } => { let json_str = read_entity_file(input_path)?; let ee = api_client.create_entity_from_json(entity_type, &json_str, editgroup_id)?; println!("{}", serde_json::to_string(&ee)?); - }, - Command::Update { specifier, input_path, editgroup_id, mutations } => { - let (json_str, exact_specifier): (String, Specifier) = match (&input_path, mutations.len()) { - // input path or no mutations: read from path or stdin - (Some(_), _) | (None, 0) => { - (read_entity_file(input_path)?, specifier.into_entity_specifier(&mut api_client)?) - }, - // no input path *and* mutations: fetch from API - (None, _) => { - let mut entity = specifier.get_from_api(&mut api_client, None, None)?; - entity.mutate(mutations)?; - (entity.to_json_string()?, entity.specifier()) - }, - }; - let ee = api_client.update_entity_from_json(exact_specifier, &json_str, editgroup_id)?; + } + Command::Update { + specifier, + input_path, + editgroup_id, + mutations, + } => { + let (json_str, exact_specifier): (String, Specifier) = + match (&input_path, mutations.len()) { + // input path or no mutations: read from path or stdin + (Some(_), _) | (None, 0) => ( + read_entity_file(input_path)?, + specifier.into_entity_specifier(&mut api_client)?, + ), + // no input path *and* mutations: fetch from API + (None, _) => { + let mut entity = specifier.get_from_api(&mut api_client, None, None)?; + entity.mutate(mutations)?; + (entity.to_json_string()?, entity.specifier()) + } + }; + let ee = + api_client.update_entity_from_json(exact_specifier, &json_str, editgroup_id)?; println!("{}", serde_json::to_string(&ee)?); - }, - Command::Edit { specifier, editgroup_id, json, editing_command } => { + } + Command::Edit { + specifier, + editgroup_id, + json, + editing_command, + } => { // TODO: fetch editgroup, check if this entity is already being updated in it. If so, // need to fetch that revision, do the edit, parse that synatx is good, then delete the // existing edit and update with the new one. let original_entity = specifier.get_from_api(&mut api_client, None, None)?; let exact_specifier = original_entity.specifier(); let tmp_file = tempfile::Builder::new() - .suffix( if json { ".json" } else { ".toml"} ) + .suffix(if json { ".json" } else { ".toml" }) .tempfile()?; if json { writeln!(&tmp_file, "{}", original_entity.to_json_string()?)? @@ -264,16 +313,35 @@ fn run(opt: Opt) -> Result<()> { .expect("failed to execute process"); let cmd_status = editor_cmd.wait()?; if !cmd_status.success() { - return Err(anyhow!("editor ({}) exited with non-success status code ({}), bailing on edit", editing_command, cmd_status.code().map(|v| v.to_string()).unwrap_or_else(|| "N/A".to_string()))); + return Err(anyhow!( + "editor ({}) exited with non-success status code ({}), bailing on edit", + editing_command, + cmd_status + .code() + .map(|v| v.to_string()) + .unwrap_or_else(|| "N/A".to_string()) + )); }; let json_str = read_entity_file(Some(tmp_file.path().to_path_buf()))?; // for whatever reason api_client's TCP connection is broken after spawning, so try a // dummy call, expected to fail, but connection should re-establish after this - specifier.get_from_api(&mut api_client, None, None).context("re-fetch").ok(); - let ee = api_client.update_entity_from_json(exact_specifier, &json_str, editgroup_id).context("updating after edit")?; + specifier + .get_from_api(&mut api_client, None, None) + .context("re-fetch") + .ok(); + let ee = api_client + .update_entity_from_json(exact_specifier, &json_str, editgroup_id) + .context("updating after edit")?; println!("{}", serde_json::to_string(&ee)?); - }, - Command::Search { entity_type, terms, limit, search_schema, expand, hide } => { + } + Command::Search { + entity_type, + terms, + limit, + search_schema, + expand, + hide, + } => { let limit: Option = match limit { l if l < 0 => None, l => Some(l as u64), @@ -286,79 +354,137 @@ fn run(opt: Opt) -> Result<()> { match (search_schema, entity_type) { (true, _) => writeln!(&mut std::io::stdout(), "{}", hit.to_string())?, (false, EntityType::Release) => { - let specifier = Specifier::Release(hit["ident"].as_str().unwrap().to_string()); - let entity = specifier.get_from_api(&mut api_client, expand.clone(), hide.clone())?; + let specifier = + Specifier::Release(hit["ident"].as_str().unwrap().to_string()); + let entity = specifier.get_from_api( + &mut api_client, + expand.clone(), + hide.clone(), + )?; writeln!(&mut std::io::stdout(), "{}", entity.to_json_string()?)? - }, + } (false, _) => unimplemented!("searching other entity types"), } } - }, - Command::Delete { specifier, editgroup_id } => { - let result = api_client.delete_entity(specifier.clone(), editgroup_id) + } + Command::Delete { + specifier, + editgroup_id, + } => { + let result = api_client + .delete_entity(specifier.clone(), editgroup_id) .with_context(|| format!("delete entity: {:?}", specifier))?; println!("{}", serde_json::to_string(&result)?); - }, - Command::Editgroup { cmd: EditgroupCommand::List { editor_id, limit, json } } => { + } + Command::Editgroup { + cmd: + EditgroupCommand::List { + editor_id, + limit, + json, + }, + } => { let editor_id = match editor_id.or(api_client.editor_id) { Some(eid) => eid, None => return Err(anyhow!("require either working auth token or --editor-id")), }; - let result = api_client.rt.block_on( - api_client.api.get_editor_editgroups(editor_id.clone(), Some(limit), None, None) - ).context("fetch editgroups")?; + let result = api_client + .rt + .block_on(api_client.api.get_editor_editgroups( + editor_id.clone(), + Some(limit), + None, + None, + )) + .context("fetch editgroups")?; match result { fatcat_openapi::GetEditorEditgroupsResponse::Found(eg_list) => { print_editgroups(eg_list, json)?; - }, - other => return Err(anyhow!("{:?}", other)).with_context(|| format!("failed to fetch editgroups for editor_{}", editor_id)), + } + other => { + return Err(anyhow!("{:?}", other)).with_context(|| { + format!("failed to fetch editgroups for editor_{}", editor_id) + }) + } } - }, - Command::Editgroup { cmd: EditgroupCommand::Reviewable { limit, json } } => { - let result = api_client.rt.block_on( - api_client.api.get_editgroups_reviewable(Some("editors".to_string()), Some(limit), None, None) - ).context("fetch reviewable editgroups")?; + } + Command::Editgroup { + cmd: EditgroupCommand::Reviewable { limit, json }, + } => { + let result = api_client + .rt + .block_on(api_client.api.get_editgroups_reviewable( + Some("editors".to_string()), + Some(limit), + None, + None, + )) + .context("fetch reviewable editgroups")?; match result { fatcat_openapi::GetEditgroupsReviewableResponse::Found(eg_list) => { print_editgroups(eg_list, json)?; - }, - other => return Err(anyhow!("{:?}", other)).context("failed to fetch reviewable editgroups"), + } + other => { + return Err(anyhow!("{:?}", other)) + .context("failed to fetch reviewable editgroups") + } } - }, - Command::Editgroup { cmd: EditgroupCommand::Create { description }} => { + } + Command::Editgroup { + cmd: EditgroupCommand::Create { description }, + } => { let mut eg = models::Editgroup::new(); eg.description = Some(description); eg.extra = Some({ let mut extra = std::collections::HashMap::new(); - extra.insert("agent".to_string(), serde_json::Value::String("fatcat-cli".to_string())); + extra.insert( + "agent".to_string(), + serde_json::Value::String("fatcat-cli".to_string()), + ); extra }); - let result = api_client.rt.block_on( - api_client.api.create_editgroup(eg))?; + let result = api_client + .rt + .block_on(api_client.api.create_editgroup(eg))?; match result { - fatcat_openapi::CreateEditgroupResponse::SuccessfullyCreated(eg) => - println!("{}", serde_json::to_string(&eg)?), + fatcat_openapi::CreateEditgroupResponse::SuccessfullyCreated(eg) => { + println!("{}", serde_json::to_string(&eg)?) + } other => return Err(anyhow!("{:?}", other)).context("failed to create editgroup"), } - }, - Command::Editgroup { cmd: EditgroupCommand::Accept { editgroup_id } } => { - let result = api_client.rt.block_on( - api_client.api.accept_editgroup(editgroup_id.clone()) - ).context("accept editgroup")?; + } + Command::Editgroup { + cmd: EditgroupCommand::Accept { editgroup_id }, + } => { + let result = api_client + .rt + .block_on(api_client.api.accept_editgroup(editgroup_id.clone())) + .context("accept editgroup")?; match result { - fatcat_openapi::AcceptEditgroupResponse::MergedSuccessfully(msg) => - println!("{}", serde_json::to_string(&msg)?), - other => return Err(anyhow!("failed to accept editgroup {}: {:?}", editgroup_id, other)), + fatcat_openapi::AcceptEditgroupResponse::MergedSuccessfully(msg) => { + println!("{}", serde_json::to_string(&msg)?) + } + other => { + return Err(anyhow!( + "failed to accept editgroup {}: {:?}", + editgroup_id, + other + )) + } } - }, - Command::Editgroup { cmd: EditgroupCommand::Submit{ editgroup_id } } => { + } + Command::Editgroup { + cmd: EditgroupCommand::Submit { editgroup_id }, + } => { let eg = api_client.update_editgroup_submit(editgroup_id, true)?; println!("{}", eg.to_json_string()?); - }, - Command::Editgroup { cmd: EditgroupCommand::Unsubmit { editgroup_id } } => { + } + Command::Editgroup { + cmd: EditgroupCommand::Unsubmit { editgroup_id }, + } => { let eg = api_client.update_editgroup_submit(editgroup_id, false)?; println!("{}", eg.to_json_string()?); - }, + } Command::Status { json } => { let status = api_client.status()?; if json { @@ -366,7 +492,7 @@ fn run(opt: Opt) -> Result<()> { } else { status.pretty_print()?; } - }, + } } Ok(()) } diff --git a/rust/fatcat-cli/src/search.rs b/rust/fatcat-cli/src/search.rs index d7fbbad..133ea41 100644 --- a/rust/fatcat-cli/src/search.rs +++ b/rust/fatcat-cli/src/search.rs @@ -1,10 +1,8 @@ - +use crate::EntityType; +use anyhow::{anyhow, Result}; +use log::{self, info}; use serde_json::json; use std::time::Duration; -use anyhow::{Result, anyhow}; -use log::{self,info}; -use crate::EntityType; - pub struct SearchResults { pub entity_type: EntityType, @@ -25,17 +23,22 @@ impl Iterator for SearchResults { // if we already hit limit, bail early if let Some(l) = self.limit { if self.offset >= l { - return None + return None; } } // if current batch is empty, and we are scrolling, refill the current batch if self.batch.is_empty() && self.scroll_id.is_some() { - let response = self.http_client.get(&self.scroll_url) + let response = self + .http_client + .get(&self.scroll_url) .header("Content-Type", "application/json") - .body(json!({ - "scroll": "2m", - "scroll_id": self.scroll_id.clone().unwrap(), - }).to_string()) + .body( + json!({ + "scroll": "2m", + "scroll_id": self.scroll_id.clone().unwrap(), + }) + .to_string(), + ) .send(); let mut response = match response { Err(e) => return Some(Err(e.into())), @@ -66,13 +69,22 @@ impl Iterator for SearchResults { } } -pub fn crude_search(api_host: &str, entity_type: EntityType, limit: Option, terms: Vec) -> Result { - +pub fn crude_search( + api_host: &str, + entity_type: EntityType, + limit: Option, + terms: Vec, +) -> Result { let index = match entity_type { EntityType::Release => "fatcat_release", EntityType::File => "fatcat_file", EntityType::Container => "fatcat_container", - _ => return Err(anyhow!("No search index for entity type: {:?}", entity_type)), + _ => { + return Err(anyhow!( + "No search index for entity type: {:?}", + entity_type + )) + } }; let http_client = reqwest::Client::builder() .timeout(Duration::from_secs(10)) @@ -94,50 +106,51 @@ pub fn crude_search(api_host: &str, entity_type: EntityType, limit: Option, None => (true, "_doc", 100), Some(l) if l > 100 => (true, "_doc", 100), Some(l) => (false, "_score", l), - }; let query_body = json!({ - "query": { - "boosting": { - "positive": { - "bool": { - "must": { - "query_string": { - "query": query, - "default_operator": "AND", - "analyze_wildcard": true, - "allow_leading_wildcard": false, - "lenient": true, - "fields": [ - "title^2", - "biblio", - ], - }, - }, - "should": { - "term": { "in_ia": true }, + "query": { + "boosting": { + "positive": { + "bool": { + "must": { + "query_string": { + "query": query, + "default_operator": "AND", + "analyze_wildcard": true, + "allow_leading_wildcard": false, + "lenient": true, + "fields": [ + "title^2", + "biblio", + ], }, }, - }, - "negative": { - "bool": { - "should": [ - {"bool": { "must_not" : { "exists": { "field": "title" }}}}, - {"bool": { "must_not" : { "exists": { "field": "year" }}}}, - {"bool": { "must_not" : { "exists": { "field": "type" }}}}, - {"bool": { "must_not" : { "exists": { "field": "stage" }}}}, - ], + "should": { + "term": { "in_ia": true }, }, }, - "negative_boost": 0.5, }, + "negative": { + "bool": { + "should": [ + {"bool": { "must_not" : { "exists": { "field": "title" }}}}, + {"bool": { "must_not" : { "exists": { "field": "year" }}}}, + {"bool": { "must_not" : { "exists": { "field": "type" }}}}, + {"bool": { "must_not" : { "exists": { "field": "stage" }}}}, + ], + }, + }, + "negative_boost": 0.5, }, - "size": size, - "sort": [ sort_mode ], - }).to_string(); + }, + "size": size, + "sort": [ sort_mode ], + }) + .to_string(); - let mut request = http_client.get(&request_url) + let mut request = http_client + .get(&request_url) .header("Content-Type", "application/json") .body(query_body); @@ -154,9 +167,9 @@ pub fn crude_search(api_host: &str, entity_type: EntityType, limit: Option, let body: serde_json::Value = response.json()?; let scroll_id = if scroll_mode { - None - } else { Some(body["_scroll_id"].as_str().unwrap().to_string()) + } else { + None }; Ok(SearchResults { diff --git a/rust/fatcat-cli/src/specifier.rs b/rust/fatcat-cli/src/specifier.rs index 3085345..c9bc581 100644 --- a/rust/fatcat-cli/src/specifier.rs +++ b/rust/fatcat-cli/src/specifier.rs @@ -1,11 +1,9 @@ - +use crate::{ApiEntityModel, FatcatApiClient}; +use anyhow::{anyhow, Context, Result}; use fatcat_openapi::ApiNoContext; -use anyhow::{Result, anyhow, Context}; -use std::str::FromStr; use lazy_static::lazy_static; use regex::Regex; -use crate::{ApiEntityModel, FatcatApiClient}; - +use std::str::FromStr; #[derive(Debug, PartialEq, Clone)] pub enum ReleaseLookupKey { @@ -53,156 +51,305 @@ pub enum Specifier { } impl Specifier { - /// If this Specifier is a lookup, call the API to do the lookup and return the resulting /// specific entity specifier (eg, with an FCID). If already specific, just pass through. pub fn into_entity_specifier(self, api_client: &mut FatcatApiClient) -> Result { use Specifier::*; match self { - Release(_) | Work(_) | Creator(_) | Container(_) | File(_) | FileSet(_) | WebCapture(_) | Editgroup(_) | Editor(_) | Changelog(_) => Ok(self), + Release(_) | Work(_) | Creator(_) | Container(_) | File(_) | FileSet(_) + | WebCapture(_) | Editgroup(_) | Editor(_) | Changelog(_) => Ok(self), ReleaseLookup(_, _) => Ok(self.get_from_api(api_client, None, None)?.specifier()), ContainerLookup(_, _) => Ok(self.get_from_api(api_client, None, None)?.specifier()), CreatorLookup(_, _) => Ok(self.get_from_api(api_client, None, None)?.specifier()), FileLookup(_, _) => Ok(self.get_from_api(api_client, None, None)?.specifier()), - EditorUsername(_username) => { - Err(anyhow!("editor lookup by username isn't implemented in fatcat-server API yet, sorry")) - }, + EditorUsername(_username) => Err(anyhow!( + "editor lookup by username isn't implemented in fatcat-server API yet, sorry" + )), } } - pub fn get_from_api(&self, api_client: &mut FatcatApiClient, expand: Option, hide: Option) -> Result> { + pub fn get_from_api( + &self, + api_client: &mut FatcatApiClient, + expand: Option, + hide: Option, + ) -> Result> { use Specifier::*; let ret: Result> = match self { - Release(fcid) => - match api_client.rt.block_on(api_client.api.get_release(fcid.to_string(), expand, hide))? { - fatcat_openapi::GetReleaseResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::GetReleaseResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetReleaseResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, + Release(fcid) => match api_client.rt.block_on(api_client.api.get_release( + fcid.to_string(), + expand, + hide, + ))? { + fatcat_openapi::GetReleaseResponse::FoundEntity(model) => Ok(Box::new(model)), + fatcat_openapi::GetReleaseResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::GetReleaseResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), + }, ReleaseLookup(ext_id, key) => { use ReleaseLookupKey::*; let (doi, pmcid, pmid, arxiv) = ( - if let DOI = ext_id { Some(key.to_string()) } else { None }, - if let PMCID = ext_id { Some(key.to_string()) } else { None }, - if let PMID = ext_id { Some(key.to_string()) } else { None }, - if let Arxiv = ext_id { Some(key.to_string()) } else { None }, + if let DOI = ext_id { + Some(key.to_string()) + } else { + None + }, + if let PMCID = ext_id { + Some(key.to_string()) + } else { + None + }, + if let PMID = ext_id { + Some(key.to_string()) + } else { + None + }, + if let Arxiv = ext_id { + Some(key.to_string()) + } else { + None + }, ); // doi, wikidata, isbn13, pmid, pmcid, core, arxiv, jstor, ark, mag - let result = api_client.rt.block_on( - api_client.api.lookup_release(doi, None, None, pmid, pmcid, None, arxiv, None, None, None, expand, hide))?; + let result = api_client.rt.block_on(api_client.api.lookup_release( + doi, None, None, pmid, pmcid, None, arxiv, None, None, None, expand, hide, + ))?; match result { - fatcat_openapi::LookupReleaseResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::LookupReleaseResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::LookupReleaseResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), + fatcat_openapi::LookupReleaseResponse::FoundEntity(model) => { + Ok(Box::new(model)) + } + fatcat_openapi::LookupReleaseResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::LookupReleaseResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), + } + } + Work(fcid) => match api_client.rt.block_on(api_client.api.get_work( + fcid.to_string(), + expand, + hide, + ))? { + fatcat_openapi::GetWorkResponse::FoundEntity(model) => Ok(Box::new(model)), + fatcat_openapi::GetWorkResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) } + fatcat_openapi::GetWorkResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), + }, + Container(fcid) => match api_client.rt.block_on(api_client.api.get_container( + fcid.to_string(), + expand, + hide, + ))? { + fatcat_openapi::GetContainerResponse::FoundEntity(model) => Ok(Box::new(model)), + fatcat_openapi::GetContainerResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::GetContainerResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), }, - Work(fcid) => - match api_client.rt.block_on(api_client.api.get_work(fcid.to_string(), expand, hide))? { - fatcat_openapi::GetWorkResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::GetWorkResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetWorkResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, - Container(fcid) => - match api_client.rt.block_on(api_client.api.get_container(fcid.to_string(), expand, hide))? { - fatcat_openapi::GetContainerResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::GetContainerResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetContainerResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, ContainerLookup(ext_id, key) => { let result = api_client.rt.block_on(match ext_id { - ContainerLookupKey::ISSNL => api_client.api.lookup_container(Some(key.to_string()), None, expand, hide), + ContainerLookupKey::ISSNL => { + api_client + .api + .lookup_container(Some(key.to_string()), None, expand, hide) + } })?; match result { - fatcat_openapi::LookupContainerResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::LookupContainerResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::LookupContainerResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), + fatcat_openapi::LookupContainerResponse::FoundEntity(model) => { + Ok(Box::new(model)) + } + fatcat_openapi::LookupContainerResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::LookupContainerResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), + } + } + Creator(fcid) => match api_client.rt.block_on(api_client.api.get_creator( + fcid.to_string(), + expand, + hide, + ))? { + fatcat_openapi::GetCreatorResponse::FoundEntity(model) => Ok(Box::new(model)), + fatcat_openapi::GetCreatorResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::GetCreatorResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), }, - Creator(fcid) => - match api_client.rt.block_on(api_client.api.get_creator(fcid.to_string(), expand, hide))? { - fatcat_openapi::GetCreatorResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::GetCreatorResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetCreatorResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, CreatorLookup(ext_id, key) => { let result = api_client.rt.block_on(match ext_id { - CreatorLookupKey::Orcid => api_client.api.lookup_creator(Some(key.to_string()), None, expand, hide), + CreatorLookupKey::Orcid => { + api_client + .api + .lookup_creator(Some(key.to_string()), None, expand, hide) + } })?; match result { - fatcat_openapi::LookupCreatorResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::LookupCreatorResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::LookupCreatorResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), + fatcat_openapi::LookupCreatorResponse::FoundEntity(model) => { + Ok(Box::new(model)) + } + fatcat_openapi::LookupCreatorResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::LookupCreatorResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), } + } + File(fcid) => match api_client.rt.block_on(api_client.api.get_file( + fcid.to_string(), + expand, + hide, + ))? { + fatcat_openapi::GetFileResponse::FoundEntity(model) => Ok(Box::new(model)), + fatcat_openapi::GetFileResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::GetFileResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), }, - File(fcid) => - match api_client.rt.block_on(api_client.api.get_file(fcid.to_string(), expand, hide))? { - fatcat_openapi::GetFileResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::GetFileResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetFileResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, FileLookup(hash, key) => { use FileLookupKey::*; let (sha1, sha256, md5) = ( - if let SHA1 = hash { Some(key.to_string()) } else { None }, - if let SHA256 = hash { Some(key.to_string()) } else { None }, - if let MD5 = hash { Some(key.to_string()) } else { None }, + if let SHA1 = hash { + Some(key.to_string()) + } else { + None + }, + if let SHA256 = hash { + Some(key.to_string()) + } else { + None + }, + if let MD5 = hash { + Some(key.to_string()) + } else { + None + }, ); - let result = api_client.rt.block_on( - api_client.api.lookup_file(sha1, sha256, md5, expand, hide), - )?; + let result = api_client + .rt + .block_on(api_client.api.lookup_file(sha1, sha256, md5, expand, hide))?; match result { fatcat_openapi::LookupFileResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::LookupFileResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::LookupFileResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), + fatcat_openapi::LookupFileResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::LookupFileResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), + } + } + FileSet(fcid) => match api_client.rt.block_on(api_client.api.get_fileset( + fcid.to_string(), + expand, + hide, + ))? { + fatcat_openapi::GetFilesetResponse::FoundEntity(model) => Ok(Box::new(model)), + fatcat_openapi::GetFilesetResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) } + fatcat_openapi::GetFilesetResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), }, - FileSet(fcid) => - match api_client.rt.block_on(api_client.api.get_fileset(fcid.to_string(), expand, hide))? { - fatcat_openapi::GetFilesetResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::GetFilesetResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetFilesetResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, - WebCapture(fcid) => - match api_client.rt.block_on(api_client.api.get_webcapture(fcid.to_string(), expand, hide))? { - fatcat_openapi::GetWebcaptureResponse::FoundEntity(model) => Ok(Box::new(model)), - fatcat_openapi::GetWebcaptureResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetWebcaptureResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, - Editgroup(fcid) => - match api_client.rt.block_on(api_client.api.get_editgroup(fcid.to_string()))? { - fatcat_openapi::GetEditgroupResponse::Found(model) => Ok(Box::new(model)), - fatcat_openapi::GetEditgroupResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetEditgroupResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, - Editor(fcid) => - match api_client.rt.block_on(api_client.api.get_editor(fcid.to_string()))? { - fatcat_openapi::GetEditorResponse::Found(model) => Ok(Box::new(model)), - fatcat_openapi::GetEditorResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetEditorResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, - Changelog(index) => - match api_client.rt.block_on(api_client.api.get_changelog_entry(*index))? { - fatcat_openapi::GetChangelogEntryResponse::FoundChangelogEntry(model) => Ok(Box::new(model)), - fatcat_openapi::GetChangelogEntryResponse::BadRequest(err) => Err(anyhow!("Bad Request ({}): {}", err.error, err.message)), - fatcat_openapi::GetChangelogEntryResponse::NotFound(err) => Err(anyhow!("Not Found: {}", err.message)), - resp => Err(anyhow!("{:?}", resp)).with_context(|| format!("API GET failed: {:?}", self)), - }, - EditorUsername(_username) => { - unimplemented!("editor lookup by username isn't implemented in fatcat-server API yet, sorry") + WebCapture(fcid) => match api_client.rt.block_on(api_client.api.get_webcapture( + fcid.to_string(), + expand, + hide, + ))? { + fatcat_openapi::GetWebcaptureResponse::FoundEntity(model) => Ok(Box::new(model)), + fatcat_openapi::GetWebcaptureResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::GetWebcaptureResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), + }, + Editgroup(fcid) => match api_client + .rt + .block_on(api_client.api.get_editgroup(fcid.to_string()))? + { + fatcat_openapi::GetEditgroupResponse::Found(model) => Ok(Box::new(model)), + fatcat_openapi::GetEditgroupResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::GetEditgroupResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), }, + Editor(fcid) => match api_client + .rt + .block_on(api_client.api.get_editor(fcid.to_string()))? + { + fatcat_openapi::GetEditorResponse::Found(model) => Ok(Box::new(model)), + fatcat_openapi::GetEditorResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::GetEditorResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), + }, + Changelog(index) => match api_client + .rt + .block_on(api_client.api.get_changelog_entry(*index))? + { + fatcat_openapi::GetChangelogEntryResponse::FoundChangelogEntry(model) => { + Ok(Box::new(model)) + } + fatcat_openapi::GetChangelogEntryResponse::BadRequest(err) => { + Err(anyhow!("Bad Request ({}): {}", err.error, err.message)) + } + fatcat_openapi::GetChangelogEntryResponse::NotFound(err) => { + Err(anyhow!("Not Found: {}", err.message)) + } + resp => Err(anyhow!("{:?}", resp)) + .with_context(|| format!("API GET failed: {:?}", self)), + }, + EditorUsername(_username) => { + unimplemented!( + "editor lookup by username isn't implemented in fatcat-server API yet, sorry" + ) + } }; match ret { Ok(_) => ret, @@ -236,18 +383,42 @@ impl FromStr for Specifier { // then try lookup prefixes lazy_static! { - static ref SPEC_LOOKUP_RE: Regex = Regex::new(r"^(doi|pmcid|pmid|arxiv|issnl|orcid|sha1|sha256|md5|username|changelog):(\S+)$").unwrap(); + static ref SPEC_LOOKUP_RE: Regex = Regex::new( + r"^(doi|pmcid|pmid|arxiv|issnl|orcid|sha1|sha256|md5|username|changelog):(\S+)$" + ) + .unwrap(); } if let Some(caps) = SPEC_LOOKUP_RE.captures(s) { return match (&caps[1], &caps[2]) { - ("doi", key) => Ok(Specifier::ReleaseLookup(ReleaseLookupKey::DOI, key.to_string())), - ("pmcid", key) => Ok(Specifier::ReleaseLookup(ReleaseLookupKey::PMCID, key.to_string())), - ("pmid", key) => Ok(Specifier::ReleaseLookup(ReleaseLookupKey::PMID, key.to_string())), - ("arxiv", key) => Ok(Specifier::ReleaseLookup(ReleaseLookupKey::Arxiv, key.to_string())), - ("issnl", key) => Ok(Specifier::ContainerLookup(ContainerLookupKey::ISSNL, key.to_string())), - ("orcid", key) => Ok(Specifier::CreatorLookup(CreatorLookupKey::Orcid, key.to_string())), + ("doi", key) => Ok(Specifier::ReleaseLookup( + ReleaseLookupKey::DOI, + key.to_string(), + )), + ("pmcid", key) => Ok(Specifier::ReleaseLookup( + ReleaseLookupKey::PMCID, + key.to_string(), + )), + ("pmid", key) => Ok(Specifier::ReleaseLookup( + ReleaseLookupKey::PMID, + key.to_string(), + )), + ("arxiv", key) => Ok(Specifier::ReleaseLookup( + ReleaseLookupKey::Arxiv, + key.to_string(), + )), + ("issnl", key) => Ok(Specifier::ContainerLookup( + ContainerLookupKey::ISSNL, + key.to_string(), + )), + ("orcid", key) => Ok(Specifier::CreatorLookup( + CreatorLookupKey::Orcid, + key.to_string(), + )), ("sha1", key) => Ok(Specifier::FileLookup(FileLookupKey::SHA1, key.to_string())), - ("sha256", key) => Ok(Specifier::FileLookup(FileLookupKey::SHA256, key.to_string())), + ("sha256", key) => Ok(Specifier::FileLookup( + FileLookupKey::SHA256, + key.to_string(), + )), ("md5", key) => Ok(Specifier::FileLookup(FileLookupKey::MD5, key.to_string())), ("username", key) => Ok(Specifier::EditorUsername(key.to_string())), _ => Err(anyhow!("unexpected entity lookup type: {}", &caps[1])), @@ -260,7 +431,10 @@ impl FromStr for Specifier { if let Some(caps) = SPEC_CHANGELOG_RE.captures(s) { return Ok(Specifier::Changelog(caps[1].parse::()?)); } - Err(anyhow!("expecting a specifier: entity identifier or key/value lookup: {}", s)) + Err(anyhow!( + "expecting a specifier: entity identifier or key/value lookup: {}", + s + )) } } @@ -271,12 +445,23 @@ mod tests { #[test] fn test_specifier_from_str() -> () { assert!(Specifier::from_str("release_asdf").is_err()); - assert_eq!(Specifier::from_str("creator_iimvc523xbhqlav6j3sbthuehu").unwrap(), Specifier::Creator("iimvc523xbhqlav6j3sbthuehu".to_string())); - assert_eq!(Specifier::from_str("username:big-bot").unwrap(), Specifier::EditorUsername("big-bot".to_string())); - assert_eq!(Specifier::from_str("doi:10.1234/a!s.df+-d").unwrap(), Specifier::ReleaseLookup(ReleaseLookupKey::DOI, "10.1234/a!s.df+-d".to_string())); + assert_eq!( + Specifier::from_str("creator_iimvc523xbhqlav6j3sbthuehu").unwrap(), + Specifier::Creator("iimvc523xbhqlav6j3sbthuehu".to_string()) + ); + assert_eq!( + Specifier::from_str("username:big-bot").unwrap(), + Specifier::EditorUsername("big-bot".to_string()) + ); + assert_eq!( + Specifier::from_str("doi:10.1234/a!s.df+-d").unwrap(), + Specifier::ReleaseLookup(ReleaseLookupKey::DOI, "10.1234/a!s.df+-d".to_string()) + ); assert!(Specifier::from_str("doi:").is_err()); - assert_eq!(Specifier::from_str("changelog_1234").unwrap(), Specifier::Changelog(1234)); + assert_eq!( + Specifier::from_str("changelog_1234").unwrap(), + Specifier::Changelog(1234) + ); assert!(Specifier::from_str("changelog_12E4").is_err()); } - } -- cgit v1.2.3