1use std::io::Cursor;
2
3use anyhow::Result;
4use polars_core::prelude::*;
5use polars_io::prelude::*;
6use reqwest::blocking::Client;
7use serde_json::Value;
8
9use crate::cli::{
10 StringFunctionalAnnotationArgs, StringFunctionalEnrichmentArgs, StringHomologyArgs,
11 StringInteractionsArgs, StringMappingArgs, StringNetworkArgs, StringPpiEnrichmentArgs,
12};
13
14fn string_api(url_extension: &str, data: &Value) -> Result<DataFrame> {
15 let url = format!("https://string-db.org/api/json/{url_extension}");
16 let response = Client::new().post(&url).form(data).send()?.text()?;
17 let file = Cursor::new(response);
18 let table = JsonReader::new(file).finish()?;
19 Ok(table)
20}
21
22fn string_api_tsv(url_extension: &str, data: &Value) -> Result<DataFrame> {
23 let url = format!("https://string-db.org/api/tsv/{url_extension}");
24 let response = Client::new().post(&url).form(data).send()?.text()?;
25 let file = Cursor::new(response);
26 let table = CsvReadOptions::default()
27 .with_parse_options(CsvParseOptions::default().with_separator(b'\t'))
28 .with_has_header(true)
29 .into_reader_with_file_handle(file)
30 .finish()?;
31 Ok(table)
32}
33
34pub fn string_network(args: &StringNetworkArgs) -> Result<DataFrame> {
35 let data = args.build_post();
36 string_api("network", &data)
37}
38
39pub fn string_homology(args: &StringHomologyArgs) -> Result<DataFrame> {
40 let data = args.build_post();
41 string_api("homology", &data)
42}
43
44pub fn string_mapping(args: &StringMappingArgs) -> Result<DataFrame> {
45 let data = args.build_post();
46 string_api("get_string_ids", &data)
47}
48
49pub fn string_interactions(args: &StringInteractionsArgs) -> Result<DataFrame> {
50 let data = args.build_post();
51 string_api("interaction_partners", &data)
52}
53
54pub fn string_enrichment(args: &StringFunctionalEnrichmentArgs) -> Result<DataFrame> {
55 let data = args.build_post();
56 string_api_tsv("enrichment", &data)
57}
58
59pub fn string_annotations(args: &StringFunctionalAnnotationArgs) -> Result<DataFrame> {
60 let data = args.build_post();
61 string_api_tsv("functional_annotation", &data)
62}
63
64pub fn string_ppi_enrichment(args: &StringPpiEnrichmentArgs) -> Result<DataFrame> {
65 let data = args.build_post();
66 string_api("ppi_enrichment", &data)
67}
68
69#[cfg(test)]
70mod testing {
71 use super::*;
72
73 fn identifiers() -> Vec<String> {
74 vec!["RFX3".to_string(), "RFX2".to_string()]
75 }
76
77 #[test]
78 fn test_string_network() -> Result<()> {
79 let args = StringNetworkArgs::builder()
80 .identifiers(identifiers())
81 .build();
82 let network = string_network(&args)?;
83 let expected_column_names = vec![
84 "stringId_A",
85 "stringId_B",
86 "preferredName_A",
87 "preferredName_B",
88 "ncbiTaxonId",
89 "score",
90 "nscore",
91 "fscore",
92 "pscore",
93 "ascore",
94 "escore",
95 "dscore",
96 "tscore",
97 ];
98 assert_eq!(network.get_column_names(), expected_column_names);
99 Ok(())
100 }
101
102 #[test]
103 fn test_string_homology() -> Result<()> {
104 let args = StringHomologyArgs::builder()
105 .identifiers(identifiers())
106 .build();
107 let homology = string_homology(&args)?;
108 let expected_column_names = vec![
109 "ncbiTaxonId_A",
110 "stringId_A",
111 "ncbiTaxonId_B",
112 "stringId_B",
113 "bitscore",
114 ];
115 assert_eq!(homology.get_column_names(), expected_column_names);
116 Ok(())
117 }
118
119 #[test]
120 fn test_string_map_ids() -> Result<()> {
121 let args = StringMappingArgs::builder()
122 .identifiers(identifiers())
123 .build();
124 let mapping = string_mapping(&args)?;
125 let expected_column_names = vec![
126 "queryIndex",
127 "queryItem",
128 "stringId",
129 "ncbiTaxonId",
130 "taxonName",
131 "preferredName",
132 "annotation",
133 ];
134 assert_eq!(mapping.get_column_names(), expected_column_names);
135 Ok(())
136 }
137
138 #[test]
139 fn test_string_interactions() -> Result<()> {
140 let args = StringInteractionsArgs::builder()
141 .identifiers(identifiers())
142 .build();
143 let interactions = string_interactions(&args)?;
144 let expected_column_names = vec![
145 "stringId_A",
146 "stringId_B",
147 "preferredName_A",
148 "preferredName_B",
149 "ncbiTaxonId",
150 "score",
151 "nscore",
152 "fscore",
153 "pscore",
154 "ascore",
155 "escore",
156 "dscore",
157 "tscore",
158 ];
159 assert_eq!(interactions.get_column_names(), expected_column_names);
160 Ok(())
161 }
162
163 #[test]
164 fn test_string_enrichment() -> Result<()> {
165 let args = StringFunctionalEnrichmentArgs::builder()
166 .identifiers(identifiers())
167 .build();
168 let enrichment = string_enrichment(&args)?;
169 let expected_column_names = vec![
170 "category",
171 "term",
172 "number_of_genes",
173 "number_of_genes_in_background",
174 "ncbiTaxonId",
175 "inputGenes",
176 "preferredNames",
177 "p_value",
178 "fdr",
179 "description",
180 ];
181 assert_eq!(enrichment.get_column_names(), expected_column_names);
182 Ok(())
183 }
184
185 #[test]
186 fn test_string_annotations() -> Result<()> {
187 let args = StringFunctionalAnnotationArgs::builder()
188 .identifiers(identifiers())
189 .build();
190 let annotations = string_annotations(&args)?;
191 let expected_column_names = vec![
192 "category",
193 "term",
194 "number_of_genes",
195 "ratio_in_set",
196 "ncbiTaxonId",
197 "inputGenes",
198 "preferredNames",
199 "description",
200 ];
201 assert_eq!(annotations.get_column_names(), expected_column_names);
202 Ok(())
203 }
204
205 #[test]
206 fn test_string_ppi_enrichment() -> Result<()> {
207 let args = StringPpiEnrichmentArgs::builder()
208 .identifiers(identifiers())
209 .build();
210 let enrichment = string_ppi_enrichment(&args)?;
211 let expected_column_names = vec![
212 "number_of_nodes",
213 "number_of_edges",
214 "average_node_degree",
215 "local_clustering_coefficient",
216 "expected_number_of_edges",
217 "p_value",
218 ];
219 assert_eq!(enrichment.get_column_names(), expected_column_names);
220 Ok(())
221 }
222}