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