1use 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
20pub type Result<T> = result::Result<T, HtsGetError>;
22
23#[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
38impl Format {
40 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 pub fn fmt_file(&self, id: &str) -> String {
52 format!("{id}{}", self.file_ending())
53 }
54
55 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 pub fn fmt_index(&self, id: &str) -> String {
67 format!("{id}{}", self.index_file_ending())
68 }
69
70 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 pub fn fmt_gzi(&self, id: &str) -> io::Result<String> {
85 Ok(format!("{id}{}", self.gzi_index_file_ending()?))
86 }
87
88 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#[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#[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 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 #[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 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 pub fn convert_end(end: u32) -> io::Result<Position> {
173 Self::convert_position(end, Ok)
174 }
175
176 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 pub fn start(&self) -> Option<u32> {
196 self.start
197 }
198
199 pub fn end(&self) -> Option<u32> {
201 self.end
202 }
203
204 pub fn new(start: Option<u32>, end: Option<u32>) -> Self {
206 Self { start, end }
207 }
208}
209
210#[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#[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#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
240#[serde(untagged, deny_unknown_fields)]
241pub enum Fields {
242 Tagged(TaggedTypeAll),
244 List(HashSet<String>),
246}
247
248impl Default for Fields {
249 fn default() -> Self {
250 Self::Tagged(TaggedTypeAll::All)
251 }
252}
253
254#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
256#[serde(untagged, deny_unknown_fields)]
257pub enum Tags {
258 Tagged(TaggedTypeAll),
260 List(HashSet<String>),
262}
263
264impl Default for Tags {
265 fn default() -> Self {
266 Self::Tagged(TaggedTypeAll::All)
267 }
268}
269
270#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
272#[serde(deny_unknown_fields)]
273pub struct NoTags(pub Option<HashSet<String>>);
274
275#[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 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 pub fn new_with_id(id: String) -> Self {
295 Self::new(id, Default::default(), Default::default())
296 }
297
298 pub fn path(&self) -> &str {
300 &self.path
301 }
302
303 pub fn query(&self) -> &HashMap<String, String> {
305 &self.query
306 }
307
308 pub fn headers(&self) -> &HeaderMap {
310 &self.headers
311 }
312}
313
314#[derive(Clone, Debug, PartialEq, Eq, Default)]
317pub struct Query {
318 id: String,
319 format: Format,
320 class: Class,
321 reference_name: Option<String>,
323 interval: Interval,
325 fields: Fields,
326 tags: Tags,
327 no_tags: NoTags,
328 request: Request,
330 #[cfg(feature = "experimental")]
331 encryption_scheme: Option<EncryptionScheme>,
332}
333
334impl Query {
335 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 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 pub fn set_id(&mut self, id: impl Into<String>) {
353 self.id = id.into();
354 }
355
356 pub fn with_id(mut self, id: impl Into<String>) -> Self {
358 self.set_id(id);
359 self
360 }
361
362 pub fn with_format(mut self, format: Format) -> Self {
364 self.format = format;
365 self
366 }
367
368 pub fn with_class(mut self, class: Class) -> Self {
370 self.class = class;
371 self
372 }
373
374 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 pub fn with_start(mut self, start: u32) -> Self {
382 self.interval.start = Some(start);
383 self
384 }
385
386 pub fn with_end(mut self, end: u32) -> Self {
388 self.interval.end = Some(end);
389 self
390 }
391
392 pub fn with_fields(mut self, fields: Fields) -> Self {
394 self.fields = fields;
395 self
396 }
397
398 pub fn with_tags(mut self, tags: Tags) -> Self {
400 self.tags = tags;
401 self
402 }
403
404 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 pub fn id(&self) -> &str {
414 &self.id
415 }
416
417 pub fn format(&self) -> Format {
419 self.format
420 }
421
422 pub fn class(&self) -> Class {
424 self.class
425 }
426
427 pub fn reference_name(&self) -> Option<&str> {
429 self.reference_name.as_deref()
430 }
431
432 pub fn interval(&self) -> Interval {
434 self.interval
435 }
436
437 pub fn fields(&self) -> &Fields {
439 &self.fields
440 }
441
442 pub fn tags(&self) -> &Tags {
444 &self.tags
445 }
446
447 pub fn no_tags(&self) -> &NoTags {
449 &self.no_tags
450 }
451
452 pub fn request(&self) -> &Request {
454 &self.request
455 }
456
457 #[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 #[cfg(feature = "experimental")]
466 pub fn encryption_scheme(&self) -> Option<EncryptionScheme> {
467 self.encryption_scheme
468 }
469}
470
471#[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 pub fn not_found<S: Into<String>>(message: S) -> Self {
499 Self::NotFound(message.into())
500 }
501
502 pub fn unsupported_format<S: Into<String>>(format: S) -> Self {
504 Self::UnsupportedFormat(format.into())
505 }
506
507 pub fn invalid_input<S: Into<String>>(message: S) -> Self {
509 Self::InvalidInput(message.into())
510 }
511
512 pub fn invalid_range<S: Into<String>>(message: S) -> Self {
514 Self::InvalidRange(message.into())
515 }
516
517 pub fn io_error<S: Into<String>>(message: S) -> Self {
519 Self::IoError(message.into())
520 }
521
522 pub fn parse_error<S: Into<String>>(message: S) -> Self {
524 Self::ParseError(message.into())
525 }
526
527 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#[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 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 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 pub fn extend(&mut self, headers: Headers) {
579 self.0.extend(headers.into_inner());
580 }
581
582 pub fn into_inner(self) -> HashMap<String, String> {
584 self.0
585 }
586
587 pub fn as_ref_inner(&self) -> &HashMap<String, String> {
589 &self.0
590 }
591
592 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#[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 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 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 pub fn with_headers(mut self, headers: Headers) -> Self {
650 self.headers = Some(headers).filter(|h| !h.is_empty());
651 self
652 }
653
654 pub fn set_class(mut self, class: Option<Class>) -> Self {
656 self.class = class;
657 self
658 }
659
660 pub fn with_class(self, class: Class) -> Self {
662 self.set_class(Some(class))
663 }
664}
665
666#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
668#[serde(deny_unknown_fields)]
669pub struct JsonResponse {
670 pub htsget: Response,
671}
672
673impl JsonResponse {
674 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#[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 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}