htsget_config/
types.rs

1//! Types related to htsget like formats, reference names, classes or intervals.
2//!
3
4use std::collections::{HashMap, HashSet};
5use std::fmt::{Debug, Display, Formatter};
6use std::io::ErrorKind::Other;
7use std::{fmt, io, result};
8
9#[cfg(feature = "experimental")]
10use crate::encryption_scheme::EncryptionScheme;
11use crate::error::Error;
12use crate::error::Error::ParseError;
13use http::HeaderMap;
14use noodles::core::region::Interval as NoodlesInterval;
15use noodles::core::Position;
16use serde::{Deserialize, Serialize};
17use thiserror::Error;
18use tracing::instrument;
19
20/// The result type returning a `HtsGetError`.
21pub type Result<T> = result::Result<T, HtsGetError>;
22
23/// An enumeration with all the possible formats.
24#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
25#[serde(rename_all(serialize = "UPPERCASE"), deny_unknown_fields)]
26pub enum Format {
27  #[default]
28  #[serde(alias = "bam", alias = "BAM")]
29  Bam,
30  #[serde(alias = "cram", alias = "CRAM")]
31  Cram,
32  #[serde(alias = "vcf", alias = "VCF")]
33  Vcf,
34  #[serde(alias = "bcf", alias = "BCF")]
35  Bcf,
36}
37
38/// Todo allow these to be configurable.
39impl Format {
40  /// Get the file ending for the format.
41  pub fn file_ending(&self) -> &str {
42    match self {
43      Format::Bam => ".bam",
44      Format::Cram => ".cram",
45      Format::Vcf => ".vcf.gz",
46      Format::Bcf => ".bcf",
47    }
48  }
49
50  /// Get the file name including its ending.
51  pub fn fmt_file(&self, id: &str) -> String {
52    format!("{id}{}", self.file_ending())
53  }
54
55  /// Get the index file ending for this format.
56  pub fn index_file_ending(&self) -> &str {
57    match self {
58      Format::Bam => ".bam.bai",
59      Format::Cram => ".cram.crai",
60      Format::Vcf => ".vcf.gz.tbi",
61      Format::Bcf => ".bcf.csi",
62    }
63  }
64
65  /// Get the index file name including its ending.
66  pub fn fmt_index(&self, id: &str) -> String {
67    format!("{id}{}", self.index_file_ending())
68  }
69
70  /// Get the GZI index file ending for this format.
71  pub fn gzi_index_file_ending(&self) -> io::Result<&str> {
72    match self {
73      Format::Bam => Ok(".bam.gzi"),
74      Format::Cram => Err(io::Error::new(
75        Other,
76        "CRAM does not support GZI".to_string(),
77      )),
78      Format::Vcf => Ok(".vcf.gz.gzi"),
79      Format::Bcf => Ok(".bcf.gzi"),
80    }
81  }
82
83  /// Get the GZI index file name including its ending.
84  pub fn fmt_gzi(&self, id: &str) -> io::Result<String> {
85    Ok(format!("{id}{}", self.gzi_index_file_ending()?))
86  }
87
88  /// Check if the id points at an index file.
89  pub fn is_index(id: &str) -> bool {
90    id.ends_with(".bai")
91      || id.ends_with(".crai")
92      || id.ends_with(".tbi")
93      || id.ends_with(".csi")
94      || id.ends_with(".gzi")
95  }
96}
97
98impl From<Format> for String {
99  fn from(format: Format) -> Self {
100    format.to_string()
101  }
102}
103
104impl Display for Format {
105  fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
106    match self {
107      Format::Bam => write!(f, "BAM"),
108      Format::Cram => write!(f, "CRAM"),
109      Format::Vcf => write!(f, "VCF"),
110      Format::Bcf => write!(f, "BCF"),
111    }
112  }
113}
114
115/// Class component of htsget response.
116#[derive(Copy, Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
117#[serde(rename_all(serialize = "lowercase"), deny_unknown_fields)]
118pub enum Class {
119  #[serde(alias = "header", alias = "HEADER")]
120  Header,
121  #[default]
122  #[serde(alias = "body", alias = "BODY")]
123  Body,
124}
125
126/// An interval represents the start (0-based, inclusive) and end (0-based exclusive) ranges of the
127/// query.
128#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
129#[serde(deny_unknown_fields)]
130pub struct Interval {
131  start: Option<u32>,
132  end: Option<u32>,
133}
134
135impl Interval {
136  /// Check if this interval contains the value.
137  pub fn contains(&self, value: u32) -> bool {
138    match (self.start.as_ref(), self.end.as_ref()) {
139      (None, None) => true,
140      (None, Some(end)) => value < *end,
141      (Some(start), None) => value >= *start,
142      (Some(start), Some(end)) => value >= *start && value < *end,
143    }
144  }
145
146  /// Convert this interval into a one-based noodles `Interval`.
147  #[instrument(level = "trace", skip_all, ret)]
148  pub fn into_one_based(self) -> io::Result<NoodlesInterval> {
149    Ok(match (self.start, self.end) {
150      (None, None) => NoodlesInterval::from(..),
151      (None, Some(end)) => NoodlesInterval::from(..=Self::convert_end(end)?),
152      (Some(start), None) => NoodlesInterval::from(Self::convert_start(start)?..),
153      (Some(start), Some(end)) => {
154        NoodlesInterval::from(Self::convert_start(start)?..=Self::convert_end(end)?)
155      }
156    })
157  }
158
159  /// Convert a start position to a noodles Position.
160  pub fn convert_start(start: u32) -> io::Result<Position> {
161    Self::convert_position(start, |value| {
162      value.checked_add(1).ok_or_else(|| {
163        io::Error::new(
164          Other,
165          format!("could not convert {value} to 1-based position."),
166        )
167      })
168    })
169  }
170
171  /// Convert an end position to a noodles Position.
172  pub fn convert_end(end: u32) -> io::Result<Position> {
173    Self::convert_position(end, Ok)
174  }
175
176  /// Convert a u32 position to a noodles Position.
177  pub fn convert_position<F>(value: u32, convert_fn: F) -> io::Result<Position>
178  where
179    F: FnOnce(u32) -> io::Result<u32>,
180  {
181    let value = convert_fn(value).map(|value| {
182      usize::try_from(value)
183        .map_err(|err| io::Error::new(Other, format!("could not convert `u32` to `usize`: {err}")))
184    })??;
185
186    Position::try_from(value).map_err(|err| {
187      io::Error::new(
188        Other,
189        format!("could not convert `{value}` into `Position`: {err}"),
190      )
191    })
192  }
193
194  /// Start position.
195  pub fn start(&self) -> Option<u32> {
196    self.start
197  }
198
199  /// End position.
200  pub fn end(&self) -> Option<u32> {
201    self.end
202  }
203
204  /// Create a new interval
205  pub fn new(start: Option<u32>, end: Option<u32>) -> Self {
206    Self { start, end }
207  }
208}
209
210/// Schemes that can be used with htsget.
211#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq)]
212#[serde(rename_all = "UPPERCASE", deny_unknown_fields)]
213pub enum Scheme {
214  #[default]
215  #[serde(alias = "Http", alias = "http")]
216  Http,
217  #[serde(alias = "Https", alias = "https")]
218  Https,
219}
220
221impl Display for Scheme {
222  fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
223    match self {
224      Scheme::Http => write!(f, "http"),
225      Scheme::Https => write!(f, "https"),
226    }
227  }
228}
229
230/// Tagged Any allow type for cors config.
231#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
232#[serde(deny_unknown_fields)]
233pub enum TaggedTypeAll {
234  #[serde(alias = "all", alias = "ALL")]
235  All,
236}
237
238/// Possible values for the fields parameter.
239#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
240#[serde(untagged, deny_unknown_fields)]
241pub enum Fields {
242  /// Include all fields
243  Tagged(TaggedTypeAll),
244  /// List of fields to include
245  List(HashSet<String>),
246}
247
248impl Default for Fields {
249  fn default() -> Self {
250    Self::Tagged(TaggedTypeAll::All)
251  }
252}
253
254/// Possible values for the tags parameter.
255#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
256#[serde(untagged, deny_unknown_fields)]
257pub enum Tags {
258  /// Include all tags
259  Tagged(TaggedTypeAll),
260  /// List of tags to include
261  List(HashSet<String>),
262}
263
264impl Default for Tags {
265  fn default() -> Self {
266    Self::Tagged(TaggedTypeAll::All)
267  }
268}
269
270/// The no tags parameter.
271#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
272#[serde(deny_unknown_fields)]
273pub struct NoTags(pub Option<HashSet<String>>);
274
275/// A struct containing the information from the HTTP request.
276#[derive(Clone, Debug, PartialEq, Eq, Default)]
277pub struct Request {
278  path: String,
279  query: HashMap<String, String>,
280  headers: HeaderMap,
281}
282
283impl Request {
284  /// Create a new request.
285  pub fn new(id: String, query: HashMap<String, String>, headers: HeaderMap) -> Self {
286    Self {
287      path: id,
288      query,
289      headers,
290    }
291  }
292
293  /// Create a new request with default query and headers.
294  pub fn new_with_id(id: String) -> Self {
295    Self::new(id, Default::default(), Default::default())
296  }
297
298  /// Get the id.
299  pub fn path(&self) -> &str {
300    &self.path
301  }
302
303  /// Get the query.
304  pub fn query(&self) -> &HashMap<String, String> {
305    &self.query
306  }
307
308  /// Get the headers.
309  pub fn headers(&self) -> &HeaderMap {
310    &self.headers
311  }
312}
313
314/// A query contains all the parameters that can be used when requesting
315/// a search for either of `reads` or `variants`.
316#[derive(Clone, Debug, PartialEq, Eq, Default)]
317pub struct Query {
318  id: String,
319  format: Format,
320  class: Class,
321  /// Reference name
322  reference_name: Option<String>,
323  /// The start and end positions are 0-based. [start, end)
324  interval: Interval,
325  fields: Fields,
326  tags: Tags,
327  no_tags: NoTags,
328  /// The raw HTTP request information.
329  request: Request,
330  #[cfg(feature = "experimental")]
331  encryption_scheme: Option<EncryptionScheme>,
332}
333
334impl Query {
335  /// Create a new query.
336  pub fn new(id: impl Into<String>, format: Format, request: Request) -> Self {
337    Self {
338      id: id.into(),
339      format,
340      request,
341      ..Default::default()
342    }
343  }
344
345  /// Create a new query with a default request.
346  pub fn new_with_default_request(id: impl Into<String>, format: Format) -> Self {
347    let id = id.into();
348    Self::new(id.clone(), format, Request::new_with_id(id))
349  }
350
351  /// Set the id.
352  pub fn set_id(&mut self, id: impl Into<String>) {
353    self.id = id.into();
354  }
355
356  /// Set the is and return self.
357  pub fn with_id(mut self, id: impl Into<String>) -> Self {
358    self.set_id(id);
359    self
360  }
361
362  /// Set the format.
363  pub fn with_format(mut self, format: Format) -> Self {
364    self.format = format;
365    self
366  }
367
368  /// Set the class.
369  pub fn with_class(mut self, class: Class) -> Self {
370    self.class = class;
371    self
372  }
373
374  /// Set the reference name.
375  pub fn with_reference_name(mut self, reference_name: impl Into<String>) -> Self {
376    self.reference_name = Some(reference_name.into());
377    self
378  }
379
380  /// Set the interval.
381  pub fn with_start(mut self, start: u32) -> Self {
382    self.interval.start = Some(start);
383    self
384  }
385
386  /// Set the interval.
387  pub fn with_end(mut self, end: u32) -> Self {
388    self.interval.end = Some(end);
389    self
390  }
391
392  /// Set the interval.
393  pub fn with_fields(mut self, fields: Fields) -> Self {
394    self.fields = fields;
395    self
396  }
397
398  /// Set the interval.
399  pub fn with_tags(mut self, tags: Tags) -> Self {
400    self.tags = tags;
401    self
402  }
403
404  /// Set no tags.
405  pub fn with_no_tags(mut self, no_tags: Vec<impl Into<String>>) -> Self {
406    self.no_tags = NoTags(Some(
407      no_tags.into_iter().map(|field| field.into()).collect(),
408    ));
409    self
410  }
411
412  /// Id.
413  pub fn id(&self) -> &str {
414    &self.id
415  }
416
417  /// Format.
418  pub fn format(&self) -> Format {
419    self.format
420  }
421
422  /// Class.
423  pub fn class(&self) -> Class {
424    self.class
425  }
426
427  /// Reference name.
428  pub fn reference_name(&self) -> Option<&str> {
429    self.reference_name.as_deref()
430  }
431
432  /// Interval.
433  pub fn interval(&self) -> Interval {
434    self.interval
435  }
436
437  /// Fields.
438  pub fn fields(&self) -> &Fields {
439    &self.fields
440  }
441
442  /// Tags.
443  pub fn tags(&self) -> &Tags {
444    &self.tags
445  }
446
447  /// No tags.
448  pub fn no_tags(&self) -> &NoTags {
449    &self.no_tags
450  }
451
452  /// Request.
453  pub fn request(&self) -> &Request {
454    &self.request
455  }
456
457  /// Set the encryption scheme.
458  #[cfg(feature = "experimental")]
459  pub fn with_encryption_scheme(mut self, encryption_scheme: EncryptionScheme) -> Self {
460    self.encryption_scheme = Some(encryption_scheme);
461    self
462  }
463
464  /// Get the encryption scheme
465  #[cfg(feature = "experimental")]
466  pub fn encryption_scheme(&self) -> Option<EncryptionScheme> {
467    self.encryption_scheme
468  }
469}
470
471/// Htsget specific errors.
472#[derive(Error, Debug, PartialEq, Eq)]
473pub enum HtsGetError {
474  #[error("not found: {0}")]
475  NotFound(String),
476
477  #[error("unsupported Format: {0}")]
478  UnsupportedFormat(String),
479
480  #[error("invalid input: {0}")]
481  InvalidInput(String),
482
483  #[error("invalid range: {0}")]
484  InvalidRange(String),
485
486  #[error("io error: {0}")]
487  IoError(String),
488
489  #[error("parsing error: {0}")]
490  ParseError(String),
491
492  #[error("internal error: {0}")]
493  InternalError(String),
494}
495
496impl HtsGetError {
497  /// Create a `NotFound` error.
498  pub fn not_found<S: Into<String>>(message: S) -> Self {
499    Self::NotFound(message.into())
500  }
501
502  /// Create an `UnsupportedFormat` error.
503  pub fn unsupported_format<S: Into<String>>(format: S) -> Self {
504    Self::UnsupportedFormat(format.into())
505  }
506
507  /// Create an `InvalidInput` error.
508  pub fn invalid_input<S: Into<String>>(message: S) -> Self {
509    Self::InvalidInput(message.into())
510  }
511
512  /// Create an `InvalidRange` error.
513  pub fn invalid_range<S: Into<String>>(message: S) -> Self {
514    Self::InvalidRange(message.into())
515  }
516
517  /// Create an `IoError` error.
518  pub fn io_error<S: Into<String>>(message: S) -> Self {
519    Self::IoError(message.into())
520  }
521
522  /// Create a `ParseError` error.
523  pub fn parse_error<S: Into<String>>(message: S) -> Self {
524    Self::ParseError(message.into())
525  }
526
527  /// Create an `InternalError` error.
528  pub fn internal_error<S: Into<String>>(message: S) -> Self {
529    Self::InternalError(message.into())
530  }
531}
532
533impl From<HtsGetError> for io::Error {
534  fn from(error: HtsGetError) -> Self {
535    Self::new(Other, error)
536  }
537}
538
539impl From<io::Error> for HtsGetError {
540  fn from(err: io::Error) -> Self {
541    Self::io_error(err.to_string())
542  }
543}
544
545/// The headers that need to be supplied when requesting data from a url.
546#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
547#[serde(deny_unknown_fields)]
548pub struct Headers(HashMap<String, String>);
549
550impl Headers {
551  pub fn new(headers: HashMap<String, String>) -> Self {
552    Self(headers)
553  }
554
555  /// Insert an entry into the headers. If the entry already exists, the value will be appended to
556  /// the existing value, separated by a comma. Returns self.
557  pub fn with_header<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
558    self.insert(key, value);
559    self
560  }
561
562  pub fn is_empty(&self) -> bool {
563    self.0.is_empty()
564  }
565
566  /// Insert an entry into the headers. If the entry already exists, the value will be appended to
567  /// the existing value, separated by a comma.
568  pub fn insert<K: Into<String>, V: Into<String>>(&mut self, key: K, value: V) {
569    let entry = self.0.entry(key.into()).or_default();
570    if entry.is_empty() {
571      entry.push_str(&value.into());
572    } else {
573      entry.push_str(&format!(", {}", value.into()));
574    }
575  }
576
577  /// Add to the headers.
578  pub fn extend(&mut self, headers: Headers) {
579    self.0.extend(headers.into_inner());
580  }
581
582  /// Get the inner HashMap.
583  pub fn into_inner(self) -> HashMap<String, String> {
584    self.0
585  }
586
587  /// Get a reference to the inner HashMap.
588  pub fn as_ref_inner(&self) -> &HashMap<String, String> {
589    &self.0
590  }
591
592  /// Get a mutable reference to the inner HashMap.
593  pub fn as_mut_inner(&mut self) -> &mut HashMap<String, String> {
594    &mut self.0
595  }
596}
597
598impl TryFrom<&HeaderMap> for Headers {
599  type Error = Error;
600
601  fn try_from(headers: &HeaderMap) -> result::Result<Self, Self::Error> {
602    headers
603      .iter()
604      .try_fold(Headers::default(), |acc, (key, value)| {
605        Ok(acc.with_header(
606          key.to_string(),
607          value.to_str().map_err(|err| {
608            ParseError(format!("failed to convert header value to string: {}", err))
609          })?,
610        ))
611      })
612  }
613}
614
615/// A url from which raw data can be retrieved.
616#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
617#[serde(deny_unknown_fields)]
618pub struct Url {
619  pub url: String,
620  #[serde(skip_serializing_if = "Option::is_none")]
621  pub headers: Option<Headers>,
622  #[serde(skip_serializing_if = "Option::is_none")]
623  pub class: Option<Class>,
624}
625
626impl Url {
627  /// Create a new Url.
628  pub fn new<S: Into<String>>(url: S) -> Self {
629    Self {
630      url: url.into(),
631      headers: None,
632      class: None,
633    }
634  }
635
636  /// Add to the headers of the Url.
637  pub fn add_headers(mut self, headers: Headers) -> Self {
638    if !headers.is_empty() {
639      self
640        .headers
641        .get_or_insert_with(Headers::default)
642        .extend(headers);
643    }
644
645    self
646  }
647
648  /// Set the headers of the Url.
649  pub fn with_headers(mut self, headers: Headers) -> Self {
650    self.headers = Some(headers).filter(|h| !h.is_empty());
651    self
652  }
653
654  /// Set the class of the Url using an optional value.
655  pub fn set_class(mut self, class: Option<Class>) -> Self {
656    self.class = class;
657    self
658  }
659
660  /// Set the class of the Url.
661  pub fn with_class(self, class: Class) -> Self {
662    self.set_class(Some(class))
663  }
664}
665
666/// Wrapped json response for htsget.
667#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
668#[serde(deny_unknown_fields)]
669pub struct JsonResponse {
670  pub htsget: Response,
671}
672
673impl JsonResponse {
674  /// Create a new `JsonResponse`.
675  pub fn new(htsget: Response) -> Self {
676    Self { htsget }
677  }
678}
679
680impl From<Response> for JsonResponse {
681  fn from(htsget: Response) -> Self {
682    Self::new(htsget)
683  }
684}
685
686/// The response for a HtsGet query.
687#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
688#[serde(deny_unknown_fields)]
689pub struct Response {
690  pub format: Format,
691  pub urls: Vec<Url>,
692}
693
694impl Response {
695  /// Create a new `Response`.
696  pub fn new(format: Format, urls: Vec<Url>) -> Self {
697    Self { format, urls }
698  }
699}
700
701#[cfg(test)]
702mod tests {
703  use std::collections::{HashMap, HashSet};
704  use std::str::FromStr;
705
706  use http::{HeaderMap, HeaderName, HeaderValue};
707  use serde_json::{json, to_value};
708
709  use crate::types::{
710    Class, Fields, Format, Headers, HtsGetError, Interval, NoTags, Query, Response, TaggedTypeAll,
711    Tags, Url,
712  };
713
714  #[test]
715  fn interval_contains() {
716    let interval = Interval {
717      start: Some(0),
718      end: Some(10),
719    };
720    assert!(interval.contains(9));
721  }
722
723  #[test]
724  fn interval_not_contains() {
725    let interval = Interval {
726      start: Some(0),
727      end: Some(10),
728    };
729    assert!(!interval.contains(10));
730  }
731
732  #[test]
733  fn interval_contains_start_not_present() {
734    let interval = Interval {
735      start: None,
736      end: Some(10),
737    };
738    assert!(interval.contains(9));
739  }
740
741  #[test]
742  fn interval_not_contains_start_not_present() {
743    let interval = Interval {
744      start: None,
745      end: Some(10),
746    };
747    assert!(!interval.contains(10));
748  }
749
750  #[test]
751  fn interval_contains_end_not_present() {
752    let interval = Interval {
753      start: Some(1),
754      end: None,
755    };
756    assert!(interval.contains(9));
757  }
758
759  #[test]
760  fn interval_not_contains_end_not_present() {
761    let interval = Interval {
762      start: Some(1),
763      end: None,
764    };
765    assert!(!interval.contains(0));
766  }
767
768  #[test]
769  fn interval_contains_both_not_present() {
770    let interval = Interval {
771      start: None,
772      end: None,
773    };
774    assert!(interval.contains(0));
775  }
776
777  #[test]
778  fn htsget_error_not_found() {
779    let result = HtsGetError::not_found("error");
780    assert!(matches!(result, HtsGetError::NotFound(message) if message == "error"));
781  }
782
783  #[test]
784  fn htsget_error_unsupported_format() {
785    let result = HtsGetError::unsupported_format("error");
786    assert!(matches!(result, HtsGetError::UnsupportedFormat(message) if message == "error"));
787  }
788
789  #[test]
790  fn htsget_error_invalid_input() {
791    let result = HtsGetError::invalid_input("error");
792    assert!(matches!(result, HtsGetError::InvalidInput(message) if message == "error"));
793  }
794
795  #[test]
796  fn htsget_error_invalid_range() {
797    let result = HtsGetError::invalid_range("error");
798    assert!(matches!(result, HtsGetError::InvalidRange(message) if message == "error"));
799  }
800
801  #[test]
802  fn htsget_error_io_error() {
803    let result = HtsGetError::io_error("error");
804    assert!(matches!(result, HtsGetError::IoError(message) if message == "error"));
805  }
806
807  #[test]
808  fn htsget_error_parse_error() {
809    let result = HtsGetError::parse_error("error");
810    assert!(matches!(result, HtsGetError::ParseError(message) if message == "error"));
811  }
812
813  #[test]
814  fn htsget_error_internal_error() {
815    let result = HtsGetError::internal_error("error");
816    assert!(matches!(result, HtsGetError::InternalError(message) if message == "error"));
817  }
818
819  #[test]
820  fn query_new() {
821    let result = Query::new_with_default_request("NA12878", Format::Bam);
822    assert_eq!(result.id(), "NA12878");
823  }
824
825  #[test]
826  fn query_with_format() {
827    let result = Query::new_with_default_request("NA12878", Format::Bam);
828    assert_eq!(result.format(), Format::Bam);
829  }
830
831  #[test]
832  fn query_with_class() {
833    let result = Query::new_with_default_request("NA12878", Format::Bam).with_class(Class::Header);
834    assert_eq!(result.class(), Class::Header);
835  }
836
837  #[test]
838  fn query_with_reference_name() {
839    let result =
840      Query::new_with_default_request("NA12878", Format::Bam).with_reference_name("chr1");
841    assert_eq!(result.reference_name(), Some("chr1"));
842  }
843
844  #[test]
845  fn query_with_start() {
846    let result = Query::new_with_default_request("NA12878", Format::Bam).with_start(0);
847    assert_eq!(result.interval().start(), Some(0));
848  }
849
850  #[test]
851  fn query_with_end() {
852    let result = Query::new_with_default_request("NA12878", Format::Bam).with_end(0);
853    assert_eq!(result.interval().end(), Some(0));
854  }
855
856  #[test]
857  fn query_with_fields() {
858    let result = Query::new_with_default_request("NA12878", Format::Bam).with_fields(Fields::List(
859      HashSet::from_iter(vec!["QNAME".to_string(), "FLAG".to_string()]),
860    ));
861    assert_eq!(
862      result.fields(),
863      &Fields::List(HashSet::from_iter(vec![
864        "QNAME".to_string(),
865        "FLAG".to_string()
866      ]))
867    );
868  }
869
870  #[test]
871  fn query_with_tags() {
872    let result = Query::new_with_default_request("NA12878", Format::Bam)
873      .with_tags(Tags::Tagged(TaggedTypeAll::All));
874    assert_eq!(result.tags(), &Tags::Tagged(TaggedTypeAll::All));
875  }
876
877  #[test]
878  fn query_with_no_tags() {
879    let result =
880      Query::new_with_default_request("NA12878", Format::Bam).with_no_tags(vec!["RG", "OQ"]);
881    assert_eq!(
882      result.no_tags(),
883      &NoTags(Some(HashSet::from_iter(vec![
884        "RG".to_string(),
885        "OQ".to_string()
886      ])))
887    );
888  }
889
890  #[test]
891  fn format_from_bam() {
892    let result = String::from(Format::Bam);
893    assert_eq!(result, "BAM");
894  }
895
896  #[test]
897  fn format_from_cram() {
898    let result = String::from(Format::Cram);
899    assert_eq!(result, "CRAM");
900  }
901
902  #[test]
903  fn format_from_vcf() {
904    let result = String::from(Format::Vcf);
905    assert_eq!(result, "VCF");
906  }
907
908  #[test]
909  fn format_from_bcf() {
910    let result = String::from(Format::Bcf);
911    assert_eq!(result, "BCF");
912  }
913
914  #[test]
915  fn headers_with_header() {
916    let header = Headers::new(HashMap::new()).with_header("Range", "bytes=0-1023");
917    let result = header.0.get("Range");
918    assert_eq!(result, Some(&"bytes=0-1023".to_string()));
919  }
920
921  #[test]
922  fn headers_is_empty() {
923    assert!(Headers::new(HashMap::new()).is_empty());
924  }
925
926  #[test]
927  fn headers_insert() {
928    let mut header = Headers::new(HashMap::new());
929    header.insert("Range", "bytes=0-1023");
930    let result = header.0.get("Range");
931    assert_eq!(result, Some(&"bytes=0-1023".to_string()));
932  }
933
934  #[test]
935  fn headers_extend() {
936    let mut headers = Headers::new(HashMap::new());
937    headers.insert("Range", "bytes=0-1023");
938
939    let mut extend_with = Headers::new(HashMap::new());
940    extend_with.insert("header", "value");
941
942    headers.extend(extend_with);
943
944    let result = headers.0.get("Range");
945    assert_eq!(result, Some(&"bytes=0-1023".to_string()));
946
947    let result = headers.0.get("header");
948    assert_eq!(result, Some(&"value".to_string()));
949  }
950
951  #[test]
952  fn headers_multiple_values() {
953    let headers = Headers::new(HashMap::new())
954      .with_header("Range", "bytes=0-1023")
955      .with_header("Range", "bytes=1024-2047");
956    let result = headers.0.get("Range");
957
958    assert_eq!(result, Some(&"bytes=0-1023, bytes=1024-2047".to_string()));
959  }
960
961  #[test]
962  fn headers_try_from_header_map() {
963    let mut headers = HeaderMap::new();
964    headers.append(
965      HeaderName::from_str("Range").unwrap(),
966      HeaderValue::from_str("bytes=0-1023").unwrap(),
967    );
968    headers.append(
969      HeaderName::from_str("Range").unwrap(),
970      HeaderValue::from_str("bytes=1024-2047").unwrap(),
971    );
972    headers.append(
973      HeaderName::from_str("Range").unwrap(),
974      HeaderValue::from_str("bytes=2048-3071, bytes=3072-4095").unwrap(),
975    );
976    let headers: Headers = (&headers).try_into().unwrap();
977
978    let result = headers.0.get("range");
979    assert_eq!(
980      result,
981      Some(&"bytes=0-1023, bytes=1024-2047, bytes=2048-3071, bytes=3072-4095".to_string())
982    );
983  }
984
985  #[test]
986  fn serialize_headers() {
987    let headers = Headers::new(HashMap::new())
988      .with_header("Range", "bytes=0-1023")
989      .with_header("Range", "bytes=1024-2047");
990
991    let result = to_value(headers).unwrap();
992    assert_eq!(
993      result,
994      json!({
995        "Range" : "bytes=0-1023, bytes=1024-2047"
996      })
997    );
998  }
999
1000  #[test]
1001  fn url_with_headers() {
1002    let result = Url::new("data:application/vnd.ga4gh.bam;base64,QkFNAQ==")
1003      .with_headers(Headers::new(HashMap::new()));
1004    assert_eq!(result.headers, None);
1005  }
1006
1007  #[test]
1008  fn url_add_headers() {
1009    let mut headers = Headers::new(HashMap::new());
1010    headers.insert("Range", "bytes=0-1023");
1011
1012    let mut extend_with = Headers::new(HashMap::new());
1013    extend_with.insert("header", "value");
1014
1015    let result = Url::new("data:application/vnd.ga4gh.bam;base64,QkFNAQ==")
1016      .with_headers(headers)
1017      .add_headers(extend_with);
1018
1019    let expected_headers = Headers::new(HashMap::new())
1020      .with_header("Range", "bytes=0-1023")
1021      .with_header("header", "value");
1022
1023    assert_eq!(result.headers, Some(expected_headers));
1024  }
1025
1026  #[test]
1027  fn url_with_class() {
1028    let result =
1029      Url::new("data:application/vnd.ga4gh.bam;base64,QkFNAQ==").with_class(Class::Header);
1030    assert_eq!(result.class, Some(Class::Header));
1031  }
1032
1033  #[test]
1034  fn url_set_class() {
1035    let result =
1036      Url::new("data:application/vnd.ga4gh.bam;base64,QkFNAQ==").set_class(Some(Class::Header));
1037    assert_eq!(result.class, Some(Class::Header));
1038  }
1039
1040  #[test]
1041  fn url_new() {
1042    let result = Url::new("data:application/vnd.ga4gh.bam;base64,QkFNAQ==");
1043    assert_eq!(result.url, "data:application/vnd.ga4gh.bam;base64,QkFNAQ==");
1044    assert_eq!(result.headers, None);
1045    assert_eq!(result.class, None);
1046  }
1047
1048  #[test]
1049  fn response_new() {
1050    let result = Response::new(
1051      Format::Bam,
1052      vec![Url::new("data:application/vnd.ga4gh.bam;base64,QkFNAQ==")],
1053    );
1054    assert_eq!(result.format, Format::Bam);
1055    assert_eq!(
1056      result.urls,
1057      vec![Url::new("data:application/vnd.ga4gh.bam;base64,QkFNAQ==")]
1058    );
1059  }
1060}