diff --git a/CHANGELOG.md b/CHANGELOG.md index ab6454af..0b3ed878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + - [#438](https://github.com/thoth-pub/thoth/issues/438) - Allow specifying query parameters based on the requested specification + - Upgrade rust to `1.64.0` in development `Dockerfile` ## [[0.8.9]](https://github.com/thoth-pub/thoth/releases/tag/v0.8.9) - 2022-09-21 ### Added diff --git a/Cargo.lock b/Cargo.lock index c3ef4f11..c16cd081 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3834,7 +3834,7 @@ dependencies = [ [[package]] name = "thoth" -version = "0.8.9" +version = "0.8.10" dependencies = [ "cargo-husky", "clap", @@ -3849,7 +3849,7 @@ dependencies = [ [[package]] name = "thoth-api" -version = "0.8.9" +version = "0.8.10" dependencies = [ "actix-web", "argon2rs", @@ -3878,7 +3878,7 @@ dependencies = [ [[package]] name = "thoth-api-server" -version = "0.8.9" +version = "0.8.10" dependencies = [ "actix-cors", "actix-identity", @@ -3893,7 +3893,7 @@ dependencies = [ [[package]] name = "thoth-app" -version = "0.8.9" +version = "0.8.10" dependencies = [ "anyhow", "chrono", @@ -3921,7 +3921,7 @@ dependencies = [ [[package]] name = "thoth-app-server" -version = "0.8.9" +version = "0.8.10" dependencies = [ "actix-cors", "actix-web", @@ -3930,7 +3930,7 @@ dependencies = [ [[package]] name = "thoth-client" -version = "0.8.9" +version = "0.8.10" dependencies = [ "chrono", "graphql_client", @@ -3944,7 +3944,7 @@ dependencies = [ [[package]] name = "thoth-errors" -version = "0.8.9" +version = "0.8.10" dependencies = [ "actix-web", "csv", @@ -3960,7 +3960,7 @@ dependencies = [ [[package]] name = "thoth-export-server" -version = "0.8.9" +version = "0.8.10" dependencies = [ "actix-cors", "actix-web", diff --git a/Cargo.toml b/Cargo.toml index 3c6dae3b..b374ef91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth" -version = "0.8.9" +version = "0.8.10" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -16,11 +16,11 @@ maintenance = { status = "actively-developed" } members = ["thoth-api", "thoth-api-server", "thoth-app", "thoth-app-server", "thoth-client", "thoth-errors", "thoth-export-server"] [dependencies] -thoth-api = { version = "0.8.9", path = "thoth-api", features = ["backend"] } -thoth-api-server = { version = "0.8.9", path = "thoth-api-server" } -thoth-app-server = { version = "0.8.9", path = "thoth-app-server" } -thoth-errors = { version = "0.8.9", path = "thoth-errors" } -thoth-export-server = { version = "0.8.9", path = "thoth-export-server" } +thoth-api = { version = "0.8.10", path = "thoth-api", features = ["backend"] } +thoth-api-server = { version = "0.8.10", path = "thoth-api-server" } +thoth-app-server = { version = "0.8.10", path = "thoth-app-server" } +thoth-errors = { version = "0.8.10", path = "thoth-errors" } +thoth-export-server = { version = "0.8.10", path = "thoth-export-server" } clap = "2.33.3" dialoguer = "0.7.1" dotenv = "0.15.0" diff --git a/Dockerfile.dev b/Dockerfile.dev index cc50c5fb..24f2b688 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -ARG RUST_VERSION=1.63.0 +ARG RUST_VERSION=1.64.0 FROM rust:${RUST_VERSION} diff --git a/thoth-api-server/Cargo.toml b/thoth-api-server/Cargo.toml index 7565aadc..ad0a8872 100644 --- a/thoth-api-server/Cargo.toml +++ b/thoth-api-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-api-server" -version = "0.8.9" +version = "0.8.10" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -9,8 +9,8 @@ repository = "https://github.com/thoth-pub/thoth" readme = "README.md" [dependencies] -thoth-api = { version = "0.8.9", path = "../thoth-api", features = ["backend"] } -thoth-errors = { version = "0.8.9", path = "../thoth-errors" } +thoth-api = { version = "0.8.10", path = "../thoth-api", features = ["backend"] } +thoth-errors = { version = "0.8.10", path = "../thoth-errors" } actix-web = "4.0.1" actix-cors = "0.6.0" actix-identity = "0.4.0" diff --git a/thoth-api/Cargo.toml b/thoth-api/Cargo.toml index 5aa596ac..edc4f95d 100644 --- a/thoth-api/Cargo.toml +++ b/thoth-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-api" -version = "0.8.9" +version = "0.8.10" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -16,7 +16,7 @@ maintenance = { status = "actively-developed" } backend = ["diesel", "diesel-derive-enum", "diesel_migrations", "futures", "actix-web"] [dependencies] -thoth-errors = { version = "0.8.9", path = "../thoth-errors" } +thoth-errors = { version = "0.8.10", path = "../thoth-errors" } actix-web = { version = "4.0.1", optional = true } argon2rs = "0.2.5" isbn2 = "0.4.0" diff --git a/thoth-app-server/Cargo.toml b/thoth-app-server/Cargo.toml index 36e6d697..e656d20b 100644 --- a/thoth-app-server/Cargo.toml +++ b/thoth-app-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-app-server" -version = "0.8.9" +version = "0.8.10" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" diff --git a/thoth-app/Cargo.toml b/thoth-app/Cargo.toml index ec556ef8..b399be8f 100644 --- a/thoth-app/Cargo.toml +++ b/thoth-app/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-app" -version = "0.8.9" +version = "0.8.10" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -38,5 +38,5 @@ serde = { version = "1.0.115", features = ["derive"] } serde_json = "1.0" url = "2.1.1" uuid = { version = "0.7", features = ["serde", "v4"] } -thoth-api = { version = "0.8.9", path = "../thoth-api" } -thoth-errors = { version = "0.8.9", path = "../thoth-errors" } +thoth-api = { version = "0.8.10", path = "../thoth-api" } +thoth-errors = { version = "0.8.10", path = "../thoth-errors" } diff --git a/thoth-app/manifest.json b/thoth-app/manifest.json index 2833587c..77a886ee 100644 --- a/thoth-app/manifest.json +++ b/thoth-app/manifest.json @@ -9,7 +9,7 @@ "start_url": "/?homescreen=1", "background_color": "#ffffff", "theme_color": "#ffdd57", - "version": "0.8.9", + "version": "0.8.10", "icons": [ { "src": "\/android-icon-36x36.png", diff --git a/thoth-app/src/component/mod.rs b/thoth-app/src/component/mod.rs index 7e24dbb8..09319822 100644 --- a/thoth-app/src/component/mod.rs +++ b/thoth-app/src/component/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::let_unit_value)] + #[macro_export] macro_rules! pagination_helpers { ($component:ident, $pagination_text:ident, $search_text:ident) => { diff --git a/thoth-client/Cargo.toml b/thoth-client/Cargo.toml index 938e267f..c5a4fae8 100644 --- a/thoth-client/Cargo.toml +++ b/thoth-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-client" -version = "0.8.9" +version = "0.8.10" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -9,8 +9,8 @@ repository = "https://github.com/thoth-pub/thoth" readme = "README.md" [dependencies] -thoth-api = {version = "0.8.9", path = "../thoth-api" } -thoth-errors = {version = "0.8.9", path = "../thoth-errors" } +thoth-api = {version = "0.8.10", path = "../thoth-api" } +thoth-errors = {version = "0.8.10", path = "../thoth-errors" } graphql_client = "0.11.0" chrono = { version = "0.4", features = ["serde"] } reqwest = { version = "0.11", features = ["json"] } diff --git a/thoth-client/assets/queries.graphql b/thoth-client/assets/queries.graphql index 1034f2a0..377b4177 100644 --- a/thoth-client/assets/queries.graphql +++ b/thoth-client/assets/queries.graphql @@ -36,7 +36,7 @@ fragment Work on Work { publisherUrl } } - issues { + issues(limit: $issuesLimit) { issueOrdinal series { seriesType @@ -67,12 +67,12 @@ fragment Work on Work { } } } - languages { + languages(limit: $languagesLimit) { languageCode languageRelation mainLanguage } - publications { + publications(limit: $publicationsLimit) { publicationId publicationType isbn @@ -98,12 +98,12 @@ fragment Work on Work { canonical } } - subjects { + subjects(limit: $subjectsLimit) { subjectCode subjectType subjectOrdinal } - fundings { + fundings(limit: $fundingsLimit) { program projectName projectShortname @@ -116,7 +116,7 @@ fragment Work on Work { countryCode } } - relations { + relations(limit: $relationsLimit) { relationType relationOrdinal relatedWork { @@ -157,13 +157,29 @@ fragment Work on Work { } } -query WorkQuery($workId: Uuid!) { +query WorkQuery( + $workId: Uuid!, + $issuesLimit: Int!, + $languagesLimit: Int!, + $publicationsLimit: Int!, + $subjectsLimit: Int!, + $fundingsLimit: Int!, + $relationsLimit: Int! +) { work(workId: $workId) { ...Work } } -query WorksQuery($publishers: [Uuid!]) { +query WorksQuery( + $publishers: [Uuid!], + $issuesLimit: Int!, + $languagesLimit: Int!, + $publicationsLimit: Int!, + $subjectsLimit: Int!, + $fundingsLimit: Int!, + $relationsLimit: Int! +) { works(limit: 99999, publishers: $publishers) { ...Work } -} +} \ No newline at end of file diff --git a/thoth-client/src/lib.rs b/thoth-client/src/lib.rs index 1c8434fe..a7637668 100644 --- a/thoth-client/src/lib.rs +++ b/thoth-client/src/lib.rs @@ -1,3 +1,4 @@ +mod parameters; // GraphQLQuery derive macro breaks this linting rule - ignore while awaiting fix #[allow(clippy::derive_partial_eq_without_eq)] mod queries; @@ -9,6 +10,8 @@ use std::future::Future; use thoth_errors::{ThothError, ThothResult}; use uuid::Uuid; +pub use crate::parameters::QueryParameters; +use crate::parameters::{WorkQueryVariables, WorksQueryVariables}; pub use crate::queries::work_query::*; use crate::queries::{work_query, works_query, WorkQuery, WorksQuery}; @@ -50,18 +53,19 @@ impl ThothClient { /// /// ```no_run /// # use thoth_errors::ThothResult; - /// # use thoth_client::{ThothClient, Work}; + /// # use thoth_client::{QueryParameters, ThothClient, Work}; /// # use uuid::Uuid; /// /// # async fn run() -> ThothResult { /// let thoth_client = ThothClient::new("https://api.thoth.pub/graphql".to_string()); /// let work_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; - /// let work = thoth_client.get_work(work_id).await?; + /// let work = thoth_client.get_work(work_id, QueryParameters::new()).await?; /// # Ok(work) /// # } /// ``` - pub async fn get_work(&self, work_id: Uuid) -> ThothResult { - let request_body = WorkQuery::build_query(work_query::Variables { work_id }); + pub async fn get_work(&self, work_id: Uuid, parameters: QueryParameters) -> ThothResult { + let variables: work_query::Variables = WorkQueryVariables::new(work_id, parameters).into(); + let request_body = WorkQuery::build_query(variables); let res = self.post_request(&request_body).await.await?; let response_body: Response = res.json().await?; match response_body.data { @@ -80,18 +84,24 @@ impl ThothClient { /// /// ```no_run /// # use thoth_errors::ThothResult; - /// # use thoth_client::{ThothClient, Work}; + /// # use thoth_client::{QueryParameters, ThothClient, Work}; /// # use uuid::Uuid; /// /// # async fn run() -> ThothResult> { /// let thoth_client = ThothClient::new("https://api.thoth.pub/graphql".to_string()); /// let publisher_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; - /// let works = thoth_client.get_works(Some(vec![publisher_id])).await?; + /// let works = thoth_client.get_works(Some(vec![publisher_id]), QueryParameters::new()).await?; /// # Ok(works) /// # } /// ``` - pub async fn get_works(&self, publishers: Option>) -> ThothResult> { - let request_body = WorksQuery::build_query(works_query::Variables { publishers }); + pub async fn get_works( + &self, + publishers: Option>, + parameters: QueryParameters, + ) -> ThothResult> { + let variables: works_query::Variables = + WorksQueryVariables::new(publishers, parameters).into(); + let request_body = WorksQuery::build_query(variables); let res = self.post_request(&request_body).await.await?; let response_body: Response = res.json().await?; match response_body.data { diff --git a/thoth-client/src/parameters.rs b/thoth-client/src/parameters.rs new file mode 100644 index 00000000..d9c29011 --- /dev/null +++ b/thoth-client/src/parameters.rs @@ -0,0 +1,377 @@ +use crate::queries::{work_query, works_query}; +use uuid::Uuid; + +/// A set of booleans to toggle directives in the GraphQL queries +#[cfg_attr(test, derive(Debug, Eq, PartialEq))] +#[derive(Default)] +pub struct QueryParameters { + with_issues: bool, + with_languages: bool, + with_publications: bool, + with_subjects: bool, + with_fundings: bool, + with_relations: bool, +} + +/// An intermediate struct to parse QueryParameters into work_query::Variables +pub(crate) struct WorkQueryVariables { + pub work_id: Uuid, + pub parameters: QueryParameters, +} + +/// An intermediate struct to parse QueryParameters into works_query::Variables +pub(crate) struct WorksQueryVariables { + pub publishers: Option>, + pub parameters: QueryParameters, +} + +impl WorkQueryVariables { + pub(crate) fn new(work_id: Uuid, parameters: QueryParameters) -> Self { + WorkQueryVariables { + work_id, + parameters, + } + } +} + +impl WorksQueryVariables { + pub(crate) fn new(publishers: Option>, parameters: QueryParameters) -> Self { + WorksQueryVariables { + publishers, + parameters, + } + } +} + +/// Implement builder pattern for `QueryParameters` +/// +/// # Example +/// +/// ``` +/// # use thoth_client::{QueryParameters}; +/// +/// # async fn run() -> QueryParameters { +/// let parameters = QueryParameters::new().with_issues().with_languages(); +/// # parameters +/// # } +/// ``` +impl QueryParameters { + pub fn new() -> Self { + Self::default() + } + + pub fn with_all(self) -> Self { + self.with_issues() + .with_languages() + .with_publications() + .with_subjects() + .with_fundings() + .with_relations() + } + + pub fn with_issues(mut self) -> Self { + self.with_issues = true; + self + } + + pub fn with_languages(mut self) -> Self { + self.with_languages = true; + self + } + + pub fn with_publications(mut self) -> Self { + self.with_publications = true; + self + } + + pub fn with_subjects(mut self) -> Self { + self.with_subjects = true; + self + } + + pub fn with_fundings(mut self) -> Self { + self.with_fundings = true; + self + } + + pub fn with_relations(mut self) -> Self { + self.with_relations = true; + self + } + + pub fn without_issues(mut self) -> Self { + self.with_issues = false; + self + } + + pub fn without_languages(mut self) -> Self { + self.with_languages = false; + self + } + + pub fn without_publications(mut self) -> Self { + self.with_publications = false; + self + } + + pub fn without_subjects(mut self) -> Self { + self.with_subjects = false; + self + } + + pub fn without_fundings(mut self) -> Self { + self.with_fundings = false; + self + } + + pub fn without_relations(mut self) -> Self { + self.with_relations = false; + self + } +} + +const FILTER_INCLUDE_ALL: i64 = 99999; +const FILTER_INCLUDE_NONE: i64 = 0; + +impl From for work_query::Variables { + fn from(v: WorkQueryVariables) -> Self { + work_query::Variables { + work_id: v.work_id, + issues_limit: if v.parameters.with_issues { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + languages_limit: if v.parameters.with_languages { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + publications_limit: if v.parameters.with_publications { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + subjects_limit: if v.parameters.with_subjects { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + fundings_limit: if v.parameters.with_fundings { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + relations_limit: if v.parameters.with_relations { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + } + } +} + +impl From for works_query::Variables { + fn from(v: WorksQueryVariables) -> Self { + works_query::Variables { + publishers: v.publishers, + issues_limit: if v.parameters.with_issues { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + languages_limit: if v.parameters.with_languages { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + publications_limit: if v.parameters.with_publications { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + subjects_limit: if v.parameters.with_subjects { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + fundings_limit: if v.parameters.with_fundings { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + relations_limit: if v.parameters.with_relations { + FILTER_INCLUDE_ALL + } else { + FILTER_INCLUDE_NONE + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::queries::{work_query, works_query}; + + #[test] + fn test_default_query_parameters() { + let to_test = QueryParameters { + with_issues: false, + with_languages: false, + with_publications: false, + with_subjects: false, + with_fundings: false, + with_relations: false, + }; + assert_eq!(to_test, QueryParameters::default()); + assert_eq!(to_test, QueryParameters::new()) + } + + #[test] + fn test_query_parameters_builder() { + assert_eq!( + QueryParameters::new().with_all(), + QueryParameters { + with_issues: true, + with_languages: true, + with_publications: true, + with_subjects: true, + with_fundings: true, + with_relations: true + }, + ); + assert_eq!( + QueryParameters::new() + .with_all() + .without_issues() + .without_languages() + .without_publications() + .without_subjects() + .without_fundings() + .without_relations(), + QueryParameters { + with_issues: false, + with_languages: false, + with_publications: false, + with_subjects: false, + with_fundings: false, + with_relations: false + }, + ); + assert_eq!( + QueryParameters::new() + .with_issues() + .with_languages() + .with_publications() + .with_subjects() + .with_fundings() + .with_relations(), + QueryParameters { + with_issues: true, + with_languages: true, + with_publications: true, + with_subjects: true, + with_fundings: true, + with_relations: true + }, + ); + } + + #[test] + fn test_convert_parameters_to_work_query_variables() { + let work_id: Uuid = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001").unwrap(); + let mut parameters = QueryParameters::new().with_all(); + let mut variables: work_query::Variables = + WorkQueryVariables::new(work_id, parameters).into(); + assert_eq!( + variables, + work_query::Variables { + work_id, + issues_limit: FILTER_INCLUDE_ALL, + languages_limit: FILTER_INCLUDE_ALL, + publications_limit: FILTER_INCLUDE_ALL, + subjects_limit: FILTER_INCLUDE_ALL, + fundings_limit: FILTER_INCLUDE_ALL, + relations_limit: FILTER_INCLUDE_ALL, + } + ); + parameters = QueryParameters::new(); + variables = WorkQueryVariables::new(work_id, parameters).into(); + assert_eq!( + variables, + work_query::Variables { + work_id, + issues_limit: FILTER_INCLUDE_NONE, + languages_limit: FILTER_INCLUDE_NONE, + publications_limit: FILTER_INCLUDE_NONE, + subjects_limit: FILTER_INCLUDE_NONE, + fundings_limit: FILTER_INCLUDE_NONE, + relations_limit: FILTER_INCLUDE_NONE, + } + ); + parameters = QueryParameters::new().with_all().without_relations(); + variables = WorkQueryVariables::new(work_id, parameters).into(); + assert_eq!( + variables, + work_query::Variables { + work_id, + issues_limit: FILTER_INCLUDE_ALL, + languages_limit: FILTER_INCLUDE_ALL, + publications_limit: FILTER_INCLUDE_ALL, + subjects_limit: FILTER_INCLUDE_ALL, + fundings_limit: FILTER_INCLUDE_ALL, + relations_limit: FILTER_INCLUDE_NONE, + } + ); + } + + #[test] + fn test_convert_parameters_to_works_query_variables() { + let publisher_id: Uuid = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001").unwrap(); + let publishers = Some(vec![publisher_id]); + let mut parameters = QueryParameters::new().with_all(); + let mut variables: works_query::Variables = + WorksQueryVariables::new(publishers.clone(), parameters).into(); + assert_eq!( + variables, + works_query::Variables { + publishers: publishers.clone(), + issues_limit: FILTER_INCLUDE_ALL, + languages_limit: FILTER_INCLUDE_ALL, + publications_limit: FILTER_INCLUDE_ALL, + subjects_limit: FILTER_INCLUDE_ALL, + fundings_limit: FILTER_INCLUDE_ALL, + relations_limit: FILTER_INCLUDE_ALL, + } + ); + parameters = QueryParameters::new(); + variables = WorksQueryVariables::new(publishers.clone(), parameters).into(); + assert_eq!( + variables, + works_query::Variables { + publishers: publishers.clone(), + issues_limit: FILTER_INCLUDE_NONE, + languages_limit: FILTER_INCLUDE_NONE, + publications_limit: FILTER_INCLUDE_NONE, + subjects_limit: FILTER_INCLUDE_NONE, + fundings_limit: FILTER_INCLUDE_NONE, + relations_limit: FILTER_INCLUDE_NONE, + } + ); + parameters = QueryParameters::new().with_all().without_relations(); + variables = WorksQueryVariables::new(publishers.clone(), parameters).into(); + assert_eq!( + variables, + works_query::Variables { + publishers, + issues_limit: FILTER_INCLUDE_ALL, + languages_limit: FILTER_INCLUDE_ALL, + publications_limit: FILTER_INCLUDE_ALL, + subjects_limit: FILTER_INCLUDE_ALL, + fundings_limit: FILTER_INCLUDE_ALL, + relations_limit: FILTER_INCLUDE_NONE, + } + ); + } +} diff --git a/thoth-client/src/queries.rs b/thoth-client/src/queries.rs index 1cd5cd39..65b1942e 100644 --- a/thoth-client/src/queries.rs +++ b/thoth-client/src/queries.rs @@ -12,7 +12,8 @@ use uuid::Uuid; #[graphql( schema_path = "assets/schema.json", query_path = "assets/queries.graphql", - response_derives = "Debug,Clone,Deserialize,Serialize,PartialEq" + response_derives = "Debug,Clone,Deserialize,Serialize,PartialEq", + variables_derives = "Debug,PartialEq" )] pub struct WorkQuery; @@ -26,7 +27,8 @@ impl fmt::Display for work_query::LanguageCode { #[graphql( schema_path = "assets/schema.json", query_path = "assets/queries.graphql", - response_derives = "Debug,Clone,Deserialize,Serialize,PartialEq" + response_derives = "Debug,Clone,Deserialize,Serialize,PartialEq", + variables_derives = "Debug,PartialEq" )] pub struct WorksQuery; diff --git a/thoth-errors/Cargo.toml b/thoth-errors/Cargo.toml index fc6a2db1..b41a17ab 100644 --- a/thoth-errors/Cargo.toml +++ b/thoth-errors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-errors" -version = "0.8.9" +version = "0.8.10" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" diff --git a/thoth-export-server/Cargo.toml b/thoth-export-server/Cargo.toml index 1a46022a..1318a476 100644 --- a/thoth-export-server/Cargo.toml +++ b/thoth-export-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-export-server" -version = "0.8.9" +version = "0.8.10" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -9,9 +9,9 @@ repository = "https://github.com/thoth-pub/thoth" readme = "README.md" [dependencies] -thoth-api = { version = "0.8.9", path = "../thoth-api" } -thoth-errors = { version = "0.8.9", path = "../thoth-errors" } -thoth-client = { version = "0.8.9", path = "../thoth-client" } +thoth-api = { version = "0.8.10", path = "../thoth-api" } +thoth-errors = { version = "0.8.10", path = "../thoth-errors" } +thoth-client = { version = "0.8.10", path = "../thoth-client" } actix-web = "4.0.1" actix-cors = "0.6.0" chrono = { version = "0.4", features = ["serde"] } diff --git a/thoth-export-server/src/bibtex/bibtex_thoth.rs b/thoth-export-server/src/bibtex/bibtex_thoth.rs index 72dc8568..57e83995 100644 --- a/thoth-export-server/src/bibtex/bibtex_thoth.rs +++ b/thoth-export-server/src/bibtex/bibtex_thoth.rs @@ -6,6 +6,7 @@ use thoth_errors::{ThothError, ThothResult}; use super::{BibtexEntry, BibtexSpecification}; +#[derive(Copy, Clone)] pub(crate) struct BibtexThoth; #[derive(Debug)] diff --git a/thoth-export-server/src/csv/csv_thoth.rs b/thoth-export-server/src/csv/csv_thoth.rs index 23f77e24..a1689faa 100644 --- a/thoth-export-server/src/csv/csv_thoth.rs +++ b/thoth-export-server/src/csv/csv_thoth.rs @@ -10,6 +10,7 @@ use thoth_errors::ThothResult; use super::{CsvCell, CsvRow, CsvSpecification}; +#[derive(Copy, Clone)] pub(crate) struct CsvThoth; #[derive(Debug, Serialize)] diff --git a/thoth-export-server/src/csv/kbart_oclc.rs b/thoth-export-server/src/csv/kbart_oclc.rs index 8e3727d8..e7397cc3 100644 --- a/thoth-export-server/src/csv/kbart_oclc.rs +++ b/thoth-export-server/src/csv/kbart_oclc.rs @@ -7,6 +7,7 @@ use thoth_errors::{ThothError, ThothResult}; use super::{CsvRow, CsvSpecification}; +#[derive(Copy, Clone)] pub(crate) struct KbartOclc; #[derive(Debug, Serialize)] diff --git a/thoth-export-server/src/lib.rs b/thoth-export-server/src/lib.rs index 827d85d9..8d9d55c9 100644 --- a/thoth-export-server/src/lib.rs +++ b/thoth-export-server/src/lib.rs @@ -14,6 +14,7 @@ mod platform; mod rapidoc; mod record; mod specification; +mod specification_query; mod xml; use crate::rapidoc::rapidoc_source; diff --git a/thoth-export-server/src/record.rs b/thoth-export-server/src/record.rs index 80adbc45..1f0bee9c 100644 --- a/thoth-export-server/src/record.rs +++ b/thoth-export-server/src/record.rs @@ -23,6 +23,7 @@ pub const DELIMITER_TAB: u8 = b'\t'; pub const XML_DECLARATION: &str = "\n"; pub const DOCTYPE_ONIX21_REF: &str = "\n"; +#[derive(Copy, Clone)] pub(crate) enum MetadataSpecification { Onix3ProjectMuse(Onix3ProjectMuse), Onix3Oapen(Onix3Oapen), diff --git a/thoth-export-server/src/specification/handler.rs b/thoth-export-server/src/specification/handler.rs index 1eb1e562..e8e534a8 100644 --- a/thoth-export-server/src/specification/handler.rs +++ b/thoth-export-server/src/specification/handler.rs @@ -4,12 +4,12 @@ use paperclip::actix::{ web::{self, Json}, }; use thoth_client::{ThothClient, Work}; -use thoth_errors::ThothError; use uuid::Uuid; use super::model::Specification; use crate::data::{find_specification, ALL_SPECIFICATIONS}; -use crate::record::MetadataRecord; +use crate::record::{MetadataRecord, MetadataSpecification}; +use crate::specification_query::SpecificationQuery; #[api_v2_operation( summary = "List supported specifications", @@ -44,14 +44,12 @@ pub(crate) async fn by_work( thoth_client: web::Data, ) -> Result>, Error> { let (specification_id, work_id) = path.into_inner(); - thoth_client - .get_work(work_id) + let specification: MetadataSpecification = specification_id.parse()?; + + SpecificationQuery::new(thoth_client.into_inner(), specification) + .by_work(work_id) .await - .and_then(|data| { - specification_id.parse().map(|specification| { - MetadataRecord::new(work_id.to_string(), specification, vec![data]) - }) - }) + .map(|data| MetadataRecord::new(work_id.to_string(), specification, vec![data])) .map_err(|e| e.into()) } @@ -66,21 +64,11 @@ pub(crate) async fn by_publisher( thoth_client: web::Data, ) -> Result>, Error> { let (specification_id, publisher_id) = path.into_inner(); - if specification_id.eq("doideposit::crossref") { - // Full publisher record is not supported for this specification - return Err(ThothError::IncompleteMetadataRecord( - "doideposit::crossref".to_string(), - "Output can only be generated for one work at a time".to_string(), - ) - .into()); - } - thoth_client - .get_works(Some(vec![publisher_id])) + let specification: MetadataSpecification = specification_id.parse()?; + + SpecificationQuery::new(thoth_client.into_inner(), specification) + .by_publisher(publisher_id) .await - .and_then(|data| { - specification_id.parse().map(|specification| { - MetadataRecord::new(publisher_id.to_string(), specification, data) - }) - }) + .map(|data| MetadataRecord::new(publisher_id.to_string(), specification, data)) .map_err(|e| e.into()) } diff --git a/thoth-export-server/src/specification_query.rs b/thoth-export-server/src/specification_query.rs new file mode 100644 index 00000000..a4d5a76f --- /dev/null +++ b/thoth-export-server/src/specification_query.rs @@ -0,0 +1,122 @@ +use std::convert::{TryFrom, TryInto}; +use std::sync::Arc; +use thoth_client::{QueryParameters, ThothClient, Work}; +use thoth_errors::{ThothError, ThothResult}; +use uuid::Uuid; + +use crate::record::MetadataSpecification; + +enum SpecificationRequest { + ByWork, + ByPublisher, +} + +pub(crate) struct SpecificationQuery { + thoth_client: Arc, + specification: MetadataSpecification, +} + +struct QueryConfiguration { + request: SpecificationRequest, + specification: MetadataSpecification, +} + +impl SpecificationQuery { + pub(crate) fn new( + thoth_client: Arc, + specification: MetadataSpecification, + ) -> Self { + Self { + thoth_client, + specification, + } + } + + pub(crate) async fn by_work(self, work_id: Uuid) -> ThothResult { + let parameters: QueryParameters = + QueryConfiguration::by_work(self.specification).try_into()?; + self.thoth_client.get_work(work_id, parameters).await + } + + pub(crate) async fn by_publisher(self, publisher_id: Uuid) -> ThothResult> { + let parameters: QueryParameters = + QueryConfiguration::by_publisher(self.specification).try_into()?; + self.thoth_client + .get_works(Some(vec![publisher_id]), parameters) + .await + } +} + +impl QueryConfiguration { + fn by_work(specification: MetadataSpecification) -> Self { + Self { + request: SpecificationRequest::ByWork, + specification, + } + } + + fn by_publisher(specification: MetadataSpecification) -> Self { + Self { + request: SpecificationRequest::ByPublisher, + specification, + } + } +} + +impl TryFrom for QueryParameters { + type Error = ThothError; + + fn try_from(q: QueryConfiguration) -> ThothResult { + match q.specification { + MetadataSpecification::Onix3ProjectMuse(_) => { + Ok(QueryParameters::new().with_all().without_relations()) + } + MetadataSpecification::Onix3Oapen(_) => { + Ok(QueryParameters::new().with_all().without_relations()) + } + MetadataSpecification::Onix3Jstor(_) => { + Ok(QueryParameters::new().with_all().without_relations()) + } + MetadataSpecification::Onix3GoogleBooks(_) => { + Ok(QueryParameters::new().with_all().without_relations()) + } + MetadataSpecification::Onix3Overdrive(_) => { + Ok(QueryParameters::new().with_all().without_relations()) + } + MetadataSpecification::Onix21EbscoHost(_) => { + Ok(QueryParameters::new().with_all().without_relations()) + } + MetadataSpecification::Onix21ProquestEbrary(_) => { + Ok(QueryParameters::new().with_all().without_relations()) + } + MetadataSpecification::CsvThoth(_) => match q.request { + SpecificationRequest::ByWork => Ok(QueryParameters::new().with_all()), + SpecificationRequest::ByPublisher => { + Ok(QueryParameters::new().with_all().without_relations()) + } + }, + MetadataSpecification::KbartOclc(_) => { + Ok(QueryParameters::new().with_issues().with_publications()) + } + MetadataSpecification::BibtexThoth(_) => match q.request { + SpecificationRequest::ByWork => Ok(QueryParameters::new() + .with_issues() + .with_publications() + .with_relations()), + SpecificationRequest::ByPublisher => { + Ok(QueryParameters::new().with_issues().with_publications()) + } + }, + MetadataSpecification::DoiDepositCrossref(_) => match q.request { + SpecificationRequest::ByWork => Ok(QueryParameters::new() + .with_issues() + .with_publications() + .with_relations()), + SpecificationRequest::ByPublisher => Err(ThothError::IncompleteMetadataRecord( + "doideposit::crossref".to_string(), + "Output can only be generated for one work at a time".to_string(), + )), + }, + } + } +} diff --git a/thoth-export-server/src/xml/doideposit_crossref.rs b/thoth-export-server/src/xml/doideposit_crossref.rs index 77b84c77..4066dbca 100644 --- a/thoth-export-server/src/xml/doideposit_crossref.rs +++ b/thoth-export-server/src/xml/doideposit_crossref.rs @@ -12,6 +12,7 @@ use super::{write_element_block, XmlSpecification}; use crate::xml::{write_full_element_block, XmlElementBlock}; use thoth_errors::{ThothError, ThothResult}; +#[derive(Copy, Clone)] pub struct DoiDepositCrossref {} // Output format based on schema documentation at https://data.crossref.org/reports/help/schema_doc/5.3.1/index.html diff --git a/thoth-export-server/src/xml/onix21_ebsco_host.rs b/thoth-export-server/src/xml/onix21_ebsco_host.rs index 008a7898..9e55a620 100644 --- a/thoth-export-server/src/xml/onix21_ebsco_host.rs +++ b/thoth-export-server/src/xml/onix21_ebsco_host.rs @@ -11,6 +11,7 @@ use super::{write_element_block, XmlElement, XmlSpecification}; use crate::xml::{write_full_element_block, XmlElementBlock}; use thoth_errors::{ThothError, ThothResult}; +#[derive(Copy, Clone)] pub struct Onix21EbscoHost {} impl XmlSpecification for Onix21EbscoHost { diff --git a/thoth-export-server/src/xml/onix21_proquest_ebrary.rs b/thoth-export-server/src/xml/onix21_proquest_ebrary.rs index 94d7cfb2..5edcab13 100644 --- a/thoth-export-server/src/xml/onix21_proquest_ebrary.rs +++ b/thoth-export-server/src/xml/onix21_proquest_ebrary.rs @@ -11,6 +11,7 @@ use super::{write_element_block, XmlElement, XmlSpecification}; use crate::xml::{write_full_element_block, XmlElementBlock}; use thoth_errors::{ThothError, ThothResult}; +#[derive(Copy, Clone)] pub struct Onix21ProquestEbrary {} // This specification is exactly the same as EBSCO Host's except for the price point: diff --git a/thoth-export-server/src/xml/onix3_google_books.rs b/thoth-export-server/src/xml/onix3_google_books.rs index bb6b0641..afef115f 100644 --- a/thoth-export-server/src/xml/onix3_google_books.rs +++ b/thoth-export-server/src/xml/onix3_google_books.rs @@ -11,6 +11,7 @@ use super::{write_element_block, XmlElement, XmlSpecification}; use crate::xml::{write_full_element_block, XmlElementBlock}; use thoth_errors::{ThothError, ThothResult}; +#[derive(Copy, Clone)] pub struct Onix3GoogleBooks {} // Output format based on documentation at https://support.google.com/books/partner/answer/6374180. diff --git a/thoth-export-server/src/xml/onix3_jstor.rs b/thoth-export-server/src/xml/onix3_jstor.rs index 9e9c0a66..c0a6afbf 100644 --- a/thoth-export-server/src/xml/onix3_jstor.rs +++ b/thoth-export-server/src/xml/onix3_jstor.rs @@ -11,6 +11,7 @@ use super::{write_element_block, XmlElement, XmlSpecification}; use crate::xml::{write_full_element_block, XmlElementBlock}; use thoth_errors::{ThothError, ThothResult}; +#[derive(Copy, Clone)] pub struct Onix3Jstor {} impl XmlSpecification for Onix3Jstor { diff --git a/thoth-export-server/src/xml/onix3_oapen.rs b/thoth-export-server/src/xml/onix3_oapen.rs index 0b689a15..7d33a3af 100644 --- a/thoth-export-server/src/xml/onix3_oapen.rs +++ b/thoth-export-server/src/xml/onix3_oapen.rs @@ -11,6 +11,7 @@ use super::{write_element_block, XmlElement, XmlSpecification}; use crate::xml::{write_full_element_block, XmlElementBlock}; use thoth_errors::{ThothError, ThothResult}; +#[derive(Copy, Clone)] pub struct Onix3Oapen {} impl XmlSpecification for Onix3Oapen { diff --git a/thoth-export-server/src/xml/onix3_overdrive.rs b/thoth-export-server/src/xml/onix3_overdrive.rs index ab71d85c..7fe6abe1 100644 --- a/thoth-export-server/src/xml/onix3_overdrive.rs +++ b/thoth-export-server/src/xml/onix3_overdrive.rs @@ -12,6 +12,7 @@ use super::{write_element_block, XmlElement, XmlSpecification}; use crate::xml::{write_full_element_block, XmlElementBlock}; use thoth_errors::{ThothError, ThothResult}; +#[derive(Copy, Clone)] pub struct Onix3Overdrive {} impl XmlSpecification for Onix3Overdrive { diff --git a/thoth-export-server/src/xml/onix3_project_muse.rs b/thoth-export-server/src/xml/onix3_project_muse.rs index 939c3983..2de45e1c 100644 --- a/thoth-export-server/src/xml/onix3_project_muse.rs +++ b/thoth-export-server/src/xml/onix3_project_muse.rs @@ -11,6 +11,7 @@ use super::{write_element_block, XmlElement, XmlSpecification}; use crate::xml::{write_full_element_block, XmlElementBlock}; use thoth_errors::{ThothError, ThothResult}; +#[derive(Copy, Clone)] pub struct Onix3ProjectMuse {} impl XmlSpecification for Onix3ProjectMuse {