diff --git a/src/main.rs b/src/main.rs index 6fb7b1a9..fc2103c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ pub mod common; pub mod db; -pub mod server; pub mod sv; use clap::{Args, Parser, Subcommand}; @@ -30,12 +29,10 @@ struct Cli { #[allow(clippy::large_enum_variant)] #[derive(Debug, Subcommand)] enum Commands { - /// Database-related commands. + /// Database building related commands. Db(Db), /// SV filtration related commands. Sv(Sv), - /// Server-related commands. - Server(Server), } /// Parsing of "db *" sub commands. @@ -70,20 +67,6 @@ enum SvCommands { Query(sv::query::Args), } -/// Enum supporting the parsing of "server *" sub commands. -#[derive(Debug, Subcommand)] -enum ServerCommands { - Rest(server::rest::Args), -} -/// Parsing of "sv *" sub commands. -#[derive(Debug, Args)] -#[command(args_conflicts_with_subcommands = true)] -struct Server { - /// The sub command to run - #[command(subcommand)] - command: ServerCommands, -} - fn main() -> Result<(), anyhow::Error> { let cli = Cli::parse(); @@ -120,9 +103,6 @@ fn main() -> Result<(), anyhow::Error> { sv::query::run(&cli.common, args)?; } }, - Commands::Server(server) => match &server.command { - ServerCommands::Rest(args) => server::rest::run(&cli.common, args)?, - }, } Ok::<(), anyhow::Error>(()) diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index ceaf0073..00000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Module containing the different REST servers and supporting commands. - -pub mod rest; diff --git a/src/server/rest/actix_server.rs b/src/server/rest/actix_server.rs deleted file mode 100644 index 1740f5be..00000000 --- a/src/server/rest/actix_server.rs +++ /dev/null @@ -1,293 +0,0 @@ -//! Implementation of the actix server. - -use std::str::FromStr; - -use actix_web::{ - get, - middleware::Logger, - web::{self, Data, Json, Path}, - App, HttpServer, Responder, ResponseError, -}; -use serde::{Deserialize, Serialize}; -use thousands::Separable; - -use crate::{ - common::CHROMS, - db::conf::TadSet as TadSetChoice, - sv::query::{ - bgdbs::BgDbType, - schema::{Pathogenicity, SvType, VariationType}, - }, -}; -use crate::{db::conf::GenomeRelease, sv::query::records::ChromRange}; - -use super::{Args, WebServerData}; - -#[derive(Debug)] -struct CustomError { - err: anyhow::Error, -} - -impl std::fmt::Display for CustomError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.err) - } -} - -impl CustomError { - fn new(err: anyhow::Error) -> Self { - CustomError { err } - } -} - -impl ResponseError for CustomError {} - -/// Chromosomal range for parsing from REST. -/// -/// We allow floats here as igv.js likes to put floats for coordinates. -#[derive(Deserialize, Debug)] -struct QueryChromRange { - chromosome: String, - begin: f64, - end: f64, -} - -impl From for ChromRange { - fn from(val: QueryChromRange) -> Self { - ChromRange { - chromosome: val.chromosome, - begin: val.begin as i32, - end: val.end as i32, - } - } -} - -/// Parameters for `fetch_tads`. -#[derive(Deserialize, Debug, Clone)] -struct FetchTadsRequest { - chromosome: String, - begin: f64, - end: f64, - /// A padding of 1 forces igv.js to display book-ended domains to be - /// displayed on different vertical positions. - padding: Option, -} - -impl From for ChromRange { - fn from(val: FetchTadsRequest) -> Self { - ChromRange { - chromosome: val.chromosome, - begin: val.begin as i32, - end: val.end as i32, - } - } -} - -/// Result type of ""/public/svs/tads/{release}/{tad_set}/". -#[derive(Serialize, Debug)] -struct Tad { - chromosome: String, - begin: i32, - end: i32, -} - -/// List the overlapping TADs of the given TAD set. -#[get("/public/svs/tads/{release}/{tad_set}/")] -async fn fetch_tads( - data: Data, - path: Path<(String, String)>, - query: web::Query, -) -> actix_web::Result { - let genome_release = - GenomeRelease::from_str(&path.0).map_err(|e| CustomError::new(e.into()))?; - let tad_set = TadSetChoice::from_str(&path.1).map_err(|e| CustomError::new(e.into()))?; - let chrom_range: ChromRange = query.clone().into_inner().into(); - let tads = data.dbs[genome_release] - .tad_sets - .fetch_tads(tad_set, &chrom_range, &data.chrom_map) - .into_iter() - .map(|record| Tad { - chromosome: CHROMS[record.chrom_no as usize].to_string(), - begin: record.begin - query.padding.unwrap_or_default(), - end: record.end + query.padding.unwrap_or_default(), - }) - .collect::>(); - Ok(Json(tads)) -} - -/// Result type of "/public/svs/pathogenic/{release}/". -#[derive(Serialize, Debug)] -struct KnownPathogenic { - chromosome: String, - begin: i32, - end: i32, - sv_type: SvType, - id: String, -} - -/// List the overlapping TADs of the given TAD set. -#[get("/public/svs/pathogenic/{release}/")] -async fn fetch_pathogenic( - data: Data, - path: Path<(String,)>, - chrom_range: web::Query, -) -> actix_web::Result { - let genome_release = - GenomeRelease::from_str(&path.0).map_err(|e| CustomError::new(e.into()))?; - let chrom_range: ChromRange = chrom_range.into_inner().into(); - let known_pathogenics = data.dbs[genome_release] - .patho_dbs - .fetch_records(&chrom_range, &data.chrom_map) - .into_iter() - .map(|record| KnownPathogenic { - chromosome: chrom_range.chromosome.clone(), - begin: record.begin, - end: record.end, - sv_type: record.sv_type, - id: record.id, - }) - .collect::>(); - Ok(Json(known_pathogenics)) -} - -/// Result type of "/public/svs/clinvar/{release}/". -#[derive(Serialize, Debug)] -struct ClinvarEntry { - chromosome: String, - begin: i32, - end: i32, - variation_type: VariationType, - pathogenicity: Pathogenicity, - vcv: String, - name: String, -} - -/// We allow begin and end to be floats as igv.js likes to put floats. -#[derive(Serialize, Deserialize, PartialEq, Debug, Default, Clone)] -pub struct ClinVarSvQuery { - /// Chromosome name. - pub chromosome: String, - /// 0-based begin position. - pub begin: f64, - /// 0-based end position. - pub end: f64, - /// Minimal pathogenicity. - pub min_pathogenicity: Option, -} - -/// List the overlapping TADs of the given TAD set. -#[get("/public/svs/clinvar/{release}/")] -async fn fetch_clinvar_sv( - data: Data, - path: Path<(String,)>, - query: web::Query, -) -> actix_web::Result { - let genome_release = - GenomeRelease::from_str(&path.0).map_err(|e| CustomError::new(e.into()))?; - let chrom_range = ChromRange { - chromosome: query.chromosome.clone(), - begin: query.begin as i32, - end: query.end as i32, - }; - let clinvar_entries = data.dbs[genome_release] - .clinvar_sv - .fetch_records(&chrom_range, &data.chrom_map, query.min_pathogenicity) - .into_iter() - .map(|record| ClinvarEntry { - chromosome: chrom_range.chromosome.clone(), - begin: record.start - 1, - end: record.stop, - variation_type: crate::sv::query::clinvar::pbs::VariationType::from_i32( - record.variation_type, - ) - .expect("unknown variation type") - .try_into() - .expect("problem with variation type"), - pathogenicity: crate::sv::query::clinvar::pbs::Pathogenicity::from_i32( - record.pathogenicity, - ) - .expect("unknown pathogenicity") - .try_into() - .expect("problem with pathogenicity"), - vcv: format!("VCV{:09}", record.vcv), - name: format!( - "{:?} @ {}:{}-{} ({})", - record.variation_type, - &chrom_range.chromosome, - record.start.separate_with_commas(), - record.stop.separate_with_commas(), - record.pathogenicity - ), - }) - .collect::>(); - Ok(Json(clinvar_entries)) -} - -#[derive(Serialize)] -struct BgdbResponseRecord { - pub chromosome: String, - pub begin: i32, - pub end: i32, - pub sv_type: SvType, - pub count: u32, - pub name: String, -} - -/// List the overlapping background database entries. -#[get("/public/svs/bgdb/{release}/{database}/")] -async fn fetch_bgdb_records( - data: Data, - path: Path<(String, String)>, - query: web::Query, -) -> actix_web::Result { - let genome_release = - GenomeRelease::from_str(&path.0).map_err(|e| CustomError::new(e.into()))?; - let database = BgDbType::from_str(&path.1).map_err(|e| CustomError::new(e.into()))?; - let chrom_range = ChromRange { - chromosome: query.chromosome.clone(), - begin: query.begin as i32, - end: query.end as i32, - }; - let records: Vec<_> = data.dbs[genome_release] - .bg_dbs - .fetch_records(&chrom_range, &data.chrom_map, database) - .into_iter() - .map(|record| BgdbResponseRecord { - chromosome: query.chromosome.clone(), - begin: record.begin, - end: record.end, - sv_type: record.sv_type, - count: record.count, - name: format!( - "{:?} @ {}:{}-{} (carriers: {})", - record.sv_type, - &query.chromosome, - (record.begin + 1).separate_with_commas(), - record.end.separate_with_commas(), - record.count - ), - }) - .collect(); - Ok(Json(records)) -} - -#[actix_web::main] -pub async fn main(args: &Args, dbs: Data) -> std::io::Result<()> { - let tracks_dir = std::path::Path::new(&args.path_db.clone()) - .join("public") - .join("tracks"); - - HttpServer::new(move || { - let app = App::new() - .app_data(dbs.clone()) - .service(fetch_tads) - .service(fetch_pathogenic) - .service(fetch_clinvar_sv) - .service(fetch_bgdb_records); - let app = app.service(actix_files::Files::new("/public/tracks", &tracks_dir)); - app.wrap(Logger::default()) - }) - .bind((args.listen_host.as_str(), args.listen_port))? - .run() - .await -} diff --git a/src/server/rest/mod.rs b/src/server/rest/mod.rs deleted file mode 100644 index 6c22ebeb..00000000 --- a/src/server/rest/mod.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! Code supporting the `server rest` sub command. - -use std::{collections::HashMap, path::PathBuf, str::FromStr, time::Instant}; - -use actix_web::web::Data; -use clap::Parser; -use enum_map::{enum_map, EnumMap}; -use tracing::info; - -use crate::{ - common::{build_chrom_map, trace_rss_now}, - db::conf::{GenomeRelease, Top}, - sv::query::{ - bgdbs::load_bg_dbs, clinvar::load_clinvar_sv, genes::load_gene_db, masked::load_masked_dbs, - pathogenic::load_patho_dbs, tads::load_tads, Databases, - }, -}; - -pub mod actix_server; - -pub struct WebServerData { - pub chrom_map: HashMap, - pub dbs: EnumMap, -} - -/// Command line arguments for `server rest` sub command. -#[derive(Parser, Debug)] -#[command(author, version, about = "Run REST API server", long_about = None)] -pub struct Args { - /// Path to database to use for querying. - #[arg(long, required = true)] - pub path_db: String, - /// Path to configuration file, defaults to `${path_db}/conf.toml`. - #[arg(long)] - pub path_conf: Option, - /// IP to listen on. - #[arg(long, default_value = "127.0.0.1")] - pub listen_host: String, - /// Port to listen on. - #[arg(long, default_value_t = 8081)] - pub listen_port: u16, -} - -/// Main entry point for `server rest` sub command. -pub fn run(args_common: &crate::common::Args, args: &Args) -> Result<(), anyhow::Error> { - info!("args_common = {:?}", &args_common); - info!("args = {:?}", &args); - - if let Some(level) = args_common.verbose.log_level() { - match level { - log::Level::Trace | log::Level::Debug => { - std::env::set_var("RUST_LOG", "debug"); - env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - } - _ => (), - } - } - - info!("Loading database config..."); - let db_conf: Top = { - let path_conf = if let Some(path_conf) = &args.path_conf { - PathBuf::from_str(path_conf)? - } else { - PathBuf::from_str(&args.path_db)?.join("conf.toml") - }; - let toml_str = std::fs::read_to_string(&path_conf)?; - toml::from_str(&toml_str)? - }; - - info!("Loading databases..."); - let before_loading = Instant::now(); - let dbs = enum_map! { - GenomeRelease::Grch37 => Databases { - bg_dbs: load_bg_dbs(&args.path_db, &db_conf.vardbs[GenomeRelease::Grch37].strucvar)?, - patho_dbs: load_patho_dbs(&args.path_db, &db_conf.vardbs[GenomeRelease::Grch37].strucvar)?, - tad_sets: load_tads(&args.path_db, &db_conf.features[GenomeRelease::Grch37])?, - masked: load_masked_dbs(&args.path_db, &db_conf.features[GenomeRelease::Grch37])?, - genes: load_gene_db(&args.path_db, &db_conf.genes, &db_conf.features[GenomeRelease::Grch37])?, - clinvar_sv: load_clinvar_sv(&args.path_db, &db_conf.vardbs[GenomeRelease::Grch37].strucvar)?, - }, - GenomeRelease::Grch38 => Default::default(), - }; - info!( - "...done loading databases in {:?}", - before_loading.elapsed() - ); - - let data = Data::new(WebServerData { - chrom_map: build_chrom_map(), - dbs, - }); - - trace_rss_now(); - - info!("Launching server ..."); - actix_server::main(args, data)?; - - info!("All done. Have a nice day!"); - Ok(()) -}