From a71d62a316f1eca09a55f32c260a35f0aefb09ea Mon Sep 17 00:00:00 2001 From: Manuel Holtgrewe Date: Wed, 13 Nov 2024 10:04:59 +0100 Subject: [PATCH] feat: endpoint /api/v1/genes/lookup with OpenAPI (#587) (#589) --- openapi.schema.yaml | 53 +++++++++++++++++++++ src/server/run/genes_lookup.rs | 86 +++++++++++++++++++++++++++++----- src/server/run/mod.rs | 5 ++ 3 files changed, 132 insertions(+), 12 deletions(-) diff --git a/openapi.schema.yaml b/openapi.schema.yaml index 3514eb36..8551ec24 100644 --- a/openapi.schema.yaml +++ b/openapi.schema.yaml @@ -39,6 +39,34 @@ paths: application/json: schema: $ref: '#/components/schemas/CustomError' + /api/v1/genes/lookup: + get: + tags: + - genes_lookup + summary: Search for genes. + operationId: genesLookup + parameters: + - name: q + in: query + description: The strings to search for. + required: true + schema: + type: array + items: + type: string + responses: + '200': + description: Genes search results. + content: + application/json: + schema: + $ref: '#/components/schemas/GenesLookupResponse' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/CustomError' /api/v1/genes/search: get: tags: @@ -1886,6 +1914,31 @@ components: - string - 'null' description: The disorder name. + GenesLookupResponse: + type: object + description: Result for `async fn handle_with_openapi( + required: + - genes + properties: + genes: + type: array + items: + $ref: '#/components/schemas/GenesLookupResultEntry' + description: The resulting gene information. + GenesLookupResultEntry: + type: object + description: One result entry in the response. + required: + - query + properties: + query: + type: string + description: The query string, + gene_names: + oneOf: + - type: 'null' + - $ref: '#/components/schemas/GeneNames' + description: The gene names information. GenesNcbiRecord: type: object description: A record from the NCBI gene database. diff --git a/src/server/run/genes_lookup.rs b/src/server/run/genes_lookup.rs index a14e96b3..e8c7ba23 100644 --- a/src/server/run/genes_lookup.rs +++ b/src/server/run/genes_lookup.rs @@ -1,11 +1,12 @@ -//! Implementation of `/genes/lookup` that allows to lookup genes by symbol or identifier. +//! Implementation of endpoint `/api/v1/genes/lookup`. +//! +//! Also includes the implementation of the `/genes/lookup` endpoint (deprecated). //! //! In contrast to gene search, more than one query may be given but this must match exactly //! the symbol or HGNC/NCBI/ENSEMBL identifier. use actix_web::{ get, web::{self, Data, Json, Path}, - Responder, }; use crate::server::run::GeneNames; @@ -16,9 +17,11 @@ use serde_with::{formats::CommaSeparator, StringWithSeparator}; /// Parameters for `handle`. #[serde_with::skip_serializing_none] #[serde_with::serde_as] -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +#[derive( + serde::Serialize, serde::Deserialize, Debug, Clone, utoipa::ToSchema, utoipa::IntoParams, +)] #[serde(rename_all = "snake_case")] -struct Request { +pub(crate) struct GenesLookupQuery { /// The strings to search for. #[serde_as(as = "StringWithSeparator::")] pub q: Vec, @@ -33,14 +36,12 @@ struct Container { pub genes: indexmap::IndexMap>, } -/// Query for annotations for one variant. -#[allow(clippy::option_map_unit_fn)] -#[get("/genes/lookup")] -async fn handle( +/// Implementation of both endpoints. +async fn handle_impl( data: Data, _path: Path<()>, - query: web::Query, -) -> actix_web::Result { + query: web::Query, +) -> actix_web::Result { let genes_db = data.genes.as_ref().ok_or(CustomError::new(anyhow::anyhow!( "genes database not available" )))?; @@ -54,9 +55,70 @@ async fn handle( (q.clone(), v) })); - Ok(Json(Container { + Ok(Container { // server_version: VERSION.to_string(), // builder_version, genes, - })) + }) +} + +/// Query for annotations for one variant. +#[get("/genes/lookup")] +async fn handle( + data: Data, + path: Path<()>, + query: web::Query, +) -> actix_web::Result, CustomError> { + Ok(Json(handle_impl(data, path, query).await?)) +} + +/// One result entry in the response. +#[derive( + Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema, utoipa::ToResponse, +)] +pub(crate) struct GenesLookupResultEntry { + /// The query string, + pub query: String, + /// The gene names information. + pub gene_names: Option, +} + +/// Result for `async fn handle_with_openapi( +#[derive( + Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema, utoipa::ToResponse, +)] +pub(crate) struct GenesLookupResponse { + /// The resulting gene information. + pub genes: Vec, +} + +impl From for GenesLookupResponse { + fn from(container: Container) -> Self { + Self { + genes: container + .genes + .into_iter() + .map(|(query, gene_names)| GenesLookupResultEntry { query, gene_names }) + .collect(), + } + } +} + +/// Search for genes. +#[utoipa::path( + get, + operation_id = "genesLookup", + params(GenesLookupQuery), + responses( + (status = 200, description = "Genes search results.", body = GenesLookupResponse), + (status = 500, description = "Internal server error.", body = CustomError) + ) +)] +#[get("/api/v1/genes/lookup")] +async fn handle_with_openapi( + data: Data, + path: Path<()>, + query: web::Query, +) -> actix_web::Result, CustomError> { + Ok(Json(handle_impl(data, path, query).await?.into())) } diff --git a/src/server/run/mod.rs b/src/server/run/mod.rs index 973abad1..56d92354 100644 --- a/src/server/run/mod.rs +++ b/src/server/run/mod.rs @@ -38,6 +38,7 @@ pub mod openapi { use crate::{ common::cli::GenomeRelease, server::run::genes_info::{self, response::*}, + server::run::genes_lookup::{self, GenesLookupResponse, GenesLookupResultEntry}, server::run::genes_search::{ self, GenesFields, GenesScoredGeneNames, GenesSearchQuery, GenesSearchResponse, }, @@ -54,6 +55,7 @@ pub mod openapi { paths( versions::handle, genes_info::handle_with_openapi, + genes_lookup::handle_with_openapi, genes_search::handle_with_openapi ), components(schemas( @@ -115,6 +117,8 @@ pub mod openapi { GenesSearchResponse, GenesScoredGeneNames, GeneNames, + GenesLookupResponse, + GenesLookupResultEntry )) )] pub struct ApiDoc; @@ -142,6 +146,7 @@ pub async fn main(args: &Args, dbs: Data) -> std::io::Result<()> .service(genes_search::handle) .service(genes_search::handle_with_openapi) .service(genes_lookup::handle) + .service(genes_lookup::handle_with_openapi) .service(versions::handle) .service( utoipa_swagger_ui::SwaggerUi::new("/swagger-ui/{_:.*}")