1- //! Implementation of `/genes/search` that allows to search for genes by symbol etc.
1+ //! Implementation of endpoint `/api/v1/genes/search`.
2+ //!
3+ //! Also includes the implementation of the `/genes/search` endpoint (deprecated).
24//!
35//! Gene identifiers (HGNC, NCBI, ENSEMBL) must match. As for symbols and names, the
46//! search string may also be a substring.
57use actix_web:: {
68 get,
79 web:: { self , Data , Json , Path } ,
8- Responder ,
910} ;
1011
1112use crate :: server:: run:: GeneNames ;
@@ -15,19 +16,19 @@ use serde_with::{formats::CommaSeparator, StringWithSeparator};
1516
1617/// The allowed fields to search in.
1718#[ derive(
18- serde:: Serialize ,
19- serde:: Deserialize ,
20- strum:: Display ,
21- strum:: EnumString ,
2219 Debug ,
2320 Clone ,
2421 Copy ,
2522 PartialEq ,
2623 Eq ,
24+ strum:: Display ,
25+ strum:: EnumString ,
26+ serde:: Serialize ,
27+ serde:: Deserialize ,
28+ utoipa:: ToSchema ,
2729) ]
2830#[ serde( rename_all = "snake_case" ) ]
29- #[ strum( serialize_all = "snake_case" ) ]
30- enum Fields {
31+ pub ( crate ) enum GenesFields {
3132 /// HGNC ID field
3233 HgncId ,
3334 /// Symbol field
@@ -47,46 +48,48 @@ enum Fields {
4748/// Parameters for `handle`.
4849#[ serde_with:: skip_serializing_none]
4950#[ serde_with:: serde_as]
50- #[ derive( serde:: Serialize , serde:: Deserialize , Debug , Clone ) ]
51+ #[ derive(
52+ Debug , Clone , serde:: Serialize , serde:: Deserialize , utoipa:: ToSchema , utoipa:: IntoParams ,
53+ ) ]
5154#[ serde( rename_all = "snake_case" ) ]
52- struct Request {
55+ pub ( crate ) struct GenesSearchQuery {
5356 /// The string to search for.
5457 pub q : String ,
5558 /// The fields to search in.
56- #[ serde_as( as = "Option<StringWithSeparator::<CommaSeparator, Fields >>" ) ]
57- pub fields : Option < Vec < Fields > > ,
59+ #[ serde_as( as = "Option<StringWithSeparator::<CommaSeparator, GenesFields >>" ) ]
60+ pub fields : Option < Vec < GenesFields > > ,
5861 /// Enable case sensitive search.
5962 pub case_sensitive : Option < bool > ,
6063}
6164
6265/// A scored result.
63- #[ derive( serde:: Serialize , serde:: Deserialize , Debug , Clone ) ]
64- struct Scored < T > {
66+ #[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize , utoipa :: ToSchema ) ]
67+ pub ( crate ) struct Scored < T > {
6568 /// The score.
6669 pub score : f32 ,
6770 /// The result.
6871 pub data : T ,
6972}
7073
74+ /// Alias for scored genes names.
75+ pub ( crate ) type GenesScoredGeneNames = Scored < GeneNames > ;
76+
7177/// Result for `handle`.
72- #[ derive( serde:: Serialize , serde:: Deserialize , Debug , Clone ) ]
78+ #[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize , utoipa :: ToSchema ) ]
7379#[ serde_with:: skip_serializing_none]
74- struct Container {
75- // TODO: add data version
80+ pub ( crate ) struct GenesSearchResponse {
7681 /// The resulting gene information.
77- pub genes : Vec < Scored < GeneNames > > ,
82+ pub genes : Vec < GenesScoredGeneNames > ,
7883}
7984
80- /// Query for annotations for one variant.
81- #[ allow( clippy:: option_map_unit_fn) ]
82- #[ get( "/genes/search" ) ]
83- async fn handle (
85+ /// Implementation of both endpoints.
86+ async fn handle_impl (
8487 data : Data < crate :: server:: run:: WebServerData > ,
8588 _path : Path < ( ) > ,
86- query : web:: Query < Request > ,
87- ) -> actix_web:: Result < impl Responder , CustomError > {
89+ query : web:: Query < GenesSearchQuery > ,
90+ ) -> actix_web:: Result < Json < GenesSearchResponse > , CustomError > {
8891 if query. q . len ( ) < 2 {
89- return Ok ( Json ( Container {
92+ return Ok ( Json ( GenesSearchResponse {
9093 // server_version: VERSION.to_string(),
9194 // builder_version,
9295 genes : Vec :: new ( ) ,
@@ -120,35 +123,36 @@ async fn handle(
120123 val. to_lowercase ( ) . contains ( & q)
121124 }
122125 } ;
123- let fields: Vec < Fields > = if let Some ( fields) = query. fields . as_ref ( ) {
126+ let fields: Vec < GenesFields > = if let Some ( fields) = query. fields . as_ref ( ) {
124127 fields. clone ( )
125128 } else {
126129 Vec :: new ( )
127130 } ;
128131
129132 // The fields contain the given field or are empty.
130- let fields_contains = |field : & Fields | -> bool { fields. is_empty ( ) || fields. contains ( field) } ;
133+ let fields_contains =
134+ |field : & GenesFields | -> bool { fields. is_empty ( ) || fields. contains ( field) } ;
131135
132136 let mut genes = genes_db
133137 . data
134138 . gene_names
135139 . iter ( )
136140 . map ( |gn| -> Scored < GeneNames > {
137- let score = if ( fields_contains ( & Fields :: HgncId ) && equals_q ( & gn. hgnc_id ) )
138- || ( fields_contains ( & Fields :: Symbol ) && equals_q ( & gn. symbol ) )
139- || ( fields_contains ( & Fields :: Symbol ) && equals_q ( & gn. symbol ) )
140- || ( fields_contains ( & Fields :: Name ) && equals_q ( & gn. name ) )
141- || ( fields_contains ( & Fields :: EnsemblGeneId )
141+ let score = if ( fields_contains ( & GenesFields :: HgncId ) && equals_q ( & gn. hgnc_id ) )
142+ || ( fields_contains ( & GenesFields :: Symbol ) && equals_q ( & gn. symbol ) )
143+ || ( fields_contains ( & GenesFields :: Symbol ) && equals_q ( & gn. symbol ) )
144+ || ( fields_contains ( & GenesFields :: Name ) && equals_q ( & gn. name ) )
145+ || ( fields_contains ( & GenesFields :: EnsemblGeneId )
142146 && gn. ensembl_gene_id . iter ( ) . any ( |s| equals_q ( s) ) )
143- || ( fields_contains ( & Fields :: NcbiGeneId )
147+ || ( fields_contains ( & GenesFields :: NcbiGeneId )
144148 && gn. ncbi_gene_id . iter ( ) . any ( |s| equals_q ( s) ) )
145149 {
146150 1f32
147- } else if fields_contains ( & Fields :: Symbol ) && contains_q ( & gn. symbol ) {
151+ } else if fields_contains ( & GenesFields :: Symbol ) && contains_q ( & gn. symbol ) {
148152 q. len ( ) as f32 / gn. symbol . len ( ) as f32
149- } else if fields_contains ( & Fields :: Name ) && contains_q ( & gn. name ) {
153+ } else if fields_contains ( & GenesFields :: Name ) && contains_q ( & gn. name ) {
150154 q. len ( ) as f32 / gn. name . len ( ) as f32
151- } else if fields_contains ( & Fields :: AliasSymbol )
155+ } else if fields_contains ( & GenesFields :: AliasSymbol )
152156 && gn. alias_symbol . iter ( ) . any ( |s| contains_q ( s) )
153157 {
154158 gn. alias_symbol
@@ -162,7 +166,7 @@ async fn handle(
162166 } )
163167 . max_by ( |a, b| a. partial_cmp ( b) . unwrap_or ( std:: cmp:: Ordering :: Equal ) )
164168 . unwrap_or ( 0f32 )
165- } else if fields_contains ( & Fields :: AliasName )
169+ } else if fields_contains ( & GenesFields :: AliasName )
166170 && gn. alias_name . iter ( ) . any ( |s| contains_q ( s) )
167171 {
168172 gn. alias_name
@@ -194,9 +198,38 @@ async fn handle(
194198 . unwrap_or ( std:: cmp:: Ordering :: Equal )
195199 } ) ;
196200
197- Ok ( Json ( Container {
201+ Ok ( Json ( GenesSearchResponse {
198202 // server_version: VERSION.to_string(),
199203 // builder_version,
200204 genes,
201205 } ) )
202206}
207+
208+ /// Search for genes.
209+ #[ get( "/genes/search" ) ]
210+ async fn handle (
211+ data : Data < crate :: server:: run:: WebServerData > ,
212+ path : Path < ( ) > ,
213+ query : web:: Query < GenesSearchQuery > ,
214+ ) -> actix_web:: Result < Json < GenesSearchResponse > , CustomError > {
215+ handle_impl ( data, path, query) . await
216+ }
217+
218+ /// Search for genes.
219+ #[ utoipa:: path(
220+ get,
221+ operation_id = "genesSearch" ,
222+ params( GenesSearchQuery ) ,
223+ responses(
224+ ( status = 200 , description = "Genes search results." , body = GenesSearchResponse ) ,
225+ ( status = 500 , description = "Internal server error." , body = CustomError )
226+ )
227+ ) ]
228+ #[ get( "/api/v1/genes/search" ) ]
229+ async fn handle_with_openapi (
230+ data : Data < crate :: server:: run:: WebServerData > ,
231+ path : Path < ( ) > ,
232+ query : web:: Query < GenesSearchQuery > ,
233+ ) -> actix_web:: Result < Json < GenesSearchResponse > , CustomError > {
234+ handle_impl ( data, path, query) . await
235+ }
0 commit comments