• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rust-bio / rust-htslib / 14738329579

29 Apr 2025 06:12PM UTC coverage: 83.282% (-0.3%) from 83.605%
14738329579

Pull #473

github

web-flow
Merge 61ffae407 into 8741513e4
Pull Request #473: feat: Adding `from_hashmap` for bam Header

0 of 9 new or added lines in 1 file covered. (0.0%)

3 existing lines in 2 files now uncovered.

2720 of 3266 relevant lines covered (83.28%)

17801.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.44
/src/bcf/record.rs
1
// Copyright 2014 Johannes Köster.
2
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
3
// This file may not be copied, modified, or distributed
4
// except according to those terms.
5

6
use std::borrow::{Borrow, BorrowMut};
7
use std::ffi;
8
use std::fmt;
9
use std::marker::PhantomData;
10
use std::ops::Deref;
11
use std::os::raw::c_char;
12
use std::ptr;
13
use std::rc::Rc;
14
use std::slice;
15
use std::str;
16

17
use bio_types::genome;
18
use derive_new::new;
19
use ieee754::Ieee754;
20
use lazy_static::lazy_static;
21

22
use crate::bcf::header::{HeaderView, Id};
23
use crate::bcf::Error;
24
use crate::errors::Result;
25
use crate::htslib;
26

27
const MISSING_INTEGER: i32 = i32::MIN;
28
const VECTOR_END_INTEGER: i32 = i32::MIN + 1;
29

30
lazy_static! {
31
    static ref MISSING_FLOAT: f32 = Ieee754::from_bits(0x7F80_0001);
1✔
32
    static ref VECTOR_END_FLOAT: f32 = Ieee754::from_bits(0x7F80_0002);
2✔
33
}
34

35
/// Common methods for numeric INFO and FORMAT entries
36
pub trait Numeric {
37
    /// Return true if entry is a missing value
38
    fn is_missing(&self) -> bool;
39

40
    /// Return missing value for storage in BCF record.
41
    fn missing() -> Self;
42
}
43

44
impl Numeric for f32 {
45
    fn is_missing(&self) -> bool {
6✔
46
        self.bits() == MISSING_FLOAT.bits()
6✔
47
    }
48

49
    fn missing() -> f32 {
×
50
        *MISSING_FLOAT
×
51
    }
52
}
53

54
impl Numeric for i32 {
55
    fn is_missing(&self) -> bool {
×
56
        *self == MISSING_INTEGER
×
57
    }
58

59
    fn missing() -> i32 {
6✔
60
        MISSING_INTEGER
6✔
61
    }
62
}
63

64
trait NumericUtils {
65
    /// Return true if entry marks the end of the record.
66
    fn is_vector_end(&self) -> bool;
67
}
68

69
impl NumericUtils for f32 {
70
    fn is_vector_end(&self) -> bool {
18✔
71
        self.bits() == VECTOR_END_FLOAT.bits()
18✔
72
    }
73
}
74

75
impl NumericUtils for i32 {
76
    fn is_vector_end(&self) -> bool {
478✔
77
        *self == VECTOR_END_INTEGER
478✔
78
    }
79
}
80

81
/// A trait to allow for seamless use of bytes or integer identifiers for filters
82
pub trait FilterId {
83
    fn id_from_header(&self, header: &HeaderView) -> Result<Id>;
84
    fn is_pass(&self) -> bool;
85
}
86

87
impl FilterId for [u8] {
88
    fn id_from_header(&self, header: &HeaderView) -> Result<Id> {
48✔
89
        header.name_to_id(self)
48✔
90
    }
91
    fn is_pass(&self) -> bool {
36✔
92
        matches!(self, b"PASS" | b".")
71✔
93
    }
94
}
95

96
impl FilterId for Id {
97
    fn id_from_header(&self, _header: &HeaderView) -> Result<Id> {
21✔
98
        Ok(*self)
21✔
99
    }
100
    fn is_pass(&self) -> bool {
16✔
101
        *self == Id(0)
16✔
102
    }
103
}
104

105
/// A buffer for info or format data.
106
#[derive(Debug)]
107
pub struct Buffer {
108
    inner: *mut ::std::os::raw::c_void,
109
    len: i32,
110
}
111

112
impl Buffer {
113
    pub fn new() -> Self {
296✔
114
        Buffer {
115
            inner: ptr::null_mut(),
296✔
116
            len: 0,
117
        }
118
    }
119
}
120

121
impl Default for Buffer {
122
    fn default() -> Self {
×
123
        Self::new()
×
124
    }
125
}
126

127
impl Drop for Buffer {
128
    fn drop(&mut self) {
296✔
129
        unsafe {
130
            ::libc::free(self.inner);
296✔
131
        }
132
    }
133
}
134

135
#[derive(new, Debug)]
136
pub struct BufferBacked<'a, T: 'a + fmt::Debug, B: Borrow<Buffer> + 'a> {
137
    value: T,
138
    _buffer: B,
139
    #[new(default)]
140
    phantom: PhantomData<&'a B>,
141
}
142

143
impl<'a, T: 'a + fmt::Debug, B: Borrow<Buffer> + 'a> Deref for BufferBacked<'a, T, B> {
144
    type Target = T;
145

146
    fn deref(&self) -> &T {
453✔
147
        &self.value
442✔
148
    }
149
}
150

151
impl<'a, T: 'a + fmt::Debug + fmt::Display, B: Borrow<Buffer> + 'a> fmt::Display
152
    for BufferBacked<'a, T, B>
153
{
154
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
155
        fmt::Display::fmt(&self.value, f)
×
156
    }
157
}
158

159
/// A VCF/BCF record.
160
/// New records can be created by the `empty_record` methods of [`bcf::Reader`](crate::bcf::Reader)
161
/// and [`bcf::Writer`](crate::bcf::Writer).
162
/// # Example
163
/// ```rust
164
/// use rust_htslib::bcf::{Format, Writer};
165
/// use rust_htslib::bcf::header::Header;
166
///
167
/// // Create minimal VCF header with a single sample
168
/// let mut header = Header::new();
169
/// header.push_sample("sample".as_bytes());
170
///
171
/// // Write uncompressed VCF to stdout with above header and get an empty record
172
/// let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
173
/// let mut record = vcf.empty_record();
174
/// ```
175
#[derive(Debug)]
176
pub struct Record {
177
    pub inner: *mut htslib::bcf1_t,
178
    header: Rc<HeaderView>,
179
}
180

181
impl Record {
182
    /// Construct record with reference to header `HeaderView`, for create-internal use.
183
    pub(crate) fn new(header: Rc<HeaderView>) -> Self {
437✔
184
        let inner = unsafe {
185
            let inner = htslib::bcf_init();
437✔
186
            // Always unpack record.
187
            htslib::bcf_unpack(inner, htslib::BCF_UN_ALL as i32);
437✔
188
            inner
420✔
189
        };
190
        Record { inner, header }
191
    }
192

193
    /// Force unpacking of internal record values.
194
    pub fn unpack(&mut self) {
×
195
        unsafe { htslib::bcf_unpack(self.inner, htslib::BCF_UN_ALL as i32) };
×
196
    }
197

198
    /// Return associated header.
199
    pub fn header(&self) -> &HeaderView {
729✔
200
        self.header.as_ref()
729✔
201
    }
202

203
    /// Set the record header.
204
    pub(crate) fn set_header(&mut self, header: Rc<HeaderView>) {
446✔
205
        self.header = header;
449✔
206
    }
207

208
    /// Return reference to the inner C struct.
209
    ///
210
    /// # Remarks
211
    ///
212
    /// Note that this function is only required as long as Rust-Htslib does not provide full
213
    /// access to all aspects of Htslib.
214
    pub fn inner(&self) -> &htslib::bcf1_t {
1,171✔
215
        unsafe { &*self.inner }
1,171✔
216
    }
217

218
    /// Return mutable reference to inner C struct.
219
    ///
220
    /// # Remarks
221
    ///
222
    /// Note that this function is only required as long as Rust-Htslib does not provide full
223
    /// access to all aspects of Htslib.
224
    pub fn inner_mut(&mut self) -> &mut htslib::bcf1_t {
400✔
225
        unsafe { &mut *self.inner }
400✔
226
    }
227

228
    /// Get the reference id of the record.
229
    ///
230
    /// To look up the contig name,
231
    /// use [`HeaderView::rid2name`](../header/struct.HeaderView.html#method.rid2name).
232
    ///
233
    /// # Returns
234
    ///
235
    /// - `Some(rid)` if the internal `rid` is set to a value that is not `-1`
236
    /// - `None` if the internal `rid` is set to `-1`
237
    pub fn rid(&self) -> Option<u32> {
233✔
238
        match self.inner().rid {
233✔
239
            -1 => None,
×
240
            rid => Some(rid as u32),
233✔
241
        }
242
    }
243

244
    /// Update the reference id of the record.
245
    ///
246
    /// To look up reference id for a contig name,
247
    /// use [`HeaderView::name2rid`](../header/struct.HeaderView.html#method.name2rid).
248
    ///
249
    /// # Example
250
    ///
251
    /// Example assumes we have a Record `record` from a VCF with a header containing region
252
    /// named `1`. See [module documentation](../index.html#example-writing) for how to set
253
    /// up VCF, header, and record.
254
    ///
255
    /// ```
256
    /// # use rust_htslib::bcf::{Format, Writer};
257
    /// # use rust_htslib::bcf::header::Header;
258
    /// # let mut header = Header::new();
259
    /// # let header_contig_line = r#"##contig=<ID=1,length=10>"#;
260
    /// # header.push_record(header_contig_line.as_bytes());
261
    /// # header.push_sample("test_sample".as_bytes());
262
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
263
    /// # let mut record = vcf.empty_record();
264
    /// let rid = record.header().name2rid(b"1").ok();
265
    /// record.set_rid(rid);
266
    /// assert_eq!(record.rid(), rid);
267
    /// let name = record.header().rid2name(record.rid().unwrap()).ok();
268
    /// assert_eq!(Some("1".as_bytes()), name);
269
    /// ```
270
    pub fn set_rid(&mut self, rid: Option<u32>) {
8✔
271
        match rid {
6✔
272
            Some(rid) => self.inner_mut().rid = rid as i32,
6✔
273
            None => self.inner_mut().rid = -1,
×
274
        }
275
    }
276

277
    /// Return **0-based** position
278
    pub fn pos(&self) -> i64 {
311✔
279
        self.inner().pos
311✔
280
    }
281

282
    /// Set **0-based** position
283
    pub fn set_pos(&mut self, pos: i64) {
12✔
284
        self.inner_mut().pos = pos;
12✔
285
    }
286

287
    /// Return the **0-based, exclusive** end position
288
    ///
289
    /// # Example
290
    /// ```rust
291
    /// # use rust_htslib::bcf::{Format, Header, Writer};
292
    /// # use tempfile::NamedTempFile;
293
    /// # let tmp = NamedTempFile::new().unwrap();
294
    /// # let path = tmp.path();
295
    /// # let header = Header::new();
296
    /// # let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
297
    /// # let mut record = vcf.empty_record();
298
    /// let alleles: &[&[u8]] = &[b"AGG", b"TG"];
299
    /// record.set_alleles(alleles).expect("Failed to set alleles");
300
    /// record.set_pos(5);
301
    ///
302
    /// assert_eq!(record.end(), 8)
303
    /// ```
304
    pub fn end(&self) -> i64 {
6✔
305
        self.pos() + self.rlen()
4✔
306
    }
307

308
    /// Return the value of the ID column.
309
    ///
310
    /// When empty, returns `b".".to_vec()`.
311
    pub fn id(&self) -> Vec<u8> {
4✔
312
        if self.inner().d.id.is_null() {
4✔
313
            b".".to_vec()
2✔
314
        } else {
315
            let id = unsafe { ffi::CStr::from_ptr(self.inner().d.id) };
3✔
316
            id.to_bytes().to_vec()
3✔
317
        }
318
    }
319

320
    /// Update the ID string to the given value.
321
    pub fn set_id(&mut self, id: &[u8]) -> Result<()> {
3✔
322
        let c_str = ffi::CString::new(id).unwrap();
3✔
323
        if unsafe {
4✔
324
            htslib::bcf_update_id(
3✔
325
                self.header().inner,
4✔
326
                self.inner,
3✔
327
                c_str.as_ptr() as *mut c_char,
3✔
328
            )
329
        } == 0
2✔
330
        {
331
            Ok(())
3✔
332
        } else {
333
            Err(Error::BcfSetValues)
×
334
        }
335
    }
336

337
    /// Clear the ID column (set it to `"."`).
338
    pub fn clear_id(&mut self) -> Result<()> {
2✔
339
        let c_str = ffi::CString::new(&b"."[..]).unwrap();
2✔
340
        if unsafe {
3✔
341
            htslib::bcf_update_id(
2✔
342
                self.header().inner,
3✔
343
                self.inner,
2✔
344
                c_str.as_ptr() as *mut c_char,
2✔
345
            )
346
        } == 0
1✔
347
        {
348
            Ok(())
2✔
349
        } else {
350
            Err(Error::BcfSetValues)
×
351
        }
352
    }
353

354
    /// Add the ID string (the ID field is semicolon-separated), checking for duplicates.
355
    pub fn push_id(&mut self, id: &[u8]) -> Result<()> {
3✔
356
        let c_str = ffi::CString::new(id).unwrap();
3✔
357
        if unsafe {
4✔
358
            htslib::bcf_add_id(
3✔
359
                self.header().inner,
4✔
360
                self.inner,
3✔
361
                c_str.as_ptr() as *mut c_char,
3✔
362
            )
363
        } == 0
2✔
364
        {
365
            Ok(())
3✔
366
        } else {
367
            Err(Error::BcfSetValues)
×
368
        }
369
    }
370

371
    /// Return `Filters` iterator for enumerating all filters that have been set.
372
    ///
373
    /// A record having the `PASS` filter will return an empty `Filter` here.
374
    pub fn filters(&self) -> Filters<'_> {
2✔
375
        Filters::new(self)
2✔
376
    }
377

378
    /// Query whether the filter with the given ID has been set.
379
    ///
380
    /// This method can be used to check if a record passes filtering by using either `Id(0)`,
381
    /// `PASS` or `.`
382
    ///
383
    /// # Example
384
    /// ```rust
385
    /// # use rust_htslib::bcf::{Format, Header, Writer};
386
    /// # use rust_htslib::bcf::header::Id;
387
    /// # use tempfile::NamedTempFile;
388
    /// # let tmp = tempfile::NamedTempFile::new().unwrap();
389
    /// # let path = tmp.path();
390
    /// let mut header = Header::new();
391
    /// header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
392
    /// # let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
393
    /// # let mut record = vcf.empty_record();
394
    /// assert!(record.has_filter("PASS".as_bytes()));
395
    /// assert!(record.has_filter(".".as_bytes()));
396
    /// assert!(record.has_filter(&Id(0)));
397
    ///
398
    /// record.push_filter("foo".as_bytes()).unwrap();
399
    /// assert!(record.has_filter("foo".as_bytes()));
400
    /// assert!(!record.has_filter("PASS".as_bytes()))
401
    /// ```
402
    pub fn has_filter<T: FilterId + ?Sized>(&self, flt_id: &T) -> bool {
54✔
403
        if flt_id.is_pass() && self.inner().d.n_flt == 0 {
78✔
404
            return true;
18✔
405
        }
406
        let id = match flt_id.id_from_header(self.header()) {
38✔
407
            Ok(i) => *i,
16✔
408
            Err(_) => return false,
2✔
409
        };
410
        for i in 0..(self.inner().d.n_flt as isize) {
52✔
411
            if unsafe { *self.inner().d.flt.offset(i) } == id as i32 {
52✔
412
                return true;
25✔
413
            }
414
        }
415
        false
17✔
416
    }
417

418
    /// Set the given filter IDs to the FILTER column.
419
    ///
420
    /// Setting an empty slice removes all filters and sets `PASS`.
421
    ///
422
    /// # Example
423
    /// ```rust
424
    /// # use rust_htslib::bcf::{Format, Header, Writer};
425
    /// # use rust_htslib::bcf::header::Id;
426
    /// # use tempfile::NamedTempFile;
427
    /// # let tmp = tempfile::NamedTempFile::new().unwrap();
428
    /// # let path = tmp.path();
429
    /// let mut header = Header::new();
430
    /// header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
431
    /// header.push_record(br#"##FILTER=<ID=bar,Description="a horse walks into...">"#);
432
    /// # let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
433
    /// # let mut record = vcf.empty_record();
434
    /// let foo = record.header().name_to_id(b"foo").unwrap();
435
    /// let bar = record.header().name_to_id(b"bar").unwrap();
436
    /// assert!(record.has_filter("PASS".as_bytes()));
437
    /// let mut filters = vec![&foo, &bar];
438
    /// record.set_filters(&filters).unwrap();
439
    /// assert!(record.has_filter(&foo));
440
    /// assert!(record.has_filter(&bar));
441
    /// assert!(!record.has_filter("PASS".as_bytes()));
442
    /// filters.clear();
443
    /// record.set_filters(&filters).unwrap();
444
    /// assert!(record.has_filter("PASS".as_bytes()));
445
    /// assert!(!record.has_filter("foo".as_bytes()));
446
    /// // 'baz' isn't in the header
447
    /// assert!(record.set_filters(&["baz".as_bytes()]).is_err())
448
    /// ```
449
    ///
450
    /// # Errors
451
    /// If any of the filter IDs do not exist in the header, an [`Error::BcfUnknownID`] is returned.
452
    ///
453
    pub fn set_filters<T: FilterId + ?Sized>(&mut self, flt_ids: &[&T]) -> Result<()> {
14✔
454
        let mut ids: Vec<i32> = flt_ids
23✔
455
            .iter()
456
            .map(|id| id.id_from_header(self.header()).map(|id| *id as i32))
49✔
457
            .collect::<Result<Vec<i32>>>()?;
458
        unsafe {
459
            htslib::bcf_update_filter(
460
                self.header().inner,
8✔
461
                self.inner,
4✔
462
                ids.as_mut_ptr(),
4✔
463
                ids.len() as i32,
4✔
464
            );
465
        };
466
        Ok(())
4✔
467
    }
468

469
    /// Add the given filter to the FILTER column.
470
    ///
471
    /// If `flt_id` is `PASS` or `.` then all existing filters are removed first. Otherwise,
472
    /// any existing `PASS` filter is removed.
473
    ///
474
    /// # Example
475
    /// ```rust
476
    /// # use rust_htslib::bcf::{Format, Header, Writer};
477
    /// # use tempfile::NamedTempFile;
478
    /// # let tmp = tempfile::NamedTempFile::new().unwrap();
479
    /// # let path = tmp.path();
480
    /// let mut header = Header::new();
481
    /// header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
482
    /// header.push_record(br#"##FILTER=<ID=bar,Description="dranks">"#);
483
    /// # let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
484
    /// # let mut record = vcf.empty_record();
485
    /// let foo = "foo".as_bytes();
486
    /// let bar = record.header().name_to_id(b"bar").unwrap();
487
    /// assert!(record.has_filter("PASS".as_bytes()));
488
    ///
489
    /// record.push_filter(foo).unwrap();
490
    /// record.push_filter(&bar).unwrap();
491
    /// assert!(record.has_filter(foo));
492
    /// assert!(record.has_filter(&bar));
493
    /// // filter must exist in the header
494
    /// assert!(record.push_filter("baz".as_bytes()).is_err())
495
    /// ```
496
    ///
497
    /// # Errors
498
    /// If the `flt_id` does not exist in the header, an [`Error::BcfUnknownID`] is returned.
499
    ///
500
    pub fn push_filter<T: FilterId + ?Sized>(&mut self, flt_id: &T) -> Result<()> {
16✔
501
        let id = flt_id.id_from_header(self.header())?;
27✔
502
        unsafe {
503
            htslib::bcf_add_filter(self.header().inner, self.inner, *id as i32);
5✔
504
        };
505
        Ok(())
5✔
506
    }
507

508
    /// Remove the given filter from the FILTER column.
509
    ///
510
    /// # Arguments
511
    ///
512
    /// - `flt_id` - The corresponding filter ID to remove.
513
    /// - `pass_on_empty` - Set to `PASS` when removing the last filter.
514
    ///
515
    /// # Example
516
    /// ```rust
517
    /// # use rust_htslib::bcf::{Format, Header, Writer};
518
    /// # use tempfile::NamedTempFile;
519
    /// # let tmp = tempfile::NamedTempFile::new().unwrap();
520
    /// # let path = tmp.path();
521
    /// let mut header = Header::new();
522
    /// header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
523
    /// header.push_record(br#"##FILTER=<ID=bar,Description="a horse walks into...">"#);
524
    /// # let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
525
    /// # let mut record = vcf.empty_record();
526
    /// let foo = "foo".as_bytes();
527
    /// let bar = "bar".as_bytes();
528
    /// record.set_filters(&[foo, bar]).unwrap();
529
    /// assert!(record.has_filter(foo));
530
    /// assert!(record.has_filter(bar));
531
    ///
532
    /// record.remove_filter(foo, true).unwrap();
533
    /// assert!(!record.has_filter(foo));
534
    /// assert!(record.has_filter(bar));
535
    /// // 'baz' is not in the header
536
    /// assert!(record.remove_filter("baz".as_bytes(), true).is_err());
537
    ///
538
    /// record.remove_filter(bar, true).unwrap();
539
    /// assert!(!record.has_filter(bar));
540
    /// assert!(record.has_filter("PASS".as_bytes()));
541
    /// ```
542
    ///
543
    /// # Errors
544
    /// If the `flt_id` does not exist in the header, an [`Error::BcfUnknownID`] is returned.
545
    ///
546
    pub fn remove_filter<T: FilterId + ?Sized>(
10✔
547
        &mut self,
548
        flt_id: &T,
549
        pass_on_empty: bool,
550
    ) -> Result<()> {
551
        let id = flt_id.id_from_header(self.header())?;
17✔
552
        unsafe {
553
            htslib::bcf_remove_filter(
554
                self.header().inner,
3✔
555
                self.inner,
3✔
556
                *id as i32,
3✔
557
                pass_on_empty as i32,
3✔
558
            )
559
        };
560
        Ok(())
3✔
561
    }
562

563
    /// Get alleles strings.
564
    ///
565
    /// The first allele is the reference allele.
566
    pub fn alleles(&self) -> Vec<&[u8]> {
133✔
567
        unsafe { htslib::bcf_unpack(self.inner, htslib::BCF_UN_ALL as i32) };
133✔
568
        let n = self.inner().n_allele() as usize;
133✔
569
        let dec = self.inner().d;
133✔
570
        let alleles = unsafe { slice::from_raw_parts(dec.allele, n) };
133✔
571
        (0..n)
133✔
572
            .map(|i| unsafe { ffi::CStr::from_ptr(alleles[i]).to_bytes() })
399✔
573
            .collect()
574
    }
575

576
    /// Set alleles. The first allele is the reference allele.
577
    ///
578
    /// # Example
579
    /// ```rust
580
    /// # use rust_htslib::bcf::{Format, Writer};
581
    /// # use rust_htslib::bcf::header::Header;
582
    /// #
583
    /// # // Create minimal VCF header with a single sample
584
    /// # let mut header = Header::new();
585
    /// # header.push_sample("sample".as_bytes());
586
    /// #
587
    /// # // Write uncompressed VCF to stdout with above header and get an empty record
588
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
589
    /// # let mut record = vcf.empty_record();
590
    /// assert_eq!(record.allele_count(), 0);
591
    ///
592
    /// let alleles: &[&[u8]] = &[b"A", b"TG"];
593
    /// record.set_alleles(alleles).expect("Failed to set alleles");
594
    /// assert_eq!(record.allele_count(), 2)
595
    /// ```
596
    pub fn set_alleles(&mut self, alleles: &[&[u8]]) -> Result<()> {
16✔
597
        let cstrings: Vec<ffi::CString> = alleles
14✔
598
            .iter()
599
            .map(|vec| ffi::CString::new(*vec).unwrap())
38✔
600
            .collect();
601
        let mut ptrs: Vec<*const c_char> = cstrings
19✔
602
            .iter()
603
            .map(|cstr| cstr.as_ptr() as *const c_char)
38✔
604
            .collect();
605
        if unsafe {
19✔
606
            htslib::bcf_update_alleles(
14✔
607
                self.header().inner,
19✔
608
                self.inner,
14✔
609
                ptrs.as_mut_ptr(),
14✔
610
                alleles.len() as i32,
14✔
611
            )
612
        } == 0
9✔
613
        {
614
            Ok(())
14✔
615
        } else {
616
            Err(Error::BcfSetValues)
×
617
        }
618
    }
619

620
    /// Get variant quality.
621
    pub fn qual(&self) -> f32 {
126✔
622
        self.inner().qual
126✔
623
    }
624

625
    /// Set variant quality.
626
    pub fn set_qual(&mut self, qual: f32) {
2✔
627
        self.inner_mut().qual = qual;
2✔
628
    }
629

630
    pub fn info<'a>(&'a self, tag: &'a [u8]) -> Info<'a, Buffer> {
4✔
631
        self.info_shared_buffer(tag, Buffer::new())
4✔
632
    }
633

634
    /// Get the value of the given info tag.
635
    pub fn info_shared_buffer<'a, 'b, B: BorrowMut<Buffer> + Borrow<Buffer> + 'b>(
141✔
636
        &'a self,
637
        tag: &'a [u8],
638
        buffer: B,
639
    ) -> Info<'a, B> {
640
        Info {
641
            record: self,
642
            tag,
643
            buffer,
644
        }
645
    }
646

647
    /// Get the number of samples in the record.
648
    pub fn sample_count(&self) -> u32 {
134✔
649
        self.inner().n_sample()
134✔
650
    }
651

652
    /// Get the number of alleles, including reference allele.
653
    pub fn allele_count(&self) -> u32 {
6✔
654
        self.inner().n_allele()
6✔
655
    }
656

657
    /// Add/replace genotypes in FORMAT GT tag.
658
    ///
659
    /// # Arguments
660
    ///
661
    /// - `genotypes` - a flattened, two-dimensional array of GenotypeAllele,
662
    ///                 the first dimension contains one array for each sample.
663
    ///
664
    /// # Errors
665
    ///
666
    /// Returns error if GT tag is not present in header.
667
    ///
668
    /// # Example
669
    ///
670
    /// Example assumes we have a Record `record` from a VCF with a `GT` `FORMAT` tag.
671
    /// See [module documentation](../index.html#example-writing) for how to set up
672
    /// VCF, header, and record.
673
    ///
674
    /// ```
675
    /// # use rust_htslib::bcf::{Format, Writer};
676
    /// # use rust_htslib::bcf::header::Header;
677
    /// # use rust_htslib::bcf::record::GenotypeAllele;
678
    /// # let mut header = Header::new();
679
    /// # let header_contig_line = r#"##contig=<ID=1,length=10>"#;
680
    /// # header.push_record(header_contig_line.as_bytes());
681
    /// # let header_gt_line = r#"##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">"#;
682
    /// # header.push_record(header_gt_line.as_bytes());
683
    /// # header.push_sample("test_sample".as_bytes());
684
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
685
    /// # let mut record = vcf.empty_record();
686
    /// let alleles = &[GenotypeAllele::Unphased(1), GenotypeAllele::Unphased(1)];
687
    /// record.push_genotypes(alleles);
688
    /// assert_eq!("1/1", &format!("{}", record.genotypes().unwrap().get(0)));
689
    /// ```
690
    pub fn push_genotypes(&mut self, genotypes: &[GenotypeAllele]) -> Result<()> {
8✔
691
        let encoded: Vec<i32> = genotypes.iter().map(|gt| i32::from(*gt)).collect();
23✔
692
        self.push_format_integer(b"GT", &encoded)
9✔
693
    }
694

695
    /// Get genotypes as vector of one `Genotype` per sample.
696
    ///
697
    /// # Example
698
    /// Parsing genotype field (`GT` tag) from a VCF record:
699
    /// ```
700
    /// use crate::rust_htslib::bcf::{Reader, Read};
701
    /// let mut vcf = Reader::from_path(&"test/test_string.vcf").expect("Error opening file.");
702
    /// let expected = ["./1", "1|1", "0/1", "0|1", "1|.", "1/1"];
703
    /// for (rec, exp_gt) in vcf.records().zip(expected.iter()) {
704
    ///     let mut rec = rec.expect("Error reading record.");
705
    ///     let genotypes = rec.genotypes().expect("Error reading genotypes");
706
    ///     assert_eq!(&format!("{}", genotypes.get(0)), exp_gt);
707
    /// }
708
    /// ```
709
    pub fn genotypes(&self) -> Result<Genotypes<'_, Buffer>> {
26✔
710
        self.genotypes_shared_buffer(Buffer::new())
24✔
711
    }
712

713
    /// Get genotypes as vector of one `Genotype` per sample, using a given shared buffer
714
    /// to avoid unnecessary allocations.
715
    pub fn genotypes_shared_buffer<'a, B>(&self, buffer: B) -> Result<Genotypes<'a, B>>
24✔
716
    where
717
        B: BorrowMut<Buffer> + Borrow<Buffer> + 'a,
718
    {
719
        Ok(Genotypes {
24✔
720
            encoded: self.format_shared_buffer(b"GT", buffer).integer()?,
24✔
721
        })
722
    }
723

724
    /// Retrieve data for a `FORMAT` field
725
    ///
726
    /// # Example
727
    /// *Note: some boilerplate for the example is hidden for clarity. See [module documentation](../index.html#example-writing)
728
    /// for an example of the setup used here.*
729
    ///
730
    /// ```rust
731
    /// # use rust_htslib::bcf::{Format, Writer};
732
    /// # use rust_htslib::bcf::header::Header;
733
    /// #
734
    /// # // Create minimal VCF header with a single sample
735
    /// # let mut header = Header::new();
736
    /// header.push_sample(b"sample1").push_sample(b"sample2").push_record(br#"##FORMAT=<ID=DP,Number=1,Type=Integer,Description="Read Depth">"#);
737
    /// #
738
    /// # // Write uncompressed VCF to stdout with above header and get an empty record
739
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
740
    /// # let mut record = vcf.empty_record();
741
    /// record.push_format_integer(b"DP", &[20, 12]).expect("Failed to set DP format field");
742
    ///
743
    /// let read_depths = record.format(b"DP").integer().expect("Couldn't retrieve DP field");
744
    /// let sample1_depth = read_depths[0];
745
    /// assert_eq!(sample1_depth, &[20]);
746
    /// let sample2_depth = read_depths[1];
747
    /// assert_eq!(sample2_depth, &[12])
748
    /// ```
749
    ///
750
    /// # Errors
751
    /// **Attention:** the returned [`BufferBacked`] from [`integer()`](Format::integer)
752
    /// (`read_depths`), which holds the data, has to be kept in scope as long as the data is
753
    /// accessed. If parts of the data are accessed after the `BufferBacked` object is been
754
    /// dropped, you will access unallocated memory.
755
    pub fn format<'a>(&'a self, tag: &'a [u8]) -> Format<'a, Buffer> {
142✔
756
        self.format_shared_buffer(tag, Buffer::new())
142✔
757
    }
758

759
    /// Get the value of the given format tag for each sample.
760
    pub fn format_shared_buffer<'a, 'b, B: BorrowMut<Buffer> + Borrow<Buffer> + 'b>(
172✔
761
        &'a self,
762
        tag: &'a [u8],
763
        buffer: B,
764
    ) -> Format<'a, B> {
765
        Format::new(self, tag, buffer)
172✔
766
    }
767

768
    /// Add/replace an integer-typed FORMAT tag.
769
    ///
770
    /// # Arguments
771
    ///
772
    /// - `tag` - The tag's string.
773
    /// - `data` - a flattened, two-dimensional array, the first dimension contains one array
774
    ///            for each sample.
775
    ///
776
    /// # Errors
777
    ///
778
    /// Returns error if tag is not present in header.
779
    pub fn push_format_integer(&mut self, tag: &[u8], data: &[i32]) -> Result<()> {
9✔
780
        self.push_format(tag, data, htslib::BCF_HT_INT)
9✔
781
    }
782

783
    /// Add/replace a float-typed FORMAT tag.
784
    ///
785
    /// # Arguments
786
    ///
787
    /// - `tag` - The tag's string.
788
    /// - `data` - a flattened, two-dimensional array, the first dimension contains one array
789
    ///            for each sample.
790
    ///
791
    /// # Errors
792
    ///
793
    /// Returns error if tag is not present in header.
794
    ///
795
    /// # Example
796
    ///
797
    /// Example assumes we have a Record `record` from a VCF with an `AF` `FORMAT` tag.
798
    /// See [module documentation](../index.html#example-writing) for how to set up
799
    /// VCF, header, and record.
800
    ///
801
    /// ```
802
    /// # use rust_htslib::bcf::{Format, Writer};
803
    /// # use rust_htslib::bcf::header::Header;
804
    /// # use rust_htslib::bcf::record::GenotypeAllele;
805
    /// # let mut header = Header::new();
806
    /// # let header_contig_line = r#"##contig=<ID=1,length=10>"#;
807
    /// # header.push_record(header_contig_line.as_bytes());
808
    /// # let header_af_line = r#"##FORMAT=<ID=AF,Number=1,Type=Float,Description="Frequency">"#;
809
    /// # header.push_record(header_af_line.as_bytes());
810
    /// # header.push_sample("test_sample".as_bytes());
811
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
812
    /// # let mut record = vcf.empty_record();
813
    /// record.push_format_float(b"AF", &[0.5]);
814
    /// assert_eq!(0.5, record.format(b"AF").float().unwrap()[0][0]);
815
    /// ```
816
    pub fn push_format_float(&mut self, tag: &[u8], data: &[f32]) -> Result<()> {
6✔
817
        self.push_format(tag, data, htslib::BCF_HT_REAL)
4✔
818
    }
819

820
    /// Add/replace a single-char-typed FORMAT tag.
821
    ///
822
    /// # Arguments
823
    ///
824
    /// - `tag` - The tag's string.
825
    /// - `data` - a flattened, two-dimensional array, the first dimension contains one array
826
    ///            for each sample.
827
    ///
828
    /// # Errors
829
    ///
830
    /// Returns error if tag is not present in header.
831
    pub fn push_format_char(&mut self, tag: &[u8], data: &[u8]) -> Result<()> {
2✔
832
        self.push_format(tag, data, htslib::BCF_HT_STR)
2✔
833
    }
834

835
    /// Add a format tag. Data is a flattened two-dimensional array.
836
    /// The first dimension contains one array for each sample.
837
    fn push_format<T>(&mut self, tag: &[u8], data: &[T], ht: u32) -> Result<()> {
15✔
838
        let tag_c_str = ffi::CString::new(tag).unwrap();
15✔
839
        unsafe {
840
            if htslib::bcf_update_format(
22✔
841
                self.header().inner,
22✔
842
                self.inner,
15✔
843
                tag_c_str.as_ptr() as *mut c_char,
15✔
844
                data.as_ptr() as *const ::std::os::raw::c_void,
15✔
845
                data.len() as i32,
15✔
846
                ht as i32,
8✔
847
            ) == 0
8✔
848
            {
849
                Ok(())
15✔
850
            } else {
851
                Err(Error::BcfSetTag {
×
852
                    tag: str::from_utf8(tag).unwrap().to_owned(),
×
853
                })
854
            }
855
        }
856
    }
857

858
    // TODO: should we add convenience methods clear_format_*?
859

860
    /// Add a string-typed FORMAT tag. Note that genotypes are treated as a special case
861
    /// and cannot be added with this method. See instead [push_genotypes](#method.push_genotypes).
862
    ///
863
    /// # Arguments
864
    ///
865
    /// - `tag` - The tag's string.
866
    /// - `data` - a two-dimensional array, the first dimension contains one array
867
    ///            for each sample. Must be non-empty.
868
    ///
869
    /// # Errors
870
    ///
871
    /// Returns error if tag is not present in header.
872
    pub fn push_format_string<D: Borrow<[u8]>>(&mut self, tag: &[u8], data: &[D]) -> Result<()> {
2✔
873
        assert!(
2✔
874
            !data.is_empty(),
1✔
UNCOV
875
            "given string data must have at least 1 element"
×
876
        );
877
        let c_data = data
2✔
878
            .iter()
879
            .map(|s| ffi::CString::new(s.borrow()).unwrap())
5✔
880
            .collect::<Vec<ffi::CString>>();
881
        let c_ptrs = c_data
3✔
882
            .iter()
883
            .map(|s| s.as_ptr() as *mut i8)
5✔
884
            .collect::<Vec<*mut i8>>();
885
        let tag_c_str = ffi::CString::new(tag).unwrap();
3✔
886
        unsafe {
887
            if htslib::bcf_update_format_string(
3✔
888
                self.header().inner,
3✔
889
                self.inner,
2✔
890
                tag_c_str.as_ptr() as *mut c_char,
2✔
891
                c_ptrs.as_slice().as_ptr() as *mut *const c_char,
2✔
892
                data.len() as i32,
2✔
893
            ) == 0
1✔
894
            {
895
                Ok(())
2✔
896
            } else {
897
                Err(Error::BcfSetTag {
×
898
                    tag: str::from_utf8(tag).unwrap().to_owned(),
×
899
                })
900
            }
901
        }
902
    }
903

904
    /// Add/replace an integer-typed INFO entry.
905
    pub fn push_info_integer(&mut self, tag: &[u8], data: &[i32]) -> Result<()> {
2✔
906
        self.push_info(tag, data, htslib::BCF_HT_INT)
2✔
907
    }
908

909
    /// Remove the integer-typed INFO entry.
910
    pub fn clear_info_integer(&mut self, tag: &[u8]) -> Result<()> {
×
911
        self.push_info::<i32>(tag, &[], htslib::BCF_HT_INT)
×
912
    }
913

914
    /// Add/replace a float-typed INFO entry.
915
    pub fn push_info_float(&mut self, tag: &[u8], data: &[f32]) -> Result<()> {
2✔
916
        self.push_info(tag, data, htslib::BCF_HT_REAL)
2✔
917
    }
918

919
    /// Remove the float-typed INFO entry.
920
    pub fn clear_info_float(&mut self, tag: &[u8]) -> Result<()> {
×
921
        self.push_info::<u8>(tag, &[], htslib::BCF_HT_REAL)
×
922
    }
923

924
    /// Add/replace an INFO tag.
925
    ///
926
    /// # Arguments
927
    /// * `tag` - the tag to add/replace
928
    /// * `data` - the data to set
929
    /// * `ht` - the HTSLib type to use
930
    fn push_info<T>(&mut self, tag: &[u8], data: &[T], ht: u32) -> Result<()> {
4✔
931
        let tag_c_str = ffi::CString::new(tag).unwrap();
4✔
932
        unsafe {
933
            if htslib::bcf_update_info(
6✔
934
                self.header().inner,
6✔
935
                self.inner,
4✔
936
                tag_c_str.as_ptr() as *mut c_char,
4✔
937
                data.as_ptr() as *const ::std::os::raw::c_void,
4✔
938
                data.len() as i32,
4✔
939
                ht as i32,
2✔
940
            ) == 0
2✔
941
            {
942
                Ok(())
4✔
943
            } else {
944
                Err(Error::BcfSetTag {
×
945
                    tag: str::from_utf8(tag).unwrap().to_owned(),
×
946
                })
947
            }
948
        }
949
    }
950

951
    /// Set flag into the INFO column.
952
    pub fn push_info_flag(&mut self, tag: &[u8]) -> Result<()> {
2✔
953
        self.push_info_string_impl(tag, &[b""], htslib::BCF_HT_FLAG)
2✔
954
    }
955

956
    /// Remove the flag from the INFO column.
957
    pub fn clear_info_flag(&mut self, tag: &[u8]) -> Result<()> {
×
958
        self.push_info_string_impl(tag, &[], htslib::BCF_HT_FLAG)
×
959
    }
960

961
    /// Add/replace a string-typed INFO entry.
962
    pub fn push_info_string(&mut self, tag: &[u8], data: &[&[u8]]) -> Result<()> {
2✔
963
        self.push_info_string_impl(tag, data, htslib::BCF_HT_STR)
2✔
964
    }
965

966
    /// Remove the string field from the INFO column.
967
    pub fn clear_info_string(&mut self, tag: &[u8]) -> Result<()> {
×
968
        self.push_info_string_impl(tag, &[], htslib::BCF_HT_STR)
×
969
    }
970

971
    /// Add an string-valued INFO tag.
972
    fn push_info_string_impl(&mut self, tag: &[u8], data: &[&[u8]], ht: u32) -> Result<()> {
3✔
973
        let mut buf: Vec<u8> = Vec::new();
3✔
974
        for (i, &s) in data.iter().enumerate() {
6✔
975
            if i > 0 {
1✔
976
                buf.extend(b",");
×
977
            }
978
            buf.extend(s);
2✔
979
        }
980
        let c_str = ffi::CString::new(buf).unwrap();
3✔
981
        let len = if ht == htslib::BCF_HT_FLAG {
5✔
982
            data.len()
2✔
983
        } else {
984
            c_str.to_bytes().len()
3✔
985
        };
986
        let tag_c_str = ffi::CString::new(tag).unwrap();
3✔
987
        unsafe {
988
            if htslib::bcf_update_info(
4✔
989
                self.header().inner,
4✔
990
                self.inner,
3✔
991
                tag_c_str.as_ptr() as *mut c_char,
3✔
992
                c_str.as_ptr() as *const ::std::os::raw::c_void,
3✔
993
                len as i32,
3✔
994
                ht as i32,
2✔
995
            ) == 0
2✔
996
            {
997
                Ok(())
3✔
998
            } else {
999
                Err(Error::BcfSetTag {
×
1000
                    tag: str::from_utf8(tag).unwrap().to_owned(),
×
1001
                })
1002
            }
1003
        }
1004
    }
1005

1006
    /// Remove unused alleles.
1007
    pub fn trim_alleles(&mut self) -> Result<()> {
63✔
1008
        match unsafe { htslib::bcf_trim_alleles(self.header().inner, self.inner) } {
63✔
1009
            -1 => Err(Error::BcfRemoveAlleles),
×
1010
            _ => Ok(()),
63✔
1011
        }
1012
    }
1013

1014
    pub fn remove_alleles(&mut self, remove: &[bool]) -> Result<()> {
2✔
1015
        let rm_set = unsafe { htslib::kbs_init(remove.len()) };
2✔
1016

1017
        for (i, &r) in remove.iter().enumerate() {
6✔
1018
            if r {
1✔
1019
                unsafe {
1020
                    htslib::kbs_insert(rm_set, i as i32);
2✔
1021
                }
1022
            }
1023
        }
1024

1025
        let ret = unsafe { htslib::bcf_remove_allele_set(self.header().inner, self.inner, rm_set) };
2✔
1026

1027
        unsafe {
1028
            htslib::kbs_destroy(rm_set);
2✔
1029
        }
1030

1031
        match ret {
2✔
1032
            -1 => Err(Error::BcfRemoveAlleles),
×
1033
            _ => Ok(()),
2✔
1034
        }
1035
    }
1036

1037
    /// Get the length of the reference allele. If the record has no reference allele, then the
1038
    /// result will be `0`.
1039
    ///
1040
    /// # Example
1041
    /// ```rust
1042
    /// # use rust_htslib::bcf::{Format, Writer};
1043
    /// # use rust_htslib::bcf::header::Header;
1044
    /// #
1045
    /// # // Create minimal VCF header with a single sample
1046
    /// # let mut header = Header::new();
1047
    /// # header.push_sample("sample".as_bytes());
1048
    /// #
1049
    /// # // Write uncompressed VCF to stdout with above header and get an empty record
1050
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
1051
    /// # let mut record = vcf.empty_record();
1052
    /// # assert_eq!(record.rlen(), 0);
1053
    /// let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1054
    /// record.set_alleles(alleles).expect("Failed to set alleles");
1055
    /// assert_eq!(record.rlen(), 3)
1056
    /// ```
1057
    pub fn rlen(&self) -> i64 {
14✔
1058
        self.inner().rlen
12✔
1059
    }
1060

1061
    /// Clear all parts of the record. Useful if you plan to reuse a record object multiple times.
1062
    ///
1063
    /// # Example
1064
    /// ```rust
1065
    /// # use rust_htslib::bcf::{Format, Writer};
1066
    /// # use rust_htslib::bcf::header::Header;
1067
    /// #
1068
    /// # // Create minimal VCF header with a single sample
1069
    /// # let mut header = Header::new();
1070
    /// # header.push_sample("sample".as_bytes());
1071
    /// #
1072
    /// # // Write uncompressed VCF to stdout with above header and get an empty record
1073
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
1074
    /// # let mut record = vcf.empty_record();
1075
    /// let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1076
    /// record.set_alleles(alleles).expect("Failed to set alleles");
1077
    /// record.set_pos(6);
1078
    /// record.clear();
1079
    /// assert_eq!(record.rlen(), 0);
1080
    /// assert_eq!(record.pos(), 0)
1081
    /// ```
1082
    pub fn clear(&self) {
6✔
1083
        unsafe { htslib::bcf_clear(self.inner) }
4✔
1084
    }
1085

1086
    /// Provide short description of record for locating it in the BCF/VCF file.
1087
    pub fn desc(&self) -> String {
×
1088
        if let Some(rid) = self.rid() {
×
1089
            if let Ok(contig) = self.header.rid2name(rid) {
×
1090
                return format!("{}:{}", str::from_utf8(contig).unwrap(), self.pos());
1091
            }
1092
        }
1093
        "".to_owned()
×
1094
    }
1095

1096
    /// Convert to VCF String
1097
    ///
1098
    /// Intended for debug only. Use Writer for efficient VCF output.
1099
    ///
1100
    pub fn to_vcf_string(&self) -> Result<String> {
3✔
1101
        let mut buf = htslib::kstring_t {
1102
            l: 0,
1103
            m: 0,
1104
            s: ptr::null_mut(),
3✔
1105
        };
1106
        let ret = unsafe { htslib::vcf_format(self.header().inner, self.inner, &mut buf) };
3✔
1107

1108
        if ret < 0 {
3✔
1109
            if !buf.s.is_null() {
2✔
1110
                unsafe {
1111
                    libc::free(buf.s as *mut libc::c_void);
×
1112
                }
1113
            }
1114
            return Err(Error::BcfToString);
2✔
1115
        }
1116

1117
        let vcf_str = unsafe {
1118
            let vcf_str = String::from(ffi::CStr::from_ptr(buf.s).to_str().unwrap());
1✔
1119
            if !buf.s.is_null() {
3✔
1120
                libc::free(buf.s as *mut libc::c_void);
2✔
1121
            }
1122
            vcf_str
1✔
1123
        };
1124

1125
        Ok(vcf_str)
1✔
1126
    }
1127
}
1128

1129
impl Clone for Record {
1130
    fn clone(&self) -> Self {
2✔
1131
        let inner = unsafe { htslib::bcf_dup(self.inner) };
2✔
1132
        Record {
1133
            inner,
1134
            header: self.header.clone(),
2✔
1135
        }
1136
    }
1137
}
1138

1139
impl genome::AbstractLocus for Record {
1140
    fn contig(&self) -> &str {
×
1141
        str::from_utf8(
1142
            self.header()
×
1143
                .rid2name(self.rid().expect("rid not set"))
×
1144
                .expect("unable to find rid in header"),
1145
        )
1146
        .expect("unable to interpret contig name as UTF-8")
1147
    }
1148

1149
    fn pos(&self) -> u64 {
×
1150
        self.pos() as u64
×
1151
    }
1152
}
1153

1154
/// Phased or unphased alleles, represented as indices.
1155
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1156
pub enum GenotypeAllele {
1157
    Unphased(i32),
1158
    Phased(i32),
1159
    UnphasedMissing,
1160
    PhasedMissing,
1161
}
1162

1163
impl GenotypeAllele {
1164
    /// Decode given integer according to BCF standard.
1165
    #[deprecated(
1166
        since = "0.36.0",
1167
        note = "Please use the conversion trait From<i32> for GenotypeAllele instead."
1168
    )]
1169
    pub fn from_encoded(encoded: i32) -> Self {
×
1170
        match (encoded, encoded & 1) {
×
1171
            (0, 0) => GenotypeAllele::UnphasedMissing,
×
1172
            (1, 1) => GenotypeAllele::PhasedMissing,
×
1173
            (e, 1) => GenotypeAllele::Phased((e >> 1) - 1),
×
1174
            (e, 0) => GenotypeAllele::Unphased((e >> 1) - 1),
×
1175
            _ => panic!("unexpected phasing type"),
×
1176
        }
1177
    }
1178

1179
    /// Get the index into the list of alleles.
1180
    pub fn index(self) -> Option<u32> {
58✔
1181
        match self {
65✔
1182
            GenotypeAllele::Unphased(i) | GenotypeAllele::Phased(i) => Some(i as u32),
105✔
1183
            GenotypeAllele::UnphasedMissing | GenotypeAllele::PhasedMissing => None,
10✔
1184
        }
1185
    }
1186
}
1187

1188
impl fmt::Display for GenotypeAllele {
1189
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29✔
1190
        match self.index() {
29✔
1191
            Some(a) => write!(f, "{}", a),
25✔
1192
            None => write!(f, "."),
6✔
1193
        }
1194
    }
1195
}
1196

1197
impl From<GenotypeAllele> for i32 {
1198
    fn from(allele: GenotypeAllele) -> i32 {
13✔
1199
        let (allele, phased) = match allele {
26✔
1200
            GenotypeAllele::UnphasedMissing => (-1, 0),
×
1201
            GenotypeAllele::PhasedMissing => (-1, 1),
2✔
1202
            GenotypeAllele::Unphased(a) => (a, 0),
10✔
1203
            GenotypeAllele::Phased(a) => (a, 1),
4✔
1204
        };
1205
        ((allele + 1) << 1) | phased
13✔
1206
    }
1207
}
1208

1209
impl From<i32> for GenotypeAllele {
1210
    fn from(encoded: i32) -> GenotypeAllele {
60✔
1211
        match (encoded, encoded & 1) {
60✔
1212
            (0, 0) => GenotypeAllele::UnphasedMissing,
7✔
1213
            (1, 1) => GenotypeAllele::PhasedMissing,
7✔
1214
            (e, 1) => GenotypeAllele::Phased((e >> 1) - 1),
12✔
1215
            (e, 0) => GenotypeAllele::Unphased((e >> 1) - 1),
50✔
1216
            _ => panic!("unexpected phasing type"),
×
1217
        }
1218
    }
1219
}
1220

1221
custom_derive! {
1222
    /// Genotype representation as a vector of `GenotypeAllele`.
1223
    #[derive(NewtypeDeref, Debug, Clone, PartialEq, Eq, Hash)]
1224
    pub struct Genotype(Vec<GenotypeAllele>);
1225
}
1226

1227
impl fmt::Display for Genotype {
1228
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16✔
1229
        let Genotype(alleles) = self;
16✔
1230
        write!(f, "{}", alleles[0])?;
16✔
1231
        for a in &alleles[1..] {
32✔
1232
            let sep = match a {
29✔
1233
                GenotypeAllele::Phased(_) | GenotypeAllele::PhasedMissing => '|',
8✔
1234
                GenotypeAllele::Unphased(_) | GenotypeAllele::UnphasedMissing => '/',
10✔
1235
            };
1236
            write!(f, "{}{}", sep, a)?;
16✔
1237
        }
1238
        Ok(())
16✔
1239
    }
1240
}
1241

1242
/// Lazy representation of genotypes, that does no computation until a particular genotype is queried.
1243
#[derive(Debug)]
1244
pub struct Genotypes<'a, B>
1245
where
1246
    B: Borrow<Buffer> + 'a,
1247
{
1248
    encoded: BufferBacked<'a, Vec<&'a [i32]>, B>,
1249
}
1250

1251
impl<'a, B: Borrow<Buffer> + 'a> Genotypes<'a, B> {
1252
    /// Get genotype of ith sample. So far, only supports diploid genotypes.
1253
    ///
1254
    /// Note that the result complies with the BCF spec. This means that the
1255
    /// first allele will always be marked as `Unphased`. That is, if you have 1|1 in the VCF,
1256
    /// this method will return `[Unphased(1), Phased(1)]`.
1257
    pub fn get(&self, i: usize) -> Genotype {
31✔
1258
        let igt = self.encoded[i];
31✔
1259
        Genotype(igt.iter().map(|&e| GenotypeAllele::from(e)).collect())
120✔
1260
    }
1261
}
1262

1263
impl Drop for Record {
1264
    fn drop(&mut self) {
438✔
1265
        unsafe { htslib::bcf_destroy(self.inner) };
438✔
1266
    }
1267
}
1268

1269
unsafe impl Send for Record {}
1270

1271
unsafe impl Sync for Record {}
1272

1273
/// Info tag representation.
1274
#[derive(Debug)]
1275
pub struct Info<'a, B: BorrowMut<Buffer> + Borrow<Buffer>> {
1276
    record: &'a Record,
1277
    tag: &'a [u8],
1278
    buffer: B,
1279
}
1280

1281
pub type BufferBackedOption<'b, B> = Option<BufferBacked<'b, Vec<&'b [u8]>, B>>;
1282

1283
impl<'b, B: BorrowMut<Buffer> + Borrow<Buffer> + 'b> Info<'_, B> {
1284
    /// Short description of info tag.
1285
    pub fn desc(&self) -> String {
×
1286
        str::from_utf8(self.tag).unwrap().to_owned()
×
1287
    }
1288

1289
    fn data(&mut self, data_type: u32) -> Result<Option<i32>> {
141✔
1290
        let mut n: i32 = self.buffer.borrow().len;
141✔
1291
        let c_str = ffi::CString::new(self.tag).unwrap();
141✔
1292
        let ret = unsafe {
1293
            htslib::bcf_get_info_values(
1294
                self.record.header().inner,
143✔
1295
                self.record.inner,
141✔
1296
                c_str.as_ptr() as *mut c_char,
141✔
1297
                &mut self.buffer.borrow_mut().inner,
141✔
1298
                &mut n,
139✔
1299
                data_type as i32,
139✔
1300
            )
1301
        };
1302
        self.buffer.borrow_mut().len = n;
141✔
1303

1304
        match ret {
141✔
1305
            -1 => Err(Error::BcfUndefinedTag { tag: self.desc() }),
×
1306
            -2 => Err(Error::BcfUnexpectedType { tag: self.desc() }),
×
1307
            -3 => Ok(None),
×
1308
            ret => Ok(Some(ret)),
141✔
1309
        }
1310
    }
1311

1312
    /// Get integers from tag. `None` if tag not present in record.
1313
    ///
1314
    /// Import `bcf::record::Numeric` for missing value handling.
1315
    ///
1316
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1317
    /// as along as the data is accessed. If parts of the data are accessed while
1318
    /// the BufferBacked object is already dropped, you will access unallocated
1319
    /// memory.
1320
    pub fn integer(mut self) -> Result<Option<BufferBacked<'b, &'b [i32], B>>> {
2✔
1321
        self.data(htslib::BCF_HT_INT).map(|data| {
5✔
1322
            data.map(|ret| {
4✔
1323
                let values = unsafe {
2✔
1324
                    slice::from_raw_parts(self.buffer.borrow().inner as *const i32, ret as usize)
3✔
1325
                };
1326
                BufferBacked::new(&values[..ret as usize], self.buffer)
2✔
1327
            })
1328
        })
1329
    }
1330

1331
    /// Get floats from tag. `None` if tag not present in record.
1332
    ///
1333
    /// Import `bcf::record::Numeric` for missing value handling.
1334
    ///
1335
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1336
    /// as along as the data is accessed. If parts of the data are accessed while
1337
    /// the BufferBacked object is already dropped, you will access unallocated
1338
    /// memory.
1339
    pub fn float(mut self) -> Result<Option<BufferBacked<'b, &'b [f32], B>>> {
129✔
1340
        self.data(htslib::BCF_HT_REAL).map(|data| {
259✔
1341
            data.map(|ret| {
258✔
1342
                let values = unsafe {
129✔
1343
                    slice::from_raw_parts(self.buffer.borrow().inner as *const f32, ret as usize)
130✔
1344
                };
1345
                BufferBacked::new(&values[..ret as usize], self.buffer)
129✔
1346
            })
1347
        })
1348
    }
1349

1350
    /// Get flags from tag. `false` if not set.
1351
    pub fn flag(&mut self) -> Result<bool> {
×
1352
        self.data(htslib::BCF_HT_FLAG).map(|data| match data {
×
1353
            Some(ret) => ret == 1,
×
1354
            None => false,
×
1355
        })
1356
    }
1357

1358
    /// Get strings from tag. `None` if tag not present in record.
1359
    ///
1360
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1361
    /// as along as the data is accessed. If parts of the data are accessed while
1362
    /// the BufferBacked object is already dropped, you will access unallocated
1363
    /// memory.
1364
    pub fn string(mut self) -> Result<BufferBackedOption<'b, B>> {
12✔
1365
        self.data(htslib::BCF_HT_STR).map(|data| {
26✔
1366
            data.map(|ret| {
24✔
1367
                BufferBacked::new(
12✔
1368
                    unsafe {
14✔
1369
                        slice::from_raw_parts(self.buffer.borrow().inner as *const u8, ret as usize)
14✔
1370
                    }
1371
                    .split(|c| *c == b',')
1,889✔
1372
                    .map(|s| {
36✔
1373
                        // stop at zero character
1374
                        s.split(|c| *c == 0u8)
1,891✔
1375
                            .next()
24✔
1376
                            .expect("Bug: returned string should not be empty.")
24✔
1377
                    })
1378
                    .collect(),
10✔
1379
                    self.buffer,
12✔
1380
                )
1381
            })
1382
        })
1383
    }
1384
}
1385

1386
unsafe impl<B: BorrowMut<Buffer> + Borrow<Buffer>> Send for Info<'_, B> {}
1387

1388
unsafe impl<B: BorrowMut<Buffer> + Borrow<Buffer>> Sync for Info<'_, B> {}
1389

1390
fn trim_slice<T: PartialEq + NumericUtils>(s: &[T]) -> &[T] {
183✔
1391
    s.split(|v| v.is_vector_end())
686✔
1392
        .next()
1393
        .expect("Bug: returned slice should not be empty.")
1394
}
1395

1396
// Representation of per-sample data.
1397
#[derive(Debug)]
1398
pub struct Format<'a, B: BorrowMut<Buffer> + Borrow<Buffer>> {
1399
    record: &'a Record,
1400
    tag: &'a [u8],
1401
    inner: *mut htslib::bcf_fmt_t,
1402
    buffer: B,
1403
}
1404

1405
impl<'a, 'b, B: BorrowMut<Buffer> + Borrow<Buffer> + 'b> Format<'a, B> {
1406
    /// Create new format data in a given record.
1407
    fn new(record: &'a Record, tag: &'a [u8], buffer: B) -> Format<'a, B> {
172✔
1408
        let c_str = ffi::CString::new(tag).unwrap();
179✔
1409
        let inner = unsafe {
1410
            htslib::bcf_get_fmt(
1411
                record.header().inner,
179✔
1412
                record.inner,
172✔
1413
                c_str.as_ptr() as *mut c_char,
172✔
1414
            )
1415
        };
1416
        Format {
1417
            record,
1418
            tag,
1419
            inner,
1420
            buffer,
1421
        }
1422
    }
1423

1424
    /// Provide short description of format entry (just the tag name).
1425
    pub fn desc(&self) -> String {
×
1426
        str::from_utf8(self.tag).unwrap().to_owned()
×
1427
    }
1428

1429
    pub fn inner(&self) -> &htslib::bcf_fmt_t {
171✔
1430
        unsafe { &*self.inner }
171✔
1431
    }
1432

1433
    pub fn inner_mut(&mut self) -> &mut htslib::bcf_fmt_t {
×
1434
        unsafe { &mut *self.inner }
×
1435
    }
1436

1437
    fn values_per_sample(&self) -> usize {
171✔
1438
        self.inner().n as usize
171✔
1439
    }
1440

1441
    /// Read and decode format data into a given type.
1442
    fn data(&mut self, data_type: u32) -> Result<i32> {
172✔
1443
        let mut n: i32 = self.buffer.borrow().len;
172✔
1444
        let c_str = ffi::CString::new(self.tag).unwrap();
172✔
1445
        let ret = unsafe {
1446
            htslib::bcf_get_format_values(
1447
                self.record.header().inner,
179✔
1448
                self.record.inner,
172✔
1449
                c_str.as_ptr() as *mut c_char,
172✔
1450
                &mut self.buffer.borrow_mut().inner,
172✔
1451
                &mut n,
165✔
1452
                data_type as i32,
165✔
1453
            )
1454
        };
1455
        self.buffer.borrow_mut().len = n;
172✔
1456
        match ret {
172✔
1457
            -1 => Err(Error::BcfUndefinedTag { tag: self.desc() }),
×
1458
            -2 => Err(Error::BcfUnexpectedType { tag: self.desc() }),
×
1459
            -3 => Err(Error::BcfMissingTag {
×
1460
                tag: self.desc(),
×
1461
                record: self.record.desc(),
×
1462
            }),
1463
            ret => Ok(ret),
172✔
1464
        }
1465
    }
1466

1467
    /// Get format data as integers.
1468
    ///
1469
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1470
    /// as long as the data is accessed. If parts of the data are accessed while
1471
    /// the BufferBacked object is already dropped, you will access unallocated
1472
    /// memory.
1473
    pub fn integer(mut self) -> Result<BufferBacked<'b, Vec<&'b [i32]>, B>> {
153✔
1474
        self.data(htslib::BCF_HT_INT).map(|ret| {
311✔
1475
            BufferBacked::new(
153✔
1476
                unsafe {
158✔
1477
                    slice::from_raw_parts(
153✔
1478
                        self.buffer.borrow_mut().inner as *const i32,
158✔
1479
                        ret as usize,
153✔
1480
                    )
1481
                }
1482
                .chunks(self.values_per_sample())
153✔
1483
                .map(trim_slice)
148✔
1484
                .collect(),
148✔
1485
                self.buffer,
153✔
1486
            )
1487
        })
1488
    }
1489

1490
    /// Get format data as floats.
1491
    ///
1492
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1493
    /// as along as the data is accessed. If parts of the data are accessed while
1494
    /// the BufferBacked object is already dropped, you will access unallocated
1495
    /// memory.
1496
    pub fn float(mut self) -> Result<BufferBacked<'b, Vec<&'b [f32]>, B>> {
6✔
1497
        self.data(htslib::BCF_HT_REAL).map(|ret| {
14✔
1498
            BufferBacked::new(
6✔
1499
                unsafe {
8✔
1500
                    slice::from_raw_parts(
6✔
1501
                        self.buffer.borrow_mut().inner as *const f32,
8✔
1502
                        ret as usize,
6✔
1503
                    )
1504
                }
1505
                .chunks(self.values_per_sample())
6✔
1506
                .map(trim_slice)
4✔
1507
                .collect(),
4✔
1508
                self.buffer,
6✔
1509
            )
1510
        })
1511
    }
1512

1513
    /// Get format data as byte slices. To obtain the values strings, use `std::str::from_utf8`.
1514
    ///
1515
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1516
    /// as along as the data is accessed. If parts of the data are accessed while
1517
    /// the BufferBacked object is already dropped, you will access unallocated
1518
    /// memory.
1519
    pub fn string(mut self) -> Result<BufferBacked<'b, Vec<&'b [u8]>, B>> {
15✔
1520
        self.data(htslib::BCF_HT_STR).map(|ret| {
32✔
1521
            if ret == 0 {
15✔
1522
                return BufferBacked::new(Vec::new(), self.buffer);
3✔
1523
            }
1524
            BufferBacked::new(
14✔
1525
                unsafe {
14✔
1526
                    slice::from_raw_parts(self.buffer.borrow_mut().inner as *const u8, ret as usize)
16✔
1527
                }
1528
                .chunks(self.values_per_sample())
14✔
1529
                .map(|s| {
38✔
1530
                    // stop at zero character
1531
                    s.split(|c| *c == 0u8)
210✔
1532
                        .next()
24✔
1533
                        .expect("Bug: returned string should not be empty.")
24✔
1534
                })
1535
                .collect(),
×
1536
                self.buffer,
2✔
1537
            )
1538
        })
1539
    }
1540
}
1541

1542
unsafe impl<B: BorrowMut<Buffer> + Borrow<Buffer>> Send for Format<'_, B> {}
1543

1544
unsafe impl<B: BorrowMut<Buffer> + Borrow<Buffer>> Sync for Format<'_, B> {}
1545

1546
#[derive(Debug)]
1547
pub struct Filters<'a> {
1548
    /// Reference to the `Record` to enumerate records for.
1549
    record: &'a Record,
1550
    /// Index of the next filter to return, if not at end.
1551
    idx: i32,
1552
}
1553

1554
impl<'a> Filters<'a> {
1555
    pub fn new(record: &'a Record) -> Self {
2✔
1556
        Filters { record, idx: 0 }
1557
    }
1558
}
1559

1560
impl Iterator for Filters<'_> {
1561
    type Item = Id;
1562

1563
    fn next(&mut self) -> Option<Id> {
2✔
1564
        if self.record.inner().d.n_flt <= self.idx {
3✔
1565
            None
2✔
1566
        } else {
1567
            let i = self.idx as isize;
×
1568
            self.idx += 1;
×
1569
            Some(Id(unsafe { *self.record.inner().d.flt.offset(i) } as u32))
×
1570
        }
1571
    }
1572
}
1573

1574
#[cfg(test)]
1575
mod tests {
1576
    use super::*;
1577
    use crate::bcf::{Format, Header, Writer};
1578
    use tempfile::NamedTempFile;
1579

1580
    #[test]
1581
    fn test_missing_float() {
1582
        let expected: u32 = 0x7F80_0001;
1583
        assert_eq!(MISSING_FLOAT.bits(), expected);
1584
    }
1585

1586
    #[test]
1587
    fn test_vector_end_float() {
1588
        let expected: u32 = 0x7F80_0002;
1589
        assert_eq!(VECTOR_END_FLOAT.bits(), expected);
1590
    }
1591

1592
    #[test]
1593
    fn test_record_rlen() {
1594
        let tmp = NamedTempFile::new().unwrap();
1595
        let path = tmp.path();
1596
        let header = Header::new();
1597
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1598
        let mut record = vcf.empty_record();
1599
        assert_eq!(record.rlen(), 0);
1600
        let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1601
        record.set_alleles(alleles).expect("Failed to set alleles");
1602
        assert_eq!(record.rlen(), 3)
1603
    }
1604

1605
    #[test]
1606
    fn test_record_end() {
1607
        let tmp = NamedTempFile::new().unwrap();
1608
        let path = tmp.path();
1609
        let header = Header::new();
1610
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1611
        let mut record = vcf.empty_record();
1612
        let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1613
        record.set_alleles(alleles).expect("Failed to set alleles");
1614
        record.set_pos(5);
1615

1616
        assert_eq!(record.end(), 8)
1617
    }
1618

1619
    #[test]
1620
    fn test_record_clear() {
1621
        let tmp = NamedTempFile::new().unwrap();
1622
        let path = tmp.path();
1623
        let mut header = Header::new();
1624
        header.push_sample("sample".as_bytes());
1625
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1626
        let mut record = vcf.empty_record();
1627
        let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1628
        record.set_alleles(alleles).expect("Failed to set alleles");
1629
        record.set_pos(6);
1630
        record.clear();
1631

1632
        assert_eq!(record.rlen(), 0);
1633
        assert_eq!(record.sample_count(), 0);
1634
        assert_eq!(record.pos(), 0)
1635
    }
1636

1637
    #[test]
1638
    fn test_record_clone() {
1639
        let tmp = NamedTempFile::new().unwrap();
1640
        let path = tmp.path();
1641
        let header = Header::new();
1642
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1643
        let mut record = vcf.empty_record();
1644
        let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1645
        record.set_alleles(alleles).expect("Failed to set alleles");
1646
        record.set_pos(6);
1647

1648
        let mut cloned_record = record.clone();
1649
        cloned_record.set_pos(5);
1650

1651
        assert_eq!(record.pos(), 6);
1652
        assert_eq!(record.allele_count(), 2);
1653
        assert_eq!(cloned_record.pos(), 5);
1654
        assert_eq!(cloned_record.allele_count(), 2);
1655
    }
1656

1657
    #[test]
1658
    fn test_record_has_filter_pass_is_default() {
1659
        let tmp = NamedTempFile::new().unwrap();
1660
        let path = tmp.path();
1661
        let header = Header::new();
1662
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1663
        let record = vcf.empty_record();
1664

1665
        assert!(record.has_filter("PASS".as_bytes()));
1666
        assert!(record.has_filter(".".as_bytes()));
1667
        assert!(record.has_filter(&Id(0)));
1668
        assert!(!record.has_filter("foo".as_bytes()));
1669
        assert!(!record.has_filter(&Id(2)));
1670
    }
1671

1672
    #[test]
1673
    fn test_record_has_filter_custom() {
1674
        let tmp = NamedTempFile::new().unwrap();
1675
        let path = tmp.path();
1676
        let mut header = Header::new();
1677
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1678
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1679
        let mut record = vcf.empty_record();
1680
        record.push_filter("foo".as_bytes()).unwrap();
1681

1682
        assert!(record.has_filter("foo".as_bytes()));
1683
        assert!(!record.has_filter("PASS".as_bytes()))
1684
    }
1685

1686
    #[test]
1687
    fn test_record_push_filter() {
1688
        let tmp = NamedTempFile::new().unwrap();
1689
        let path = tmp.path();
1690
        let mut header = Header::new();
1691
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1692
        header.push_record(br#"##FILTER=<ID=bar,Description="dranks">"#);
1693
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1694
        let mut record = vcf.empty_record();
1695
        assert!(record.has_filter("PASS".as_bytes()));
1696
        record.push_filter("foo".as_bytes()).unwrap();
1697
        let bar = record.header().name_to_id(b"bar").unwrap();
1698
        record.push_filter(&bar).unwrap();
1699
        assert!(record.has_filter("foo".as_bytes()));
1700
        assert!(record.has_filter(&bar));
1701
        assert!(!record.has_filter("PASS".as_bytes()));
1702
        assert!(record.push_filter("baz".as_bytes()).is_err())
1703
    }
1704

1705
    #[test]
1706
    fn test_record_set_filters() {
1707
        let tmp = NamedTempFile::new().unwrap();
1708
        let path = tmp.path();
1709
        let mut header = Header::new();
1710
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1711
        header.push_record(br#"##FILTER=<ID=bar,Description="a horse walks into...">"#);
1712
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1713
        let mut record = vcf.empty_record();
1714
        assert!(record.has_filter("PASS".as_bytes()));
1715
        record
1716
            .set_filters(&["foo".as_bytes(), "bar".as_bytes()])
1717
            .unwrap();
1718
        assert!(record.has_filter("foo".as_bytes()));
1719
        assert!(record.has_filter("bar".as_bytes()));
1720
        assert!(!record.has_filter("PASS".as_bytes()));
1721
        let filters: &[&Id] = &[];
1722
        record.set_filters(filters).unwrap();
1723
        assert!(record.has_filter("PASS".as_bytes()));
1724
        assert!(!record.has_filter("foo".as_bytes()));
1725
        assert!(record
1726
            .set_filters(&["foo".as_bytes(), "baz".as_bytes()])
1727
            .is_err())
1728
    }
1729

1730
    #[test]
1731
    fn test_record_remove_filter() {
1732
        let tmp = NamedTempFile::new().unwrap();
1733
        let path = tmp.path();
1734
        let mut header = Header::new();
1735
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1736
        header.push_record(br#"##FILTER=<ID=bar,Description="a horse walks into...">"#);
1737
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1738
        let mut record = vcf.empty_record();
1739
        let foo = record.header().name_to_id(b"foo").unwrap();
1740
        let bar = record.header().name_to_id(b"bar").unwrap();
1741
        record.set_filters(&[&foo, &bar]).unwrap();
1742
        assert!(record.has_filter(&foo));
1743
        assert!(record.has_filter(&bar));
1744
        record.remove_filter(&foo, true).unwrap();
1745
        assert!(!record.has_filter(&foo));
1746
        assert!(record.has_filter(&bar));
1747
        assert!(record.remove_filter("baz".as_bytes(), true).is_err());
1748
        record.remove_filter(&bar, true).unwrap();
1749
        assert!(!record.has_filter(&bar));
1750
        assert!(record.has_filter("PASS".as_bytes()));
1751
    }
1752

1753
    #[test]
1754
    fn test_record_to_vcf_string_err() {
1755
        let tmp = NamedTempFile::new().unwrap();
1756
        let path = tmp.path();
1757
        let header = Header::new();
1758
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1759
        let record = vcf.empty_record();
1760
        assert!(record.to_vcf_string().is_err());
1761
    }
1762

1763
    #[test]
1764
    fn test_record_to_vcf_string() {
1765
        let tmp = NamedTempFile::new().unwrap();
1766
        let path = tmp.path();
1767
        let mut header = Header::new();
1768
        header.push_record(b"##contig=<ID=chr1,length=1000>");
1769
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1770
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1771
        let mut record = vcf.empty_record();
1772
        record.push_filter("foo".as_bytes()).unwrap();
1773
        assert_eq!(
1774
            record.to_vcf_string().unwrap(),
1775
            "chr1\t1\t.\t.\t.\t0\tfoo\t.\n"
1776
        );
1777
    }
1778
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc