noodles_vcf/header/
builder.rs

1use super::{
2    record::{
3        self,
4        value::{
5            map::{AlternativeAllele, Contig, Filter, Format, Info},
6            Map,
7        },
8    },
9    AlternativeAlleles, Contigs, FileFormat, Filters, Formats, Header, Infos, OtherRecords,
10    SampleNames, StringMaps,
11};
12
13use indexmap::IndexMap;
14
15/// A VCF header builder.
16#[derive(Debug, Default)]
17pub struct Builder {
18    file_format: FileFormat,
19    infos: Infos,
20    filters: Filters,
21    formats: Formats,
22    alternative_alleles: AlternativeAlleles,
23    contigs: Contigs,
24    sample_names: SampleNames,
25    other_records: OtherRecords,
26}
27
28impl Builder {
29    /// Sets the fileformat record (`fileformat`).
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// use noodles_vcf::{self as vcf, header::FileFormat};
35    ///
36    /// let header = vcf::Header::builder()
37    ///     .set_file_format(FileFormat::default())
38    ///     .build();
39    ///
40    /// assert_eq!(header.file_format(), FileFormat::default());
41    /// ```
42    pub fn set_file_format(mut self, file_format: FileFormat) -> Self {
43        self.file_format = file_format;
44        self
45    }
46
47    /// Adds an information record (`INFO`).
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// use noodles_vcf::{
53    ///     self as vcf,
54    ///     header::record::value::{map::Info, Map},
55    ///     variant::record::info::field::key,
56    /// };
57    ///
58    /// let id = key::SAMPLES_WITH_DATA_COUNT;
59    /// let info = Map::<Info>::from(id);
60    ///
61    /// let header = vcf::Header::builder()
62    ///     .add_info(id, info.clone())
63    ///     .build();
64    ///
65    /// let infos = header.infos();
66    /// assert_eq!(infos.len(), 1);
67    /// assert_eq!(&infos[0], &info);
68    /// ```
69    pub fn add_info<I>(mut self, id: I, info: Map<Info>) -> Self
70    where
71        I: Into<String>,
72    {
73        self.infos.insert(id.into(), info);
74        self
75    }
76
77    /// Adds a filter record (`FILTER`).
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use noodles_vcf::{self as vcf, header::record::value::{map::Filter, Map}};
83    ///
84    /// let filter = Map::<Filter>::new("Quality below 10");
85    ///
86    /// let header = vcf::Header::builder()
87    ///     .add_filter("q10", filter.clone())
88    ///     .build();
89    ///
90    /// let filters = header.filters();
91    /// assert_eq!(filters.len(), 1);
92    /// assert_eq!(&filters[0], &filter);
93    /// ```
94    pub fn add_filter<I>(mut self, id: I, filter: Map<Filter>) -> Self
95    where
96        I: Into<String>,
97    {
98        self.filters.insert(id.into(), filter);
99        self
100    }
101
102    /// Adds a genotype format record (`FORMAT`).
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use noodles_vcf::{
108    ///     self as vcf,
109    ///     header::record::value::{map::Format, Map},
110    ///     variant::record::samples::keys::key,
111    /// };
112    ///
113    /// let id = key::GENOTYPE;
114    /// let format = Map::<Format>::from(id);
115    ///
116    /// let header = vcf::Header::builder()
117    ///     .add_format(id, format.clone())
118    ///     .build();
119    ///
120    /// let formats = header.formats();
121    /// assert_eq!(formats.len(), 1);
122    /// assert_eq!(&formats[0], &format);
123    /// ```
124    pub fn add_format<I>(mut self, id: I, format: Map<Format>) -> Self
125    where
126        I: Into<String>,
127    {
128        self.formats.insert(id.into(), format);
129        self
130    }
131
132    /// Adds an alternative allele record (`ALT`).
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use noodles_vcf::{
138    ///     self as vcf,
139    ///     header::record::value::{map::AlternativeAllele, Map},
140    /// };
141    ///
142    /// let alt = Map::<AlternativeAllele>::new("Deletion");
143    ///
144    /// let header = vcf::Header::builder()
145    ///     .add_alternative_allele("DEL", alt.clone())
146    ///     .build();
147    ///
148    /// let alternative_alleles = header.alternative_alleles();
149    /// assert_eq!(alternative_alleles.len(), 1);
150    /// assert_eq!(&alternative_alleles[0], &alt);
151    /// ```
152    pub fn add_alternative_allele<I>(
153        mut self,
154        id: I,
155        alternative_allele: Map<AlternativeAllele>,
156    ) -> Self
157    where
158        I: Into<String>,
159    {
160        self.alternative_alleles
161            .insert(id.into(), alternative_allele);
162
163        self
164    }
165
166    /// Adds a contig record (`contig`).
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use noodles_vcf::{self as vcf, header::record::value::{map::Contig, Map}};
172    ///
173    /// let contig = Map::<Contig>::new();
174    ///
175    /// let header = vcf::Header::builder()
176    ///     .add_contig("sq0", contig.clone())
177    ///     .build();
178    ///
179    /// let contigs = header.contigs();
180    /// assert_eq!(contigs.len(), 1);
181    /// assert_eq!(&contigs[0], &contig);
182    /// ```
183    pub fn add_contig<I>(mut self, id: I, contig: Map<Contig>) -> Self
184    where
185        I: Into<String>,
186    {
187        self.contigs.insert(id.into(), contig);
188        self
189    }
190
191    /// Sets sample names.
192    ///
193    /// # Examples
194    ///
195    /// ```
196    /// use indexmap::IndexSet;
197    /// use noodles_vcf as vcf;
198    ///
199    /// let sample_names: IndexSet<_> = [String::from("sample0")]
200    ///     .into_iter()
201    ///     .collect();
202    ///
203    /// let header = vcf::Header::builder()
204    ///     .set_sample_names(sample_names.clone())
205    ///     .build();
206    ///
207    /// assert_eq!(header.sample_names(), &sample_names);
208    /// ```
209    pub fn set_sample_names(mut self, sample_names: SampleNames) -> Self {
210        self.sample_names = sample_names;
211        self
212    }
213
214    /// Adds a sample name.
215    ///
216    /// Duplicate names are discarded.
217    ///
218    /// # Examples
219    ///
220    /// ```
221    /// use indexmap::IndexSet;
222    /// use noodles_vcf as vcf;
223    ///
224    /// let header = vcf::Header::builder()
225    ///     .add_sample_name("sample0")
226    ///     .add_sample_name("sample1")
227    ///     .build();
228    ///
229    /// let expected: IndexSet<_> = [String::from("sample0"), String::from("sample1")]
230    ///     .into_iter()
231    ///     .collect();
232    ///
233    /// assert_eq!(header.sample_names(), &expected);
234    /// ```
235    pub fn add_sample_name<I>(mut self, sample_name: I) -> Self
236    where
237        I: Into<String>,
238    {
239        self.sample_names.insert(sample_name.into());
240        self
241    }
242
243    /// Inserts a key-value pair representing an unstructured record into the header.
244    ///
245    /// # Examples
246    ///
247    /// ```
248    /// use noodles_vcf::{
249    ///     self as vcf,
250    ///     header::record::{value::Collection, Value},
251    /// };
252    ///
253    /// let header = vcf::Header::builder()
254    ///     .insert("fileDate".parse()?, Value::from("20200709"))?
255    ///     .build();
256    ///
257    /// assert_eq!(
258    ///     header.get("fileDate"),
259    ///     Some(&Collection::Unstructured(vec![String::from("20200709")]))
260    /// );
261    /// # Ok::<_, Box<dyn std::error::Error>>(())
262    /// ```
263    pub fn insert(
264        mut self,
265        key: record::key::Other,
266        value: record::Value,
267    ) -> Result<Self, super::record::value::collection::AddError> {
268        let collection = self
269            .other_records
270            .entry(key)
271            .or_insert_with(|| match value {
272                record::Value::String(_) => record::value::Collection::Unstructured(Vec::new()),
273                record::Value::Map(..) => record::value::Collection::Structured(IndexMap::new()),
274            });
275
276        collection.add(value)?;
277
278        Ok(self)
279    }
280
281    /// Builds a VCF header.
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// use noodles_vcf as vcf;
287    /// let header = vcf::Header::builder().build();
288    /// ```
289    pub fn build(self) -> Header {
290        Header {
291            file_format: self.file_format,
292            infos: self.infos,
293            filters: self.filters,
294            formats: self.formats,
295            alternative_alleles: self.alternative_alleles,
296            contigs: self.contigs,
297            sample_names: self.sample_names,
298            other_records: self.other_records,
299            string_maps: StringMaps::default(),
300        }
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_default() {
310        let header = Builder::default().build();
311
312        assert_eq!(header.file_format(), FileFormat::default());
313        assert!(header.infos().is_empty());
314        assert!(header.filters().is_empty());
315        assert!(header.formats().is_empty());
316        assert!(header.alternative_alleles().is_empty());
317        assert!(header.contigs().is_empty());
318        assert!(header.sample_names().is_empty());
319    }
320
321    #[test]
322    fn test_build() -> Result<(), Box<dyn std::error::Error>> {
323        use crate::{
324            header,
325            variant::record::{info::field::key as info_key, samples::keys::key as format_key},
326        };
327
328        let (key, value) = (
329            "fileDate".parse::<header::record::key::Other>()?,
330            header::record::Value::from("20200709"),
331        );
332
333        let header = Builder::default()
334            .set_file_format(FileFormat::new(4, 3))
335            .add_info(
336                info_key::SAMPLES_WITH_DATA_COUNT,
337                Map::<Info>::from(info_key::SAMPLES_WITH_DATA_COUNT),
338            )
339            .add_filter("q10", Map::<Filter>::new("Quality below 10"))
340            .add_format(
341                format_key::GENOTYPE,
342                Map::<Format>::from(format_key::GENOTYPE),
343            )
344            .add_alternative_allele("DEL", Map::<AlternativeAllele>::new("Deletion"))
345            .add_contig("sq0", Map::<Contig>::new())
346            .add_contig("sq1", Map::<Contig>::new())
347            .add_sample_name("sample0")
348            .insert(key.clone(), value.clone())?
349            .insert(key.clone(), value)?
350            .build();
351
352        assert_eq!(header.file_format(), FileFormat::new(4, 3));
353        assert_eq!(header.infos().len(), 1);
354        assert_eq!(header.filters().len(), 1);
355        assert_eq!(header.formats().len(), 1);
356        assert_eq!(header.alternative_alleles().len(), 1);
357        assert_eq!(header.contigs().len(), 2);
358        assert_eq!(header.get(&key).map(|collection| collection.len()), Some(2));
359
360        Ok(())
361    }
362}