htsget_http/
post_request.rs

1use htsget_config::types::{Query, Request};
2use serde::{Deserialize, Serialize};
3use tracing::instrument;
4
5use crate::{Endpoint, QueryBuilder, Result, match_format, set_query_builder};
6
7/// A struct to represent a POST request according to the
8/// [HtsGet specification](https://samtools.github.io/hts-specs/htsget.html). It implements
9/// [Deserialize] to make it more ergonomic. Each `PostRequest` can contain several regions.
10#[derive(Serialize, Deserialize, Debug, Default)]
11pub struct PostRequest {
12  pub format: Option<String>,
13  pub class: Option<String>,
14  pub fields: Option<Vec<String>>,
15  pub tags: Option<Vec<String>>,
16  pub notags: Option<Vec<String>>,
17  pub regions: Option<Vec<Region>>,
18  #[serde(rename = "encryptionScheme")]
19  pub encryption_scheme: Option<String>,
20}
21
22/// A struct that contains the data to quest for a specific region. It is only meant to be use
23/// alongside a `PostRequest`
24#[derive(Serialize, Deserialize, Debug)]
25pub struct Region {
26  #[serde(rename = "referenceName")]
27  pub reference_name: String,
28  pub start: Option<u32>,
29  pub end: Option<u32>,
30}
31
32impl PostRequest {
33  /// Converts the `PostRequest` into one or more equivalent [Queries](Query)
34  #[instrument(level = "trace", skip_all, ret)]
35  pub(crate) fn get_queries(self, request: Request, endpoint: &Endpoint) -> Result<Vec<Query>> {
36    let format = match_format(endpoint, self.format.clone())?;
37
38    if let Some(ref regions) = self.regions {
39      regions
40        .iter()
41        .map(|region| {
42          set_query_builder(
43            QueryBuilder::new(request.clone(), format),
44            self.class.clone(),
45            Some(region.reference_name.clone()),
46            Self::join_vec(self.fields.clone()),
47            (
48              Self::join_vec(self.tags.clone()),
49              Self::join_vec(self.notags.clone()),
50            ),
51            (
52              region.start.map(|start| start.to_string()),
53              region.end.map(|end| end.to_string()),
54            ),
55            self.encryption_scheme.clone(),
56          )
57        })
58        .collect::<Result<Vec<Query>>>()
59    } else {
60      Ok(vec![set_query_builder(
61        QueryBuilder::new(request, format),
62        self.class,
63        None::<String>,
64        Self::join_vec(self.fields),
65        (Self::join_vec(self.tags), Self::join_vec(self.notags)),
66        (None::<String>, None::<String>),
67        self.encryption_scheme,
68      )?])
69    }
70  }
71
72  fn join_vec(fields: Option<Vec<String>>) -> Option<String> {
73    fields.map(|fields| fields.join(","))
74  }
75}
76
77#[cfg(test)]
78mod tests {
79  use htsget_config::types::{Class, Format};
80
81  use super::*;
82
83  #[test]
84  fn post_request_without_regions() {
85    let request = Request::new_with_id("id".to_string());
86
87    assert_eq!(
88      PostRequest {
89        format: Some("VCF".to_string()),
90        class: Some("header".to_string()),
91        fields: None,
92        tags: None,
93        notags: None,
94        regions: None,
95        encryption_scheme: None,
96      }
97      .get_queries(request.clone(), &Endpoint::Variants)
98      .unwrap(),
99      vec![Query::new("id", Format::Vcf, request).with_class(Class::Header)]
100    );
101  }
102
103  #[test]
104  fn post_request_with_one_region() {
105    let request = Request::new_with_id("id".to_string());
106
107    assert_eq!(
108      PostRequest {
109        format: Some("VCF".to_string()),
110        class: Some("header".to_string()),
111        fields: None,
112        tags: None,
113        notags: None,
114        regions: Some(vec![Region {
115          reference_name: "20".to_string(),
116          start: Some(150),
117          end: Some(153),
118        }]),
119        encryption_scheme: None,
120      }
121      .get_queries(request.clone(), &Endpoint::Variants)
122      .unwrap(),
123      vec![
124        Query::new("id", Format::Vcf, request)
125          .with_class(Class::Header)
126          .with_reference_name("20".to_string())
127          .with_start(150)
128          .with_end(153)
129      ]
130    );
131  }
132
133  #[test]
134  fn post_request_with_regions() {
135    let request = Request::new_with_id("id".to_string());
136
137    assert_eq!(
138      PostRequest {
139        format: Some("VCF".to_string()),
140        class: Some("header".to_string()),
141        fields: None,
142        tags: None,
143        notags: None,
144        regions: Some(vec![
145          Region {
146            reference_name: "20".to_string(),
147            start: Some(150),
148            end: Some(153),
149          },
150          Region {
151            reference_name: "11".to_string(),
152            start: Some(152),
153            end: Some(154),
154          }
155        ]),
156        encryption_scheme: None,
157      }
158      .get_queries(request.clone(), &Endpoint::Variants)
159      .unwrap(),
160      vec![
161        Query::new("id", Format::Vcf, request.clone())
162          .with_class(Class::Header)
163          .with_reference_name("20".to_string())
164          .with_start(150)
165          .with_end(153),
166        Query::new("id", Format::Vcf, request)
167          .with_class(Class::Header)
168          .with_reference_name("11".to_string())
169          .with_start(152)
170          .with_end(154)
171      ]
172    );
173  }
174}