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}