From 88ac0287e2d520e46b59589e7fa1a694e707b4fd Mon Sep 17 00:00:00 2001 From: Anicet Ebou Date: Sun, 28 Apr 2024 20:05:24 +0000 Subject: [PATCH] refactor: optimize some functions --- README.md | 10 +-- src/api/taxon_api.rs | 10 +-- src/cmd/genome.rs | 200 ++++++++++++++++--------------------------- src/cmd/search.rs | 139 ++++++++++-------------------- src/cmd/taxon.rs | 115 ++++++++----------------- src/cmd/utils.rs | 35 ++++---- 6 files changed, 173 insertions(+), 336 deletions(-) diff --git a/README.md b/README.md index e61d50d..2c4051e 100644 --- a/README.md +++ b/README.md @@ -61,19 +61,19 @@ Full help is available from `xgt --help`. `xgt` through `ureq` performs peer SSL certificate verification by default. To tell `xgt` to _not_ verify the peer, use `-k/--insecure` option. -Currently (as of Apr 28, 2024), you should add this options to your command to get the desired result as the GTDB API have a certificate issue. +Currently (as of Apr 28, 2024), you should add this options to your command to get the desired result as GTDB API's server have currently a certificate issue. -### Minimum supported Rust version +## Minimum supported Rust version `xgt` minimum [Rust](https://www.rust-lang.org/) version is 1.70.0. -### Semver +## Semver `xgt` is following [Semantic Versioning 2.0](https://semver.org/). -### Licence +## Licence `xgt` is distributed under the terms of both the MIT license and the Apache License (Version 2.0). See [LICENSE-APACHE](https://github.com/Ebedthan/xgt/blob/main/LICENSE-APACHE) and [LICENSE-MIT](https://github.com/Ebedthan/xgt/blob/main/LICENSE-MIT) for details. -### Note +## Note Unstable work is on dev branch. \ No newline at end of file diff --git a/src/api/taxon_api.rs b/src/api/taxon_api.rs index 05a5520..4fd4285 100644 --- a/src/api/taxon_api.rs +++ b/src/api/taxon_api.rs @@ -10,9 +10,7 @@ impl TaxonAPI { pub fn get_name_request(&self) -> String { format!("https://api.gtdb.ecogenomic.org/taxon/{}", self.name) } - pub fn get_name(&self) -> String { - self.name.clone() - } + pub fn get_search_request(&self) -> String { format!( "https://api.gtdb.ecogenomic.org/taxon/search/{}?limit=1000000", @@ -50,12 +48,6 @@ mod test { assert_eq!(taxon_api.get_name_request(), expected); } - #[test] - fn test_get_name() { - let taxon_api = TaxonAPI::from("s__Escherichia coli".to_string()); - assert_eq!(taxon_api.get_name(), "s__Escherichia coli"); - } - #[test] fn test_get_search_request() { let taxon_api = TaxonAPI::from("s__Escherichia coli".to_string()); diff --git a/src/cmd/genome.rs b/src/cmd/genome.rs index a95f37c..5b9d886 100644 --- a/src/cmd/genome.rs +++ b/src/cmd/genome.rs @@ -3,7 +3,8 @@ use super::utils::GenomeArgs; use crate::api::genome_api::GenomeAPI; use crate::api::genome_api::GenomeRequestType; -use anyhow::{bail, Context, Result}; +use anyhow::anyhow; +use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use std::fs::OpenOptions; use std::io::{self, Write}; @@ -185,52 +186,33 @@ pub fn get_genome_metadata(args: GenomeArgs) -> Result<()> { for accession in genome_api { let request_url = accession.request(GenomeRequestType::Metadata); - let response = match agent.get(&request_url).call() { - Ok(r) => r, - Err(ureq::Error::Status(code, _)) => { - bail!("The server returned an unexpected status code ({})", code); + let response = agent.get(&request_url).call().map_err(|e| match e { + ureq::Error::Status(code, _) => { + anyhow!("The server returned an unexpected status code ({})", code) } - Err(_) => { - bail!("There was an error making the request or receiving the response."); - } - }; + _ => anyhow!("There was an error making the request or receiving the response."), + })?; - let genome: GenomeMetadata = response.into_json()?; - - match raw { - true => { - let genome_string = serde_json::to_string(&genome)?; - let output = args.get_output(); - if let Some(path) = output { - let path_clone = path.clone(); - let mut file = OpenOptions::new() - .append(true) - .create(true) - .open(path) - .with_context(|| format!("Failed to create file {path_clone}"))?; - file.write_all(genome_string.as_bytes()) - .with_context(|| format!("Failed to write to {path_clone}"))?; - } else { - writeln!(io::stdout(), "{genome_string}")?; - } - } - false => { - let genome_string = serde_json::to_string_pretty(&genome)?; - let output = args.get_output(); - if let Some(path) = output { - let path_clone = path.clone(); - let mut file = OpenOptions::new() - .append(true) - .create(true) - .open(path) - .with_context(|| format!("Failed to create file {path_clone}"))?; - file.write_all(genome_string.as_bytes()) - .with_context(|| format!("Failed to write to {path_clone}"))?; - } else { - writeln!(io::stdout(), "{genome_string}")?; - } - } + let genome_card: GenomeMetadata = response.into_json()?; + + let genome_string = if raw { + serde_json::to_string(&genome_card)? + } else { + serde_json::to_string_pretty(&genome_card)? }; + + let output = args.get_output(); + if let Some(path) = output { + let mut file = OpenOptions::new() + .append(true) + .create(true) + .open(&path) + .with_context(|| format!("Failed to create file {}", path))?; + writeln!(file, "{}", genome_string) + .with_context(|| format!("Failed to write to {}", path))?; + } else { + writeln!(io::stdout(), "{}", genome_string)?; + } } Ok(()) @@ -249,52 +231,33 @@ pub fn get_genome_card(args: GenomeArgs) -> Result<()> { for accession in genome_api { let request_url = accession.request(GenomeRequestType::Card); - let response = match agent.get(&request_url).call() { - Ok(r) => r, - Err(ureq::Error::Status(code, _)) => { - bail!("The server returned an unexpected status code ({})", code); - } - Err(_) => { - bail!("There was an error making the request or receiving the response"); + let response = agent.get(&request_url).call().map_err(|e| match e { + ureq::Error::Status(code, _) => { + anyhow!("The server returned an unexpected status code ({})", code) } - }; + _ => anyhow!("There was an error making the request or receiving the response."), + })?; - let genome: GenomeCard = response.into_json()?; - - match raw { - true => { - let genome_string = serde_json::to_string(&genome)?; - let output = args.get_output(); - if let Some(path) = output { - let path_clone = path.clone(); - let mut file = OpenOptions::new() - .append(true) - .create(true) - .open(path) - .with_context(|| format!("Failed to create file {path_clone}"))?; - file.write_all(genome_string.as_bytes()) - .with_context(|| format!("Failed to write to {path_clone}"))?; - } else { - writeln!(io::stdout(), "{genome_string}")?; - } - } - false => { - let genome_string = serde_json::to_string_pretty(&genome)?; - let output = args.get_output(); - if let Some(path) = output { - let path_clone = path.clone(); - let mut file = OpenOptions::new() - .append(true) - .create(true) - .open(path) - .with_context(|| format!("Failed to create file {path_clone}"))?; - file.write_all(genome_string.as_bytes()) - .with_context(|| format!("Failed to write to {path_clone}"))?; - } else { - writeln!(io::stdout(), "{genome_string}")?; - } - } + let genome_card: GenomeCard = response.into_json()?; + + let genome_string = if raw { + serde_json::to_string(&genome_card)? + } else { + serde_json::to_string_pretty(&genome_card)? }; + + let output = args.get_output(); + if let Some(path) = output { + let mut file = OpenOptions::new() + .append(true) + .create(true) + .open(&path) + .with_context(|| format!("Failed to create file {}", path))?; + writeln!(file, "{}", genome_string) + .with_context(|| format!("Failed to write to {}", path))?; + } else { + writeln!(io::stdout(), "{}", genome_string)?; + } } Ok(()) @@ -313,52 +276,33 @@ pub fn get_genome_taxon_history(args: GenomeArgs) -> Result<()> { for accession in genome_api { let request_url = accession.request(GenomeRequestType::TaxonHistory); - let response = match agent.get(&request_url).call() { - Ok(r) => r, - Err(ureq::Error::Status(code, _)) => { - bail!("The server returned an unexpected status code ({})", code); - } - Err(_) => { - bail!("There was an error making the request or receiving the response."); + let response = agent.get(&request_url).call().map_err(|e| match e { + ureq::Error::Status(code, _) => { + anyhow!("The server returned an unexpected status code ({})", code) } - }; + _ => anyhow!("There was an error making the request or receiving the response."), + })?; let genome: GenomeTaxonHistory = response.into_json()?; - match raw { - true => { - let genome_string = serde_json::to_string(&genome)?; - let output = args.get_output(); - if let Some(path) = output { - let path_clone = path.clone(); - let mut file = OpenOptions::new() - .append(true) - .create(true) - .open(path) - .with_context(|| format!("Failed to create file {path_clone}"))?; - file.write_all(genome_string.as_bytes()) - .with_context(|| format!("Failed to write to {path_clone}"))?; - } else { - writeln!(io::stdout(), "{genome_string}")?; - } - } - false => { - let genome_string = serde_json::to_string_pretty(&genome)?; - let output = args.get_output(); - if let Some(path) = output { - let path_clone = path.clone(); - let mut file = OpenOptions::new() - .append(true) - .create(true) - .open(path) - .with_context(|| format!("Failed to create file {path_clone}"))?; - file.write_all(genome_string.as_bytes()) - .with_context(|| format!("Failed to write to {path_clone}"))?; - } else { - writeln!(io::stdout(), "{genome_string}")?; - } - } + let genome_string = if raw { + serde_json::to_string(&genome)? + } else { + serde_json::to_string_pretty(&genome)? }; + + let output = args.get_output(); + if let Some(path) = output { + let mut file = OpenOptions::new() + .append(true) + .create(true) + .open(&path) + .with_context(|| format!("Failed to create file {}", path))?; + writeln!(file, "{}", genome_string) + .with_context(|| format!("Failed to write to {}", path))?; + } else { + writeln!(io::stdout(), "{}", genome_string)?; + } } Ok(()) diff --git a/src/cmd/search.rs b/src/cmd/search.rs index 5b680f4..4234c73 100644 --- a/src/cmd/search.rs +++ b/src/cmd/search.rs @@ -64,31 +64,22 @@ impl SearchResults { fn get_total_rows(&self) -> u32 { self.total_rows } - fn get_rows(&self) -> Vec { - self.rows.clone() + fn get_rows(&self) -> &[SearchResult] { + &self.rows } } pub fn partial_search(args: utils::SearchArgs) -> Result<()> { - // get args - let gid = args.get_gid(); - let count = args.get_count(); - let raw = args.get_raw(); - let output = args.get_out(); - - let needles = args.get_needle(); - let agent: Agent = utils::get_agent(args.get_disable_certificate_verification())?; - for needle in needles { + for needle in args.get_needle() { let search_api = SearchAPI::from(&needle, &args); - let request_url = search_api.request(); let response = match agent.get(&request_url).call() { Ok(r) => r, Err(ureq::Error::Status(code, _)) => { - bail!("The server returned an unexpected status code ({})", code); + bail!("The server returned an unexpected status code ({})", code) } Err(_) => { bail!("There was an error making the request or receiving the response."); @@ -96,48 +87,32 @@ pub fn partial_search(args: utils::SearchArgs) -> Result<()> { }; let search_result: SearchResults = response.into_json()?; - let search_result_list = search_result.get_rows(); ensure!( search_result.get_total_rows() != 0, "No matching data found in GTDB" ); - // Return number of genomes? - match count { - true => { - utils::write_to_output( - format!("{}{}", &search_result.get_total_rows(), "\n"), - output.clone(), - )?; + if args.get_count() { + utils::write_to_output( + format!("{}\n", search_result.get_total_rows()), + args.get_out().clone(), + )?; + } else if args.get_gid() { + let list: Vec = search_result_list.iter().map(|x| x.gid.clone()).collect(); + for gid in list { + utils::write_to_output(format!("{}\n", gid), args.get_out().clone())?; + } + } else if args.get_raw() { + for result in search_result_list { + let genome_string = serde_json::to_string(&result)?; + utils::write_to_output(genome_string, args.get_out().clone())?; + } + } else { + for result in search_result_list { + let genome_string = serde_json::to_string_pretty(&result)?; + utils::write_to_output(genome_string, args.get_out().clone())?; } - - // Return only genome id? - false => match gid { - true => { - let list: Vec = - search_result_list.iter().map(|x| x.gid.clone()).collect(); - - for gid in list { - utils::write_to_output(format!("{}{}", gid, "\n"), output.clone())?; - } - } - // Return all data in pretty print json? - false => match raw { - true => { - for result in search_result_list { - let genome_string = serde_json::to_string(&result)?; - utils::write_to_output(genome_string, output.clone())?; - } - } - false => { - for result in search_result_list { - let genome_string = serde_json::to_string_pretty(&result)?; - utils::write_to_output(genome_string, output.clone())?; - } - } - }, - }, } } @@ -145,20 +120,10 @@ pub fn partial_search(args: utils::SearchArgs) -> Result<()> { } pub fn exact_search(args: utils::SearchArgs) -> Result<()> { - // get args - let gid = args.get_gid(); - let count = args.get_count(); - let raw = args.get_raw(); - let output = args.get_out(); - - let needles = args.get_needle(); - let agent: Agent = utils::get_agent(args.get_disable_certificate_verification())?; - for needle in needles { - let oneedle = needle.clone(); - let search_api = SearchAPI::from(&oneedle, &args); - + for needle in args.get_needle() { + let search_api = SearchAPI::from(&needle, &args); let request_url = search_api.request(); let response = match agent.get(&request_url).call() { @@ -173,47 +138,31 @@ pub fn exact_search(args: utils::SearchArgs) -> Result<()> { let mut search_result: SearchResults = response.into_json()?; search_result.retain(&args.get_level(), &needle); + ensure!( search_result.get_total_rows() != 0, "No matching data found in GTDB" ); - // Return number of genomes? - match count { - true => { - utils::write_to_output( - format!("{}{}", search_result.get_total_rows(), "\n"), - output.clone(), - )?; + if args.get_count() { + utils::write_to_output( + format!("{}\n", search_result.get_total_rows()), + args.get_out().clone(), + )?; + } else if args.get_gid() { + let list: Vec = search_result.rows.iter().map(|x| x.gid.clone()).collect(); + for gid in list { + utils::write_to_output(format!("{}\n", gid), args.get_out().clone())?; } - - // Return only genome id? - false => { - match gid { - true => { - let list: Vec = - search_result.rows.iter().map(|x| x.gid.clone()).collect(); - - for gid in list { - utils::write_to_output(format!("{}{}", gid, "\n"), output.clone())?; - } - } - // Return all data in pretty print json? - false => match raw { - true => { - for result in search_result.rows { - let genome_string = serde_json::to_string(&result)?; - utils::write_to_output(genome_string, output.clone())?; - } - } - false => { - for result in search_result.rows { - let genome_string = serde_json::to_string_pretty(&result)?; - utils::write_to_output(genome_string, output.clone())?; - } - } - }, - } + } else if args.get_raw() { + for result in &search_result.rows { + let genome_string = serde_json::to_string(result)?; + utils::write_to_output(genome_string, args.get_out().clone())?; + } + } else { + for result in &search_result.rows { + let genome_string = serde_json::to_string_pretty(result)?; + utils::write_to_output(genome_string, args.get_out().clone())?; } } } diff --git a/src/cmd/taxon.rs b/src/cmd/taxon.rs index b6db217..e29bb93 100644 --- a/src/cmd/taxon.rs +++ b/src/cmd/taxon.rs @@ -59,94 +59,65 @@ impl TaxonSearchResult { } pub fn get_taxon_name(args: TaxonArgs) -> Result<()> { - // format the request - let taxon_api: Vec = args - .get_name() - .iter() - .map(|x| TaxonAPI::from(x.to_string())) - .collect(); let raw = args.get_raw(); - let agent: Agent = utils::get_agent(args.get_disable_certificate_verification())?; - for name in taxon_api { - let request_url = name.get_name_request(); - + for name in args.get_name() { + let request_url = TaxonAPI::from(name.to_string()).get_name_request(); let response = match agent.get(&request_url).call() { Ok(r) => r, - Err(ureq::Error::Status(400, _)) => { - bail!("Taxon {} not found", name.get_name()); - } - Err(ureq::Error::Status(code, _)) => { - bail!("The server returned an unexpected status code ({})", code); - } - Err(_) => { - bail!("There was an error making the request or receiving the response."); - } + Err(ureq::Error::Status(400, _)) => bail!("Taxon {} not found", name), + Err(ureq::Error::Status(code, _)) => bail!("Unexpected status code: {}", code), + Err(_) => bail!("Error making the request or receiving the response."), }; let taxon_data: TaxonResult = response.into_json()?; - - match raw { - true => { - let taxon_string = serde_json::to_string(&taxon_data)?; - utils::write_to_output(taxon_string, args.get_output())?; - } - false => { - let taxon_string = serde_json::to_string_pretty(&taxon_data)?; - utils::write_to_output(taxon_string, args.get_output())?; - } + let taxon_string = if raw { + serde_json::to_string(&taxon_data)? + } else { + serde_json::to_string_pretty(&taxon_data)? }; + utils::write_to_output(taxon_string, args.get_output())?; } Ok(()) } pub fn search_taxon(args: TaxonArgs) -> Result<()> { - let taxon_api: Vec = args - .get_name() - .iter() - .map(|x| TaxonAPI::from(x.to_string())) - .collect(); let raw = args.get_raw(); let partial = args.get_partial(); - let agent: Agent = utils::get_agent(args.get_disable_certificate_verification())?; - for search in taxon_api { - let request_url: String = if args.is_search_all() { - search.get_search_all_request() + for name in args.get_name() { + let search_api = TaxonAPI::from(name.to_string()); + let request_url = if args.is_search_all() { + search_api.get_search_all_request() } else { - search.get_search_request() + search_api.get_search_request() }; let response = match agent.get(&request_url).call() { Ok(r) => r, - Err(ureq::Error::Status(400, _)) => { - bail!("No match found for {}", search.get_name()); - } - Err(ureq::Error::Status(code, _)) => { - bail!("The server returned an unexpected status code ({})", code); - } - Err(_) => { - bail!("There was an error making the request or receiving the response."); - } + Err(ureq::Error::Status(400, _)) => bail!("No match found for {}", name), + Err(ureq::Error::Status(code, _)) => bail!("Unexpected status code: {}", code), + Err(_) => bail!("Error making the request or receiving the response."), }; let mut taxon_data: TaxonSearchResult = response.into_json()?; if !partial { - taxon_data.filter(search.get_name()); + taxon_data.filter(name.to_string()); } ensure!( !taxon_data.matches.is_empty(), "No match found for {}", - search.get_name() + name ); - let taxon_string: String = match raw { - true => serde_json::to_string(&taxon_data)?, - false => serde_json::to_string_pretty(&taxon_data)?, + let taxon_string = if raw { + serde_json::to_string(&taxon_data)? + } else { + serde_json::to_string_pretty(&taxon_data)? }; utils::write_to_output(format!("{}{}", taxon_string, "\n"), args.get_output())?; @@ -156,46 +127,32 @@ pub fn search_taxon(args: TaxonArgs) -> Result<()> { } pub fn get_taxon_genomes(args: TaxonArgs) -> Result<()> { - let taxon_api: Vec = args - .get_name() - .iter() - .map(|x| TaxonAPI::from(x.to_string())) - .collect(); let raw = args.get_raw(); let sp_reps_only = args.is_reps_only(); - let agent: Agent = utils::get_agent(args.get_disable_certificate_verification())?; - for search in taxon_api { - let request_url = search.get_genomes_request(sp_reps_only); + for name in args.get_name() { + let search_api = TaxonAPI::from(name.to_string()); + let request_url = search_api.get_genomes_request(sp_reps_only); let response = match agent.get(&request_url).call() { Ok(r) => r, - Err(ureq::Error::Status(400, _)) => { - bail!("No match found for {}", search.get_name()); - } - Err(ureq::Error::Status(code, _)) => { - bail!("The server returned an unexpected status code ({})", code); - } - Err(_) => { - bail!("There was an error making the request or receiving the response."); - } + Err(ureq::Error::Status(400, _)) => bail!("No match found for {}", name), + Err(ureq::Error::Status(code, _)) => bail!("Unexpected status code: {}", code), + Err(_) => bail!("Error making the request or receiving the response."), }; let taxon_data: TaxonGenomes = response.into_json()?; - ensure!( - !taxon_data.data.is_empty(), - "No data found for {}", - search.get_name() - ); + ensure!(!taxon_data.data.is_empty(), "No data found for {}", name); - let taxon_string: String = match raw { - true => serde_json::to_string(&taxon_data)?, - false => serde_json::to_string_pretty(&taxon_data)?, + let taxon_string = if raw { + serde_json::to_string(&taxon_data)? + } else { + serde_json::to_string_pretty(&taxon_data)? }; - utils::write_to_output(format!("{}{}", taxon_string, "\n"), args.get_output())?; + utils::write_to_output(format!("{}{}\n", taxon_string, "\n"), args.get_output())?; } Ok(()) diff --git a/src/cmd/utils.rs b/src/cmd/utils.rs index eec29ec..d90a9a2 100644 --- a/src/cmd/utils.rs +++ b/src/cmd/utils.rs @@ -182,10 +182,9 @@ impl GenomeArgs { pub fn from_arg_matches(arg_matches: &ArgMatches) -> Self { let mut accession = Vec::new(); - if arg_matches.contains_id("file") { - let file = File::open(arg_matches.get_one::("file").unwrap()) - .expect("file should be well-formatted"); - + if let Some(file_path) = arg_matches.get_one::("file") { + let file = File::open(file_path) + .unwrap_or_else(|_| panic!("Failed to open file: {}", file_path)); accession = BufReader::new(file) .lines() .map(|l| l.expect("Cannot parse line")) @@ -194,7 +193,7 @@ impl GenomeArgs { accession.push( arg_matches .get_one::("accession") - .unwrap() + .unwrap_or_else(|| panic!("Missing accession value")) .to_string(), ); } @@ -202,11 +201,7 @@ impl GenomeArgs { GenomeArgs { accession, raw: arg_matches.get_flag("raw"), - output: if arg_matches.contains_id("out") { - arg_matches.get_one::("out").cloned() - } else { - None - }, + output: arg_matches.get_one::("out").map(String::from), disable_certificate_verification: arg_matches.get_flag("insecure"), } } @@ -265,26 +260,26 @@ impl TaxonArgs { pub fn from_arg_matches(arg_matches: &ArgMatches) -> Self { let mut names = Vec::new(); - if arg_matches.contains_id("file") { - let file = File::open(arg_matches.get_one::("file").unwrap()) - .expect("file should be well-formatted"); - + if let Some(file_path) = arg_matches.get_one::("file") { + let file = File::open(file_path) + .unwrap_or_else(|_| panic!("Failed to open file: {}", file_path)); names = BufReader::new(file) .lines() .map(|l| l.expect("Cannot parse line")) .collect(); } else { - names.push(arg_matches.get_one::("name").unwrap().to_string()); + names.push( + arg_matches + .get_one::("name") + .unwrap_or_else(|| panic!("Missing name value")) + .to_string(), + ); } TaxonArgs { name: names, raw: arg_matches.get_flag("raw"), - output: if arg_matches.contains_id("out") { - arg_matches.get_one::("out").cloned() - } else { - None - }, + output: arg_matches.get_one::("out").map(String::from), partial: arg_matches.get_flag("partial"), search: arg_matches.get_flag("search"), search_all: arg_matches.get_flag("all"),