noodles_vcf/header.rs
1//! VCF header and fields.
2
3mod builder;
4pub mod file_format;
5pub mod parser;
6pub mod record;
7pub mod string_maps;
8
9pub use self::{
10 builder::Builder, file_format::FileFormat, parser::ParseError, parser::Parser, record::Record,
11 string_maps::StringMaps,
12};
13
14use std::{hash::Hash, str::FromStr};
15
16use indexmap::{IndexMap, IndexSet};
17
18use self::record::value::{
19 map::{AlternativeAllele, Contig, Filter, Format, Info},
20 Map,
21};
22
23/// VCF header info records.
24pub type Infos = IndexMap<String, Map<Info>>;
25
26/// VCF header filter records.
27pub type Filters = IndexMap<String, Map<Filter>>;
28
29/// VCF header format records.
30pub type Formats = IndexMap<String, Map<Format>>;
31
32/// VCF header alternative allele records.
33pub type AlternativeAlleles = IndexMap<String, Map<AlternativeAllele>>;
34
35/// VCF header contig records.
36pub type Contigs = IndexMap<String, Map<Contig>>;
37
38/// VCF header sample names.
39pub type SampleNames = IndexSet<String>;
40
41/// VCF header generic records.
42pub type OtherRecords = IndexMap<record::key::Other, record::value::Collection>;
43
44/// A VCF header.
45#[derive(Clone, Debug, Eq, PartialEq)]
46pub struct Header {
47 file_format: FileFormat,
48 infos: Infos,
49 filters: Filters,
50 formats: Formats,
51 alternative_alleles: AlternativeAlleles,
52 contigs: Contigs,
53 sample_names: SampleNames,
54 other_records: OtherRecords,
55 string_maps: StringMaps,
56}
57
58impl Header {
59 /// Returns a builder to create a record from each of its fields.
60 ///
61 /// # Examples
62 ///
63 /// ```
64 /// use noodles_vcf as vcf;
65 /// let builder = vcf::Header::builder();
66 /// ```
67 pub fn builder() -> Builder {
68 Builder::default()
69 }
70
71 /// Returns the file format (`fileformat`) of the VCF.
72 ///
73 /// `fileformat` is a required meta record and is guaranteed to be set.
74 ///
75 /// # Examples
76 ///
77 /// ```
78 /// use noodles_vcf::{self as vcf, header::FileFormat};
79 ///
80 /// let header = vcf::Header::builder()
81 /// .set_file_format(FileFormat::default())
82 /// .build();
83 ///
84 /// assert_eq!(header.file_format(), FileFormat::default());
85 /// ```
86 pub fn file_format(&self) -> FileFormat {
87 self.file_format
88 }
89
90 /// Returns a mutable reference to the file format (`fileformat`) of the VCF.
91 ///
92 /// `fileformat` is a required meta record and is guaranteed to be set.
93 ///
94 /// # Examples
95 ///
96 /// ```
97 /// use noodles_vcf::{self as vcf, header::FileFormat};
98 ///
99 /// let mut header = vcf::Header::default();
100 ///
101 /// let file_format = FileFormat::new(4, 2);
102 /// *header.file_format_mut() = file_format;
103 ///
104 /// assert_eq!(header.file_format(), file_format);
105 /// ```
106 pub fn file_format_mut(&mut self) -> &mut FileFormat {
107 &mut self.file_format
108 }
109
110 /// Returns a map of information records (`INFO`).
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// use noodles_vcf::{
116 /// self as vcf,
117 /// header::record::value::{map::Info, Map},
118 /// variant::record::info::field::key,
119 /// };
120 ///
121 /// let id = key::SAMPLES_WITH_DATA_COUNT;
122 /// let info = Map::<Info>::from(id);
123 ///
124 /// let header = vcf::Header::builder()
125 /// .add_info(id, info.clone())
126 /// .build();
127 ///
128 /// let infos = header.infos();
129 /// assert_eq!(infos.len(), 1);
130 /// assert_eq!(&infos[0], &info);
131 /// ```
132 pub fn infos(&self) -> &Infos {
133 &self.infos
134 }
135
136 /// Returns a mutable reference to a map of information records (`INFO`).
137 ///
138 /// # Examples
139 ///
140 /// ```
141 /// use noodles_vcf::{
142 /// self as vcf,
143 /// header::record::value::{map::Info, Map},
144 /// variant::record::info::field::key,
145 /// };
146 ///
147 /// let mut header = vcf::Header::default();
148 ///
149 /// let id = key::SAMPLES_WITH_DATA_COUNT;
150 /// let info = Map::<Info>::from(id);
151 /// header.infos_mut().insert(id.into(), info.clone());
152 ///
153 /// let infos = header.infos();
154 /// assert_eq!(infos.len(), 1);
155 /// assert_eq!(&infos[0], &info);
156 /// ```
157 pub fn infos_mut(&mut self) -> &mut Infos {
158 &mut self.infos
159 }
160
161 /// Returns a map of filter records (`FILTER`).
162 ///
163 /// # Examples
164 ///
165 /// ```
166 /// use noodles_vcf::{self as vcf, header::record::value::{map::Filter, Map}};
167 ///
168 /// let filter = Map::<Filter>::new("Quality below 10");
169 ///
170 /// let header = vcf::Header::builder()
171 /// .add_filter("q10", filter.clone())
172 /// .build();
173 ///
174 /// let filters = header.filters();
175 /// assert_eq!(filters.len(), 1);
176 /// assert_eq!(&filters[0], &filter);
177 /// ```
178 pub fn filters(&self) -> &Filters {
179 &self.filters
180 }
181
182 /// Returns a mutable reference to a map of filter records (`FILTER`).
183 ///
184 /// # Examples
185 ///
186 /// ```
187 /// use noodles_vcf::{self as vcf, header::record::value::{map::Filter, Map}};
188 ///
189 /// let mut header = vcf::Header::default();
190 ///
191 /// let filter = Map::<Filter>::new("Quality below 10");
192 /// header.filters_mut().insert(String::from("q10"), filter.clone());
193 ///
194 /// let filters = header.filters();
195 /// assert_eq!(filters.len(), 1);
196 /// assert_eq!(&filters[0], &filter);
197 /// ```
198 pub fn filters_mut(&mut self) -> &mut Filters {
199 &mut self.filters
200 }
201
202 /// Returns a list of genotype format records (`FORMAT`).
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// use noodles_vcf::{
208 /// self as vcf,
209 /// header::record::value::{map::Format, Map},
210 /// variant::record::samples::keys::key,
211 /// };
212 ///
213 /// let id = key::GENOTYPE;
214 /// let format = Map::<Format>::from(id);
215 ///
216 /// let header = vcf::Header::builder()
217 /// .add_format(id, format.clone())
218 /// .build();
219 ///
220 /// let formats = header.formats();
221 /// assert_eq!(formats.len(), 1);
222 /// assert_eq!(&formats[0], &format);
223 /// ```
224 pub fn formats(&self) -> &Formats {
225 &self.formats
226 }
227
228 /// Returns a mutable reference to a list of genotype format records (`FORMAT`).
229 ///
230 /// # Examples
231 ///
232 /// ```
233 /// use noodles_vcf::{
234 /// self as vcf,
235 /// header::record::value::{map::Format, Map},
236 /// variant::record::samples::keys::key,
237 /// };
238 ///
239 /// let mut header = vcf::Header::default();
240 ///
241 /// let id = key::GENOTYPE;
242 /// let format = Map::<Format>::from(id);
243 /// header.formats_mut().insert(id.into(), format.clone());
244 ///
245 /// let formats = header.formats();
246 /// assert_eq!(formats.len(), 1);
247 /// assert_eq!(&formats[0], &format);
248 /// ```
249 pub fn formats_mut(&mut self) -> &mut Formats {
250 &mut self.formats
251 }
252
253 /// Returns a map of symbolic alternate alleles (`ALT`).
254 ///
255 /// # Examples
256 ///
257 /// ```
258 /// use noodles_vcf::{
259 /// self as vcf,
260 /// header::record::value::{map::AlternativeAllele, Map},
261 /// };
262 ///
263 /// let alt = Map::<AlternativeAllele>::new("Deletion");
264 ///
265 /// let header = vcf::Header::builder()
266 /// .add_alternative_allele("DEL", alt.clone())
267 /// .build();
268 ///
269 /// let alternative_alleles = header.alternative_alleles();
270 /// assert_eq!(alternative_alleles.len(), 1);
271 /// assert_eq!(&alternative_alleles[0], &alt);
272 /// ```
273 pub fn alternative_alleles(&self) -> &AlternativeAlleles {
274 &self.alternative_alleles
275 }
276
277 /// Returns a mutable reference to a map of symbolic alternate alleles (`ALT`).
278 ///
279 /// # Examples
280 ///
281 /// ```
282 /// use noodles_vcf::{
283 /// self as vcf,
284 /// header::record::value::{map::AlternativeAllele, Map},
285 /// };
286 ///
287 /// let mut header = vcf::Header::default();
288 ///
289 /// let alt = Map::<AlternativeAllele>::new("Deletion");
290 /// header.alternative_alleles_mut().insert(String::from("DEL"), alt.clone());
291 ///
292 /// let alternative_alleles = header.alternative_alleles();
293 /// assert_eq!(alternative_alleles.len(), 1);
294 /// assert_eq!(&alternative_alleles[0], &alt);
295 /// ```
296 pub fn alternative_alleles_mut(&mut self) -> &mut AlternativeAlleles {
297 &mut self.alternative_alleles
298 }
299
300 /// Returns a map of contig records (`contig`).
301 ///
302 /// # Examples
303 ///
304 /// ```
305 /// use noodles_vcf::{self as vcf, header::record::value::{map::Contig, Map}};
306 ///
307 /// let contig = Map::<Contig>::new();
308 ///
309 /// let header = vcf::Header::builder()
310 /// .add_contig("sq0", contig.clone())
311 /// .build();
312 ///
313 /// let contigs = header.contigs();
314 /// assert_eq!(contigs.len(), 1);
315 /// assert_eq!(&contigs[0], &contig);
316 /// ```
317 pub fn contigs(&self) -> &Contigs {
318 &self.contigs
319 }
320
321 /// Returns a mutable reference to a map of contig records (`contig`).
322 ///
323 /// # Examples
324 ///
325 /// ```
326 /// use noodles_vcf::{self as vcf, header::record::value::{map::Contig, Map}};
327 ///
328 /// let mut header = vcf::Header::default();
329 ///
330 /// let contig = Map::<Contig>::new();
331 /// header.contigs_mut().insert(String::from("sq0"), contig.clone());
332 ///
333 /// let contigs = header.contigs();
334 /// assert_eq!(contigs.len(), 1);
335 /// assert_eq!(&contigs[0], &contig);
336 /// ```
337 pub fn contigs_mut(&mut self) -> &mut Contigs {
338 &mut self.contigs
339 }
340
341 /// Returns a list of sample names that come after the FORMAT column in the header record.
342 ///
343 /// # Examples
344 ///
345 /// ```
346 /// use indexmap::IndexSet;
347 /// use noodles_vcf as vcf;
348 ///
349 /// let header = vcf::Header::builder()
350 /// .add_sample_name("sample0")
351 /// .add_sample_name("sample1")
352 /// .build();
353 ///
354 /// let expected: IndexSet<_> = [String::from("sample0"), String::from("sample1")]
355 /// .into_iter()
356 /// .collect();
357 ///
358 /// assert_eq!(header.sample_names(), &expected);
359 /// ```
360 pub fn sample_names(&self) -> &SampleNames {
361 &self.sample_names
362 }
363
364 /// Returns a mutable reference to a list of sample names that come after the FORMAT column in
365 /// the header record.
366 ///
367 /// # Examples
368 ///
369 /// ```
370 /// use indexmap::IndexSet;
371 /// use noodles_vcf as vcf;
372 ///
373 /// let mut header = vcf::Header::builder().add_sample_name("sample0").build();
374 /// header.sample_names_mut().insert(String::from("sample1"));
375 ///
376 /// let expected: IndexSet<_> = [String::from("sample0"), String::from("sample1")]
377 /// .into_iter()
378 /// .collect();
379 ///
380 /// assert_eq!(header.sample_names(), &expected);
381 /// ```
382 pub fn sample_names_mut(&mut self) -> &mut SampleNames {
383 &mut self.sample_names
384 }
385
386 /// Returns a map of records with nonstandard keys.
387 ///
388 /// This includes all records other than `fileformat`, `INFO`, `FILTER`, `FORMAT`, `ALT`, and
389 /// `contig`.
390 ///
391 /// # Examples
392 ///
393 /// ```
394 /// use noodles_vcf::{self as vcf, header::record::Value};
395 ///
396 /// let header = vcf::Header::builder()
397 /// .insert("fileDate".parse()?, Value::from("20200709"))?
398 /// .build();
399 ///
400 /// assert_eq!(header.other_records().len(), 1);
401 /// # Ok::<_, Box<dyn std::error::Error>>(())
402 /// ```
403 pub fn other_records(&self) -> &OtherRecords {
404 &self.other_records
405 }
406
407 /// Returns a mutable reference to a map of collections of records with nonstandard keys.
408 ///
409 /// This includes all records other than `fileformat`, `INFO`, `FILTER`, `FORMAT`, `ALT`, and
410 /// `contig`.
411 ///
412 /// To simply add an nonstandard record, consider using [`Self::insert`] instead.
413 ///
414 /// # Examples
415 ///
416 /// ```
417 /// use noodles_vcf::{
418 /// self as vcf,
419 /// header::record::{value::Collection, Value},
420 /// };
421 ///
422 /// let mut header = vcf::Header::default();
423 ///
424 /// let collection = Collection::Unstructured(vec![String::from("20200709")]);
425 /// header.other_records_mut().insert("fileDate".parse()?, collection.clone());
426 ///
427 /// assert_eq!(header.other_records().get("fileDate"), Some(&collection));
428 /// # Ok::<_, vcf::header::record::key::other::ParseError>(())
429 /// ```
430 pub fn other_records_mut(&mut self) -> &mut OtherRecords {
431 &mut self.other_records
432 }
433
434 /// Returns a collection of header values with the given key.
435 ///
436 /// This includes all records other than `fileformat`, `INFO`, `FILTER`, `FORMAT`, `ALT`, and
437 /// `contig`.
438 ///
439 /// # Examples
440 ///
441 /// ```
442 /// use noodles_vcf::{
443 /// self as vcf,
444 /// header::record::{value::Collection, Value},
445 /// };
446 ///
447 /// let header = vcf::Header::builder()
448 /// .insert("fileDate".parse()?, Value::from("20200709"))?
449 /// .build();
450 ///
451 /// assert_eq!(
452 /// header.get("fileDate"),
453 /// Some(&Collection::Unstructured(vec![String::from("20200709")]))
454 /// );
455 ///
456 /// assert!(header.get("reference").is_none());
457 /// # Ok::<_, Box<dyn std::error::Error>>(())
458 /// ```
459 pub fn get<Q>(&self, key: &Q) -> Option<&record::value::Collection>
460 where
461 Q: ?Sized + Hash + indexmap::Equivalent<record::key::Other>,
462 {
463 self.other_records.get(key)
464 }
465
466 /// Inserts a key-value pair representing a nonstandard record into the header.
467 ///
468 /// # Examples
469 ///
470 /// ```
471 /// use noodles_vcf::{
472 /// self as vcf,
473 /// header::record::{value::Collection, Value},
474 /// };
475 ///
476 /// let mut header = vcf::Header::default();
477 /// assert!(header.get("fileDate").is_none());
478 ///
479 /// header.insert("fileDate".parse()?, Value::from("20200709"))?;
480 /// assert_eq!(
481 /// header.get("fileDate"),
482 /// Some(&Collection::Unstructured(vec![String::from("20200709")]))
483 /// );
484 /// # Ok::<_, Box<dyn std::error::Error>>(())
485 /// ```
486 pub fn insert(
487 &mut self,
488 key: record::key::Other,
489 value: record::Value,
490 ) -> Result<(), record::value::collection::AddError> {
491 let collection = self
492 .other_records
493 .entry(key)
494 .or_insert_with(|| match value {
495 record::Value::String(_) => record::value::Collection::Unstructured(Vec::new()),
496 record::Value::Map(..) => record::value::Collection::Structured(IndexMap::new()),
497 });
498
499 collection.add(value)
500 }
501
502 #[doc(hidden)]
503 pub fn string_maps(&self) -> &StringMaps {
504 &self.string_maps
505 }
506
507 #[doc(hidden)]
508 pub fn string_maps_mut(&mut self) -> &mut StringMaps {
509 &mut self.string_maps
510 }
511}
512
513impl Default for Header {
514 fn default() -> Self {
515 Builder::default().build()
516 }
517}
518
519impl FromStr for Header {
520 type Err = ParseError;
521
522 fn from_str(s: &str) -> Result<Self, Self::Err> {
523 Parser::default().parse(s)
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530
531 #[test]
532 fn test_default() {
533 let header = Header::default();
534 assert_eq!(header.file_format(), FileFormat::default());
535 }
536
537 #[test]
538 fn test_insert_with_duplicate_keys() -> Result<(), Box<dyn std::error::Error>> {
539 let key: record::key::Other = "noodles".parse()?;
540 let values = [record::Value::from("0"), record::Value::from("1")];
541
542 let mut header = Header::default();
543
544 for value in values {
545 header.insert(key.clone(), value)?;
546 }
547
548 assert_eq!(
549 header.get(&key),
550 Some(&record::value::Collection::Unstructured(vec![
551 String::from("0"),
552 String::from("1")
553 ]))
554 );
555
556 Ok(())
557 }
558}