diff --git a/openapi.schema.yaml b/openapi.schema.yaml index b833d1ec..f07a5785 100644 --- a/openapi.schema.yaml +++ b/openapi.schema.yaml @@ -9,6 +9,59 @@ info: name: MIT version: 0.29.6 paths: + /api/v1/strucvars/csq: + get: + tags: + - strucvars_csq + summary: Query for consequence of a variant. + operationId: strucvarsCsq + parameters: + - name: genome_release + in: query + description: The assembly. + required: true + schema: + $ref: '#/components/schemas/GenomeRelease' + - name: chromosome + in: query + description: Chromosome. + required: true + schema: + type: string + - name: start + in: query + description: 1-based start position. + required: true + schema: + type: integer + format: int32 + - name: stop + in: query + description: 1-based stop position, ignored for INS. + required: false + schema: + type: integer + format: int32 + nullable: true + - name: sv_type + in: query + description: The variant type to use for annotation. + required: true + schema: + $ref: '#/components/schemas/StrucvarsSvType' + responses: + '200': + description: Strucvars consequence information. + content: + application/json: + schema: + $ref: '#/components/schemas/StrucvarsCsqResponse' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/CustomError' /api/v1/versionsInfo: get: tags: @@ -62,6 +115,12 @@ components: type: string description: Version of the Ensembl database, if any. nullable: true + GenomeRelease: + type: string + description: Select the genome release to use. + enum: + - grch37 + - grch38 SoftwareVersions: type: object description: Software version specification. @@ -75,6 +134,83 @@ components: hgvs_rs: type: string description: Version of the `hgvs` crate. + StrucvarsCsqQuery: + type: object + description: Query parameters of the `/api/v1/strucvars/csq` endpoint. + required: + - genome_release + - chromosome + - start + - sv_type + properties: + genome_release: + $ref: '#/components/schemas/GenomeRelease' + chromosome: + type: string + description: Chromosome. + start: + type: integer + format: int32 + description: 1-based start position. + stop: + type: integer + format: int32 + description: 1-based stop position, ignored for INS. + nullable: true + sv_type: + $ref: '#/components/schemas/StrucvarsSvType' + StrucvarsCsqResponse: + type: object + description: Response of the `/api/v1/strucvars/csq` endpoint. + required: + - version + - query + - result + properties: + version: + $ref: '#/components/schemas/VersionsInfoResponse' + query: + $ref: '#/components/schemas/StrucvarsCsqQuery' + result: + type: array + items: + $ref: '#/components/schemas/StrucvarsGeneTranscriptEffects' + description: The resulting records for the affected genes. + StrucvarsGeneTranscriptEffects: + type: object + description: Explanation of transcript effect per individual gene. + required: + - hgnc_id + - transcript_effects + properties: + hgnc_id: + type: string + description: HGNC identifier + transcript_effects: + type: array + items: + $ref: '#/components/schemas/StrucvarsTranscriptEffect' + description: Transcript effects for the gene. + StrucvarsSvType: + type: string + description: Structural Variant type. + enum: + - DEL + - DUP + - INS + - INV + - BND + StrucvarsTranscriptEffect: + type: string + description: Enumeration for effect on transcript. + enum: + - transcript_variant + - exon_variant + - splice_region_variant + - intron_variant + - upstream_variant + - downstream_variant + - intergenic_variant VersionsInfoResponse: type: object description: Response of the `/api/v1/version` endpoint. diff --git a/src/annotate/strucvars/csq.rs b/src/annotate/strucvars/csq.rs index 9923eb76..a61ecd5f 100644 --- a/src/annotate/strucvars/csq.rs +++ b/src/annotate/strucvars/csq.rs @@ -12,10 +12,20 @@ use crate::{ /// Enumeration for effect on transcript. #[derive( - serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy, + serde::Serialize, + serde::Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Debug, + Clone, + Copy, + utoipa::ToSchema, )] #[serde(rename_all = "snake_case")] -pub enum TranscriptEffect { +// #[schema(as=StrucvarsTranscriptEffect)] // TODO: rename back to TranscriptEffect once utoipa's as= is fixed. +pub enum StrucvarsTranscriptEffect { /// Affects the full transcript. TranscriptVariant, /// An exon is affected by the SV. @@ -32,17 +42,17 @@ pub enum TranscriptEffect { IntergenicVariant, } -impl TranscriptEffect { +impl StrucvarsTranscriptEffect { /// Return vector with all transcript effects. - pub fn vec_all() -> Vec { + pub fn vec_all() -> Vec { vec![ - TranscriptEffect::TranscriptVariant, - TranscriptEffect::ExonVariant, - TranscriptEffect::SpliceRegionVariant, - TranscriptEffect::IntronVariant, - TranscriptEffect::UpstreamVariant, - TranscriptEffect::DownstreamVariant, - TranscriptEffect::IntergenicVariant, + StrucvarsTranscriptEffect::TranscriptVariant, + StrucvarsTranscriptEffect::ExonVariant, + StrucvarsTranscriptEffect::SpliceRegionVariant, + StrucvarsTranscriptEffect::IntronVariant, + StrucvarsTranscriptEffect::UpstreamVariant, + StrucvarsTranscriptEffect::DownstreamVariant, + StrucvarsTranscriptEffect::IntergenicVariant, ] } } @@ -51,9 +61,20 @@ impl TranscriptEffect { pub mod interface { /// Structural Variant type. #[derive( - serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, + serde::Serialize, + serde::Deserialize, + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + utoipa::ToSchema, )] - pub enum StrucVarType { + // #[schema(as=StrucvarsSvType)] // TODO: rename back to StrucVarType once utoipa's as= is fixed. + pub enum StrucvarsSvType { #[serde(rename = "DEL")] Del, #[serde(rename = "DUP")] @@ -109,7 +130,7 @@ pub mod interface { fn stop(&self) -> i32; /// Type of the structural variant - fn sv_type(&self) -> StrucVarType; + fn sv_type(&self) -> StrucvarsSvType; /// The strand orientation of the structural variant, if applicable. fn strand_orientation(&self) -> StrandOrientation; } @@ -125,16 +146,17 @@ struct TxRegion { // "arbitrary" number no: usize, // effect of the transcript (encodes region type) - effect: TranscriptEffect, + effect: StrucvarsTranscriptEffect, } /// Explanation of transcript effect per individual gene. -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] -pub struct GeneTranscriptEffects { +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, utoipa::ToSchema)] +// #[schema(as=StrucvarsGeneTranscriptEffects)] // TODO: rename back to GeneTranscriptEffects once utoipa's as= is fixed. +pub struct StrucvarsGeneTranscriptEffects { /// HGNC identifier hgnc_id: String, /// Transcript effects for the gene. - transcript_effects: Vec, + transcript_effects: Vec, } /// Length of the upstream/downstream region. @@ -188,9 +210,9 @@ fn tx_regions(tx: &Transcript) -> Vec { end: exon_alignment.alt_start_i - 1, no, effect: if genome_alignment.strand == Strand::Plus as i32 { - TranscriptEffect::UpstreamVariant + StrucvarsTranscriptEffect::UpstreamVariant } else { - TranscriptEffect::DownstreamVariant + StrucvarsTranscriptEffect::DownstreamVariant }, }); } else { @@ -199,7 +221,7 @@ fn tx_regions(tx: &Transcript) -> Vec { begin: (exon_alignment.alt_start_i - 1) - 8, end: (exon_alignment.alt_start_i - 1) + 3, no, - effect: TranscriptEffect::SpliceRegionVariant, + effect: StrucvarsTranscriptEffect::SpliceRegionVariant, }) } @@ -210,9 +232,9 @@ fn tx_regions(tx: &Transcript) -> Vec { end: exon_alignment.alt_end_i + X_STREAM, no, effect: if genome_alignment.strand == Strand::Plus as i32 { - TranscriptEffect::DownstreamVariant + StrucvarsTranscriptEffect::DownstreamVariant } else { - TranscriptEffect::UpstreamVariant + StrucvarsTranscriptEffect::UpstreamVariant }, }); } else { @@ -221,7 +243,7 @@ fn tx_regions(tx: &Transcript) -> Vec { begin: exon_alignment.alt_end_i - 3, end: exon_alignment.alt_end_i + 8, no, - effect: TranscriptEffect::SpliceRegionVariant, + effect: StrucvarsTranscriptEffect::SpliceRegionVariant, }) } @@ -230,7 +252,7 @@ fn tx_regions(tx: &Transcript) -> Vec { begin: exon_alignment.alt_start_i - 1, end: exon_alignment.alt_end_i, no, - effect: TranscriptEffect::ExonVariant, + effect: StrucvarsTranscriptEffect::ExonVariant, }); if exon_alignment.alt_start_i != tx_start { @@ -239,7 +261,7 @@ fn tx_regions(tx: &Transcript) -> Vec { begin: prev_alt_end_i, end: exon_alignment.alt_start_i - 1, no, - effect: TranscriptEffect::IntronVariant, + effect: StrucvarsTranscriptEffect::IntronVariant, }); } @@ -251,7 +273,7 @@ fn tx_regions(tx: &Transcript) -> Vec { } /// Return the transcript region / effect for the given breakpoint. -fn gene_tx_effects_for_bp(tx: &Transcript, pos: i32) -> Vec { +fn gene_tx_effects_for_bp(tx: &Transcript, pos: i32) -> Vec { // Obtain list of regions for transcript. let regions = tx_regions(tx); @@ -263,7 +285,7 @@ fn gene_tx_effects_for_bp(tx: &Transcript, pos: i32) -> Vec { .map(|r| r.effect) .collect::>(); if result.is_empty() { - result.push(TranscriptEffect::IntergenicVariant); + result.push(StrucvarsTranscriptEffect::IntergenicVariant); } else { result.sort(); result.dedup(); @@ -272,7 +294,11 @@ fn gene_tx_effects_for_bp(tx: &Transcript, pos: i32) -> Vec { } /// Return the transcript region / effect for the given range. -fn gene_tx_effect_for_range(tx: &Transcript, start: i32, stop: i32) -> Vec { +fn gene_tx_effect_for_range( + tx: &Transcript, + start: i32, + stop: i32, +) -> Vec { // Obtain list of regions for transcript. let regions = tx_regions(tx); @@ -289,10 +315,10 @@ fn gene_tx_effect_for_range(tx: &Transcript, start: i32, stop: i32) -> Vec, -) -> Vec { +) -> Vec { // Shortcut to the `TranscriptDb`. let tx_db = mehari_tx_db .tx_db @@ -342,10 +368,12 @@ fn compute_tx_effects_for_breakpoint( // Convert the results into the final format. effects_by_gene .into_iter() - .map(|(hgnc_id, transcript_effects)| GeneTranscriptEffects { - hgnc_id, - transcript_effects, - }) + .map( + |(hgnc_id, transcript_effects)| StrucvarsGeneTranscriptEffects { + hgnc_id, + transcript_effects, + }, + ) .collect() } else { // We do not have any transcripts for this chromosome. @@ -359,7 +387,7 @@ fn compute_tx_effects_for_linear( mehari_tx_db: &TxSeqDatabase, mehari_tx_idx: &TxIntervalTrees, chrom_to_acc: &HashMap, -) -> Vec { +) -> Vec { // Shortcut to the `TranscriptDb`. let tx_db = mehari_tx_db .tx_db @@ -397,10 +425,12 @@ fn compute_tx_effects_for_linear( // Convert the results into the final format. effects_by_gene .into_iter() - .map(|(hgnc_id, transcript_effects)| GeneTranscriptEffects { - hgnc_id, - transcript_effects, - }) + .map( + |(hgnc_id, transcript_effects)| StrucvarsGeneTranscriptEffects { + hgnc_id, + transcript_effects, + }, + ) .collect() } else { // We do not have any transcripts for this chromosome. @@ -447,9 +477,9 @@ impl ConsequencePredictor { // mehari_tx_db: &TxSeqDatabase, // mehari_tx_idx: &TxIntervalTrees, // chrom_to_acc: &HashMap, - ) -> Vec { + ) -> Vec { match sv.sv_type() { - interface::StrucVarType::Ins | interface::StrucVarType::Bnd => { + interface::StrucvarsSvType::Ins | interface::StrucvarsSvType::Bnd => { compute_tx_effects_for_breakpoint( sv, &self.provider.tx_seq_db, @@ -457,9 +487,9 @@ impl ConsequencePredictor { &self.chrom_to_acc, ) } - interface::StrucVarType::Del - | interface::StrucVarType::Dup - | interface::StrucVarType::Inv => compute_tx_effects_for_linear( + interface::StrucvarsSvType::Del + | interface::StrucvarsSvType::Dup + | interface::StrucvarsSvType::Inv => compute_tx_effects_for_linear( sv, &self.provider.tx_seq_db, &self.provider.tx_trees, diff --git a/src/common/mod.rs b/src/common/mod.rs index 7188eb8e..f11b6d7d 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -46,6 +46,7 @@ pub fn trace_rss_now() { Eq, Hash, Default, + utoipa::ToSchema, )] #[serde(rename_all = "snake_case")] pub enum GenomeRelease { diff --git a/src/server/run/actix_server/gene_txs.rs b/src/server/run/actix_server/gene_txs.rs index 0d1ca01e..0fd13f81 100644 --- a/src/server/run/actix_server/gene_txs.rs +++ b/src/server/run/actix_server/gene_txs.rs @@ -1,4 +1,6 @@ -//! Implementation of `/seqvars/csq` endpoint. +//! Implementation of endpoint `/api/v1/genes/transcripts`. +//! +//! Also includes the implementation of the `/genes/txs` endpoint (deprecated). use crate::common::GenomeRelease; use crate::pbs; @@ -73,7 +75,7 @@ fn genes_tx_impl( }) } -/// Implementation of the `/genes/txs` endpoint. +/// Query for transcripts of a gene. #[allow(clippy::unused_async)] #[get("/genes/txs")] async fn handle( @@ -337,7 +339,7 @@ impl TryFrom for GenesTranscriptsListRespo } } -/// Query for consequence of a variant. +/// Query for transcripts of a gene. #[allow(clippy::unused_async)] #[utoipa::path( get, diff --git a/src/server/run/actix_server/mod.rs b/src/server/run/actix_server/mod.rs index 1511a144..f96169b6 100644 --- a/src/server/run/actix_server/mod.rs +++ b/src/server/run/actix_server/mod.rs @@ -62,6 +62,7 @@ pub async fn main( .service(gene_txs::handle_with_openapi) .service(seqvars_csq::handle) .service(strucvars_csq::handle) + .service(strucvars_csq::handle_with_openapi) .service(versions::handle) .service( utoipa_swagger_ui::SwaggerUi::new("/swagger-ui/{_:.*}") diff --git a/src/server/run/actix_server/strucvars_csq.rs b/src/server/run/actix_server/strucvars_csq.rs index 5eb2959d..d60858d5 100644 --- a/src/server/run/actix_server/strucvars_csq.rs +++ b/src/server/run/actix_server/strucvars_csq.rs @@ -1,3 +1,7 @@ +//! Implementation of endpoint `/api/v1/strucvars/csq`. +//! +//! Also includes the implementation of the `/strucvars/csq` endpoint (deprecated). + use actix_web::{ get, web::{self, Data, Json, Path}, @@ -5,10 +9,16 @@ use actix_web::{ }; use crate::{ - annotate::strucvars::csq::{interface, GeneTranscriptEffects}, + annotate::strucvars::csq::{ + interface::{self, StrucvarsSvType}, + StrucvarsGeneTranscriptEffects, + }, common::GenomeRelease, + server::run::actix_server::CustomError, }; +use super::versions::VersionsInfoResponse; + /// Parameters for `/strucvars/csq`. /// #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] @@ -41,21 +51,21 @@ impl interface::StrucVar for Query { } fn stop(&self) -> i32 { - if self.sv_type() == interface::StrucVarType::Ins { + if self.sv_type() == interface::StrucvarsSvType::Ins { self.start } else { self.stop.unwrap_or(self.start) } } - fn sv_type(&self) -> interface::StrucVarType { + fn sv_type(&self) -> interface::StrucvarsSvType { match self.sv_type.to_uppercase().as_ref() { - "DEL" => interface::StrucVarType::Del, - "DUP" => interface::StrucVarType::Dup, - "INS" => interface::StrucVarType::Ins, - "BND" => interface::StrucVarType::Bnd, - "INV" => interface::StrucVarType::Inv, - _ => interface::StrucVarType::Del, + "DEL" => interface::StrucvarsSvType::Del, + "DUP" => interface::StrucvarsSvType::Dup, + "INS" => interface::StrucvarsSvType::Ins, + "BND" => interface::StrucvarsSvType::Bnd, + "INV" => interface::StrucvarsSvType::Inv, + _ => interface::StrucvarsSvType::Del, } } @@ -72,7 +82,7 @@ struct Container { /// The original query records. pub query: Query, /// The resulting records for the scored genes. - pub result: Vec, + pub result: Vec, } /// Query for consequence of a variant. @@ -103,3 +113,104 @@ async fn handle( Ok(Json(result)) } + +/// Query parameters of the `/api/v1/strucvars/csq` endpoint. +#[derive( + Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::IntoParams, utoipa::ToSchema, +)] +#[serde(rename_all = "snake_case")] +#[serde_with::skip_serializing_none] +pub(crate) struct StrucvarsCsqQuery { + /// The assembly. + pub genome_release: GenomeRelease, + /// Chromosome. + pub chromosome: String, + /// 1-based start position. + pub start: i32, + /// 1-based stop position, ignored for INS. + pub stop: Option, + /// The variant type to use for annotation. + pub sv_type: StrucvarsSvType, +} + +impl interface::StrucVar for StrucvarsCsqQuery { + fn chrom(&self) -> String { + self.chromosome.clone() + } + + fn chrom2(&self) -> String { + self.chromosome.clone() + } + + fn start(&self) -> i32 { + self.start + } + + fn stop(&self) -> i32 { + if self.sv_type() == interface::StrucvarsSvType::Ins { + self.start + } else { + self.stop.unwrap_or(self.start) + } + } + + fn sv_type(&self) -> interface::StrucvarsSvType { + self.sv_type + } + + fn strand_orientation(&self) -> interface::StrandOrientation { + interface::StrandOrientation::NotApplicable + } +} + +/// Response of the `/api/v1/strucvars/csq` endpoint. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema)] +pub(crate) struct StrucvarsCsqResponse { + /// Version information. + pub version: VersionsInfoResponse, + /// The original query record. + pub query: StrucvarsCsqQuery, + /// The resulting records for the affected genes. + pub result: Vec, +} + +/// Query for consequence of a variant. +#[allow(clippy::unused_async)] +#[utoipa::path( + get, + operation_id = "strucvarsCsq", + params( + StrucvarsCsqQuery + ), + responses( + (status = 200, description = "Strucvars consequence information.", body = StrucvarsCsqResponse), + (status = 500, description = "Internal server error.", body = CustomError) + ) +)] +#[get("/api/v1/strucvars/csq")] +async fn handle_with_openapi( + data: Data, + _path: Path<()>, + query: web::Query, +) -> actix_web::Result, CustomError> { + let predictor = data + .strucvars_predictors + .get(&query.genome_release) + .ok_or_else(|| { + super::CustomError::new(anyhow::anyhow!( + "genome release not supported: {:?}", + &query.genome_release + )) + })?; + + let result = predictor.compute_tx_effects(&query.clone().into_inner()); + + let result = StrucvarsCsqResponse { + version: VersionsInfoResponse::from_web_server_data(data.into_inner().as_ref()) + .map_err(|e| CustomError::new(anyhow::anyhow!("Problem determining version: {}", e)))?, + query: query.into_inner(), + result, + }; + + Ok(Json(result)) +} diff --git a/src/server/run/mod.rs b/src/server/run/mod.rs index 009360e0..d66fbbd3 100644 --- a/src/server/run/mod.rs +++ b/src/server/run/mod.rs @@ -16,22 +16,36 @@ pub mod actix_server; /// Module with OpenAPI documentation. pub mod openapi { + use crate::annotate::strucvars::csq::interface::StrucvarsSvType; + use crate::annotate::strucvars::csq::{ + StrucvarsGeneTranscriptEffects, StrucvarsTranscriptEffect, + }; + use crate::common::GenomeRelease; + use crate::server::run::actix_server::strucvars_csq::{ + StrucvarsCsqQuery, StrucvarsCsqResponse, + }; use crate::server::run::actix_server::versions::{ Assembly, DataVersionEntry, SoftwareVersions, VersionsInfoResponse, }; - use super::actix_server::{versions, CustomError}; + use super::actix_server::{strucvars_csq, versions, CustomError}; /// Utoipa-based `OpenAPI` generation helper. #[derive(utoipa::OpenApi)] #[openapi( - paths(versions::handle), + paths(versions::handle, strucvars_csq::handle_with_openapi), components(schemas( Assembly, CustomError, VersionsInfoResponse, SoftwareVersions, - DataVersionEntry + DataVersionEntry, + StrucvarsCsqResponse, + StrucvarsCsqQuery, + StrucvarsGeneTranscriptEffects, + StrucvarsSvType, + GenomeRelease, + StrucvarsTranscriptEffect, )) )] pub struct ApiDoc;