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

rust-bio / rust-htslib / 16223472312

11 Jul 2025 03:15PM UTC coverage: 83.49% (-0.2%) from 83.71%
16223472312

Pull #478

github

web-flow
Merge 0ddc7b379 into d7adb080b
Pull Request #478: feat(bcf): Reduce allocations by using CStr8

34 of 59 new or added lines in 2 files covered. (57.63%)

1 existing line in 1 file now uncovered.

2751 of 3295 relevant lines covered (83.49%)

25259.48 hits per line

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

82.6
/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::fmt;
8
use std::marker::PhantomData;
9
use std::ops::Deref;
10
use std::os::raw::c_char;
11
use std::ptr;
12
use std::rc::Rc;
13
use std::slice;
14
use std::str;
15
use std::{ffi, iter};
16

17
use bio_types::genome;
18
use cstr8::{cstr8, CStr8, CString8};
19
use derive_new::new;
20
use ieee754::Ieee754;
21
use lazy_static::lazy_static;
22

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

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

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

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

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

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

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

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

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

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

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

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

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

88
impl FilterId for [u8] {
89
    fn id_from_header(&self, header: &HeaderView) -> Result<Id> {
48✔
90
        let str = String::from_utf8(self.to_vec()).map_err(|_| Error::BcfInvalidRecord)?;
220✔
91
        let id = CString8::new(str).map_err(|_| Error::BcfInvalidRecord)?;
53✔
92
        header.name_to_id(&id)
10✔
93
    }
94
    fn is_pass(&self) -> bool {
36✔
95
        matches!(self, b"PASS" | b".")
102✔
96
    }
97
}
98

99
impl<'a> FilterId for &'a CStr8 {
NEW
100
    fn id_from_header(&self, header: &HeaderView) -> Result<Id> {
×
NEW
101
        header.name_to_id(self)
×
102
    }
103

NEW
104
    fn is_pass(&self) -> bool {
×
NEW
105
        matches!(self.as_bytes(), b"PASS" | b".")
×
106
    }
107
}
108

109
impl FilterId for Id {
110
    fn id_from_header(&self, _header: &HeaderView) -> Result<Id> {
21✔
111
        Ok(*self)
21✔
112
    }
113
    fn is_pass(&self) -> bool {
16✔
114
        *self == Id(0)
16✔
115
    }
116
}
117

118
/// A buffer for info or format data.
119
#[derive(Debug)]
120
pub struct Buffer {
121
    inner: *mut ::std::os::raw::c_void,
122
    len: i32,
123
}
124

125
impl Buffer {
126
    pub fn new() -> Self {
304✔
127
        Buffer {
128
            inner: ptr::null_mut(),
304✔
129
            len: 0,
130
        }
131
    }
132
}
133

134
impl Default for Buffer {
135
    fn default() -> Self {
×
136
        Self::new()
×
137
    }
138
}
139

140
impl Drop for Buffer {
141
    fn drop(&mut self) {
304✔
142
        unsafe {
143
            ::libc::free(self.inner);
304✔
144
        }
145
    }
146
}
147

148
#[derive(new, Debug)]
149
pub struct BufferBacked<'a, T: 'a + fmt::Debug, B: Borrow<Buffer> + 'a> {
150
    value: T,
151
    _buffer: B,
152
    #[new(default)]
153
    phantom: PhantomData<&'a B>,
154
}
155

156
impl<'a, T: 'a + fmt::Debug, B: Borrow<Buffer> + 'a> Deref for BufferBacked<'a, T, B> {
157
    type Target = T;
158

159
    fn deref(&self) -> &T {
469✔
160
        &self.value
457✔
161
    }
162
}
163

164
impl<'a, T: 'a + fmt::Debug + fmt::Display, B: Borrow<Buffer> + 'a> fmt::Display
165
    for BufferBacked<'a, T, B>
166
{
167
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
168
        fmt::Display::fmt(&self.value, f)
×
169
    }
170
}
171

172
/// A VCF/BCF record.
173
/// New records can be created by the `empty_record` methods of [`bcf::Reader`](crate::bcf::Reader)
174
/// and [`bcf::Writer`](crate::bcf::Writer).
175
/// # Example
176
/// ```rust
177
/// use rust_htslib::bcf::{Format, Writer};
178
/// use rust_htslib::bcf::header::Header;
179
///
180
/// // Create minimal VCF header with a single sample
181
/// let mut header = Header::new();
182
/// header.push_sample("sample".as_bytes());
183
///
184
/// // Write uncompressed VCF to stdout with above header and get an empty record
185
/// let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
186
/// let mut record = vcf.empty_record();
187
/// ```
188
#[derive(Debug)]
189
pub struct Record {
190
    pub inner: *mut htslib::bcf1_t,
191
    header: Rc<HeaderView>,
192
}
193

194
impl Record {
195
    /// Construct record with reference to header `HeaderView`, for create-internal use.
196
    pub(crate) fn new(header: Rc<HeaderView>) -> Self {
449✔
197
        let inner = unsafe {
198
            let inner = htslib::bcf_init();
880✔
199
            // Always unpack record.
200
            htslib::bcf_unpack(inner, htslib::BCF_UN_ALL as i32);
1,311✔
201
            inner
431✔
202
        };
203
        Record { inner, header }
204
    }
205

206
    /// Force unpacking of internal record values.
207
    pub fn unpack(&mut self) {
×
208
        unsafe { htslib::bcf_unpack(self.inner, htslib::BCF_UN_ALL as i32) };
×
209
    }
210

211
    /// Return associated header.
212
    pub fn header(&self) -> &HeaderView {
748✔
213
        self.header.as_ref()
748✔
214
    }
215

216
    /// Set the record header.
217
    pub(crate) fn set_header(&mut self, header: Rc<HeaderView>) {
454✔
218
        self.header = header;
908✔
219
    }
220

221
    /// Return reference to the inner C struct.
222
    ///
223
    /// # Remarks
224
    ///
225
    /// Note that this function is only required as long as Rust-Htslib does not provide full
226
    /// access to all aspects of Htslib.
227
    pub fn inner(&self) -> &htslib::bcf1_t {
1,171✔
228
        unsafe { &*self.inner }
1,171✔
229
    }
230

231
    /// Return mutable reference to inner C struct.
232
    ///
233
    /// # Remarks
234
    ///
235
    /// Note that this function is only required as long as Rust-Htslib does not provide full
236
    /// access to all aspects of Htslib.
237
    pub fn inner_mut(&mut self) -> &mut htslib::bcf1_t {
408✔
238
        unsafe { &mut *self.inner }
408✔
239
    }
240

241
    /// Get the reference id of the record.
242
    ///
243
    /// To look up the contig name,
244
    /// use [`HeaderView::rid2name`](../header/struct.HeaderView.html#method.rid2name).
245
    ///
246
    /// # Returns
247
    ///
248
    /// - `Some(rid)` if the internal `rid` is set to a value that is not `-1`
249
    /// - `None` if the internal `rid` is set to `-1`
250
    pub fn rid(&self) -> Option<u32> {
233✔
251
        match self.inner().rid {
233✔
252
            -1 => None,
×
253
            rid => Some(rid as u32),
464✔
254
        }
255
    }
256

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

290
    /// Return **0-based** position
291
    pub fn pos(&self) -> i64 {
311✔
292
        self.inner().pos
311✔
293
    }
294

295
    /// Set **0-based** position
296
    pub fn set_pos(&mut self, pos: i64) {
12✔
297
        self.inner_mut().pos = pos;
20✔
298
    }
299

300
    /// Return the **0-based, exclusive** end position
301
    ///
302
    /// # Example
303
    /// ```rust
304
    /// # use rust_htslib::bcf::{Format, Header, Writer};
305
    /// # use tempfile::NamedTempFile;
306
    /// # let tmp = NamedTempFile::new().unwrap();
307
    /// # let path = tmp.path();
308
    /// # let header = Header::new();
309
    /// # let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
310
    /// # let mut record = vcf.empty_record();
311
    /// let alleles: &[&[u8]] = &[b"AGG", b"TG"];
312
    /// record.set_alleles(alleles).expect("Failed to set alleles");
313
    /// record.set_pos(5);
314
    ///
315
    /// assert_eq!(record.end(), 8)
316
    /// ```
317
    pub fn end(&self) -> i64 {
7✔
318
        self.pos() + self.rlen()
8✔
319
    }
320

321
    /// Return the value of the ID column.
322
    ///
323
    /// When empty, returns `b".".to_vec()`.
324
    pub fn id(&self) -> Vec<u8> {
4✔
325
        if self.inner().d.id.is_null() {
7✔
326
            b".".to_vec()
3✔
327
        } else {
328
            let id = unsafe { ffi::CStr::from_ptr(self.inner().d.id) };
3✔
329
            id.to_bytes().to_vec()
1✔
330
        }
331
    }
332

333
    /// Update the ID string to the given value.
334
    pub fn set_id(&mut self, id: &[u8]) -> Result<()> {
3✔
335
        let c_str = ffi::CString::new(id).unwrap();
9✔
336
        if unsafe {
4✔
337
            htslib::bcf_update_id(
5✔
338
                self.header().inner,
6✔
339
                self.inner,
5✔
340
                c_str.as_ptr() as *mut c_char,
3✔
341
            )
342
        } == 0
2✔
343
        {
344
            Ok(())
3✔
345
        } else {
346
            Err(Error::BcfSetValues)
×
347
        }
348
    }
349

350
    /// Clear the ID column (set it to `"."`).
351
    pub fn clear_id(&mut self) -> Result<()> {
2✔
352
        let c_str = ffi::CString::new(&b"."[..]).unwrap();
5✔
353
        if unsafe {
3✔
354
            htslib::bcf_update_id(
3✔
355
                self.header().inner,
4✔
356
                self.inner,
3✔
357
                c_str.as_ptr() as *mut c_char,
2✔
358
            )
359
        } == 0
1✔
360
        {
361
            Ok(())
2✔
362
        } else {
363
            Err(Error::BcfSetValues)
×
364
        }
365
    }
366

367
    /// Add the ID string (the ID field is semicolon-separated), checking for duplicates.
368
    pub fn push_id(&mut self, id: &[u8]) -> Result<()> {
3✔
369
        let c_str = ffi::CString::new(id).unwrap();
9✔
370
        if unsafe {
4✔
371
            htslib::bcf_add_id(
5✔
372
                self.header().inner,
6✔
373
                self.inner,
5✔
374
                c_str.as_ptr() as *mut c_char,
3✔
375
            )
376
        } == 0
2✔
377
        {
378
            Ok(())
3✔
379
        } else {
380
            Err(Error::BcfSetValues)
×
381
        }
382
    }
383

384
    /// Return `Filters` iterator for enumerating all filters that have been set.
385
    ///
386
    /// A record having the `PASS` filter will return an empty `Filter` here.
387
    pub fn filters(&self) -> Filters<'_> {
2✔
388
        Filters::new(self)
3✔
389
    }
390

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

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

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

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

578
    /// Get alleles strings.
579
    ///
580
    /// The first allele is the reference allele.
581
    pub fn alleles(&self) -> Vec<&[u8]> {
133✔
582
        unsafe { htslib::bcf_unpack(self.inner, htslib::BCF_UN_ALL as i32) };
395✔
583
        let n = self.inner().n_allele() as usize;
264✔
584
        let dec = self.inner().d;
264✔
585
        let alleles = unsafe { slice::from_raw_parts(dec.allele, n) };
526✔
586
        (0..n)
131✔
587
            .map(|i| unsafe { ffi::CStr::from_ptr(alleles[i]).to_bytes() })
929✔
588
            .collect()
589
    }
590

591
    /// Set alleles. The first allele is the reference allele.
592
    ///
593
    /// # Example
594
    /// ```rust
595
    /// # use rust_htslib::bcf::{Format, Writer};
596
    /// # use rust_htslib::bcf::header::Header;
597
    /// #
598
    /// # // Create minimal VCF header with a single sample
599
    /// # let mut header = Header::new();
600
    /// # header.push_sample("sample".as_bytes());
601
    /// #
602
    /// # // Write uncompressed VCF to stdout with above header and get an empty record
603
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
604
    /// # let mut record = vcf.empty_record();
605
    /// assert_eq!(record.allele_count(), 0);
606
    ///
607
    /// let alleles: &[&[u8]] = &[b"A", b"TG"];
608
    /// record.set_alleles(alleles).expect("Failed to set alleles");
609
    /// assert_eq!(record.allele_count(), 2)
610
    /// ```
611
    pub fn set_alleles(&mut self, alleles: &[&[u8]]) -> Result<()> {
17✔
612
        let cstrings: Vec<ffi::CString> = alleles
27✔
613
            .iter()
614
            .map(|vec| ffi::CString::new(*vec).unwrap())
81✔
615
            .collect();
616
        let mut ptrs: Vec<*const c_char> = cstrings
32✔
617
            .iter()
618
            .map(|cstr| cstr.as_ptr() as *const c_char)
43✔
619
            .collect();
620
        if unsafe {
19✔
621
            htslib::bcf_update_alleles(
23✔
622
                self.header().inner,
28✔
623
                self.inner,
23✔
624
                ptrs.as_mut_ptr(),
32✔
625
                alleles.len() as i32,
14✔
626
            )
627
        } == 0
9✔
628
        {
629
            Ok(())
14✔
630
        } else {
631
            Err(Error::BcfSetValues)
×
632
        }
633
    }
634

635
    /// Get variant quality.
636
    pub fn qual(&self) -> f32 {
126✔
637
        self.inner().qual
126✔
638
    }
639

640
    /// Set variant quality.
641
    pub fn set_qual(&mut self, qual: f32) {
2✔
642
        self.inner_mut().qual = qual;
3✔
643
    }
644

645
    pub fn info<'a>(&'a self, tag: &'a [u8]) -> Info<'a, Buffer> {
4✔
646
        self.info_shared_buffer(tag, Buffer::new())
13✔
647
    }
648

649
    /// Get the value of the given info tag.
650
    pub fn info_shared_buffer<'a, 'b, B: BorrowMut<Buffer> + Borrow<Buffer> + 'b>(
141✔
651
        &'a self,
652
        tag: &'a [u8],
653
        buffer: B,
654
    ) -> Info<'a, B> {
655
        Info {
656
            record: self,
657
            tag,
658
            buffer,
659
        }
660
    }
661

662
    /// Get the number of samples in the record.
663
    pub fn sample_count(&self) -> u32 {
134✔
664
        self.inner().n_sample()
266✔
665
    }
666

667
    /// Get the number of alleles, including reference allele.
668
    pub fn allele_count(&self) -> u32 {
6✔
669
        self.inner().n_allele()
10✔
670
    }
671

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

710
    /// Add/replace genotypes in FORMAT GT tag by providing a list of genotypes.
711
    ///
712
    /// # Arguments
713
    ///
714
    /// - `genotypes` - a two-dimensional array of GenotypeAllele
715
    /// - `max_ploidy` - the maximum number of alleles allowed for any genotype on any sample.
716
    ///
717
    /// # Errors
718
    ///
719
    /// Returns an error if any genotype has more allelles than `max_ploidy` or if the GT tag is not present in the header.
720
    ///
721
    /// # Example
722
    ///
723
    /// Example assumes we have a Record `record` from a VCF with a `GT` `FORMAT` tag and three samples.
724
    /// See [module documentation](../index.html#example-writing) for how to set up
725
    /// VCF, header, and record.
726
    ///
727
    /// ```
728
    /// # use rust_htslib::bcf::{Format, Writer};
729
    /// # use rust_htslib::bcf::header::Header;
730
    /// # use rust_htslib::bcf::record::GenotypeAllele;
731
    /// # use std::iter;
732
    /// # let mut header = Header::new();
733
    /// # let header_contig_line = r#"##contig=<ID=1,length=10>"#;
734
    /// # header.push_record(header_contig_line.as_bytes());
735
    /// # let header_gt_line = r#"##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">"#;
736
    /// # header.push_record(header_gt_line.as_bytes());
737
    /// # header.push_sample("first_sample".as_bytes());
738
    /// # header.push_sample("second_sample".as_bytes());
739
    /// # header.push_sample("third_sample".as_bytes());
740
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf)?;
741
    /// # let mut record = vcf.empty_record();
742
    /// let alleles = vec![
743
    ///     vec![GenotypeAllele::Unphased(1), GenotypeAllele::Unphased(1)],
744
    ///     vec![GenotypeAllele::Unphased(0), GenotypeAllele::Phased(1)],
745
    ///     vec![GenotypeAllele::Unphased(0)],
746
    /// ];
747
    /// record.push_genotype_structured(&alleles, 2);
748
    /// let gts = record.genotypes()?;
749
    /// assert_eq!("1/1", &format!("{}", gts.get(0)));
750
    /// assert_eq!("0|1", &format!("{}", gts.get(1)));
751
    /// assert_eq!("0", &format!("{}", gts.get(2)));
752
    /// # Ok::<(), rust_htslib::errors::Error>(())
753
    /// ```
754
    pub fn push_genotype_structured<GT>(
11✔
755
        &mut self,
756
        genotypes: &[GT],
757
        max_ploidy: usize,
758
    ) -> Result<()>
759
    where
760
        GT: AsRef<[GenotypeAllele]>,
761
    {
762
        let mut data = Vec::with_capacity(max_ploidy * genotypes.len());
22✔
763
        for gt in genotypes {
30✔
764
            if gt.as_ref().len() > max_ploidy {
26✔
765
                return Err(Error::BcfSetValues);
2✔
766
            }
767
            data.extend(
2✔
768
                gt.as_ref()
2✔
769
                    .iter()
2✔
770
                    .map(|gta| i32::from(*gta))
44✔
771
                    .chain(iter::repeat_n(
4✔
772
                        VECTOR_END_INTEGER,
×
773
                        max_ploidy - gt.as_ref().len(),
2✔
774
                    )),
775
            );
776
        }
777
        self.push_format_integer(cstr8!("GT"), &data)
6✔
778
    }
779

780
    /// Get genotypes as vector of one `Genotype` per sample.
781
    ///
782
    /// # Example
783
    /// Parsing genotype field (`GT` tag) from a VCF record:
784
    /// ```
785
    /// use crate::rust_htslib::bcf::{Reader, Read};
786
    /// let mut vcf = Reader::from_path(&"test/test_string.vcf").expect("Error opening file.");
787
    /// let expected = ["./1", "1|1", "0/1", "0|1", "1|.", "1/1"];
788
    /// for (rec, exp_gt) in vcf.records().zip(expected.iter()) {
789
    ///     let mut rec = rec.expect("Error reading record.");
790
    ///     let genotypes = rec.genotypes().expect("Error reading genotypes");
791
    ///     assert_eq!(&format!("{}", genotypes.get(0)), exp_gt);
792
    /// }
793
    /// ```
794
    pub fn genotypes(&self) -> Result<Genotypes<'_, Buffer>> {
35✔
795
        self.genotypes_shared_buffer(Buffer::new())
86✔
796
    }
797

798
    /// Get genotypes as vector of one `Genotype` per sample, using a given shared buffer
799
    /// to avoid unnecessary allocations.
800
    pub fn genotypes_shared_buffer<'a, B>(&self, buffer: B) -> Result<Genotypes<'a, B>>
32✔
801
    where
802
        B: BorrowMut<Buffer> + Borrow<Buffer> + 'a,
803
    {
804
        Ok(Genotypes {
5✔
805
            encoded: self.format_shared_buffer(b"GT", buffer).integer()?,
140✔
806
        })
807
    }
808

809
    /// Retrieve data for a `FORMAT` field
810
    ///
811
    /// # Example
812
    /// *Note: some boilerplate for the example is hidden for clarity. See [module documentation](../index.html#example-writing)
813
    /// for an example of the setup used here.*
814
    ///
815
    /// ```rust
816
    /// # use cstr8::cstr8;
817
    /// # use rust_htslib::bcf::{Format, Writer};
818
    /// # use rust_htslib::bcf::header::Header;
819
    /// #
820
    /// # // Create minimal VCF header with a single sample
821
    /// # let mut header = Header::new();
822
    /// header.push_sample(b"sample1").push_sample(b"sample2").push_record(br#"##FORMAT=<ID=DP,Number=1,Type=Integer,Description="Read Depth">"#);
823
    /// #
824
    /// # // Write uncompressed VCF to stdout with above header and get an empty record
825
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
826
    /// # let mut record = vcf.empty_record();
827
    /// record.push_format_integer(cstr8!("DP"), &[20, 12]).expect("Failed to set DP format field");
828
    ///
829
    /// let read_depths = record.format(b"DP").integer().expect("Couldn't retrieve DP field");
830
    /// let sample1_depth = read_depths[0];
831
    /// assert_eq!(sample1_depth, &[20]);
832
    /// let sample2_depth = read_depths[1];
833
    /// assert_eq!(sample2_depth, &[12])
834
    /// ```
835
    ///
836
    /// # Errors
837
    /// **Attention:** the returned [`BufferBacked`] from [`integer()`](Format::integer)
838
    /// (`read_depths`), which holds the data, has to be kept in scope as long as the data is
839
    /// accessed. If parts of the data are accessed after the `BufferBacked` object is been
840
    /// dropped, you will access unallocated memory.
841
    pub fn format<'a>(&'a self, tag: &'a [u8]) -> Format<'a, Buffer> {
142✔
842
        self.format_shared_buffer(tag, Buffer::new())
559✔
843
    }
844

845
    /// Get the value of the given format tag for each sample.
846
    pub fn format_shared_buffer<'a, 'b, B: BorrowMut<Buffer> + Borrow<Buffer> + 'b>(
180✔
847
        &'a self,
848
        tag: &'a [u8],
849
        buffer: B,
850
    ) -> Format<'a, B> {
851
        Format::new(self, tag, buffer)
696✔
852
    }
853

854
    /// Add/replace an integer-typed FORMAT tag.
855
    ///
856
    /// # Arguments
857
    ///
858
    /// - `tag` - The tag's string.
859
    /// - `data` - a flattened, two-dimensional array, the first dimension contains one array
860
    ///   for each sample.
861
    ///
862
    /// # Errors
863
    ///
864
    /// Returns error if tag is not present in header.
865
    pub fn push_format_integer(&mut self, tag: &CStr8, data: &[i32]) -> Result<()> {
14✔
866
        self.push_format(tag, data, htslib::BCF_HT_INT)
41✔
867
    }
868

869
    /// Add/replace a float-typed FORMAT tag.
870
    ///
871
    /// # Arguments
872
    ///
873
    /// - `tag` - The tag's string.
874
    /// - `data` - a flattened, two-dimensional array, the first dimension contains one array
875
    ///   for each sample.
876
    ///
877
    /// # Errors
878
    ///
879
    /// Returns error if tag is not present in header.
880
    ///
881
    /// # Example
882
    ///
883
    /// Example assumes we have a Record `record` from a VCF with an `AF` `FORMAT` tag.
884
    /// See [module documentation](../index.html#example-writing) for how to set up
885
    /// VCF, header, and record.
886
    ///
887
    /// ```
888
    /// # use cstr8::cstr8;
889
    /// # use rust_htslib::bcf::{Format, Writer};
890
    /// # use rust_htslib::bcf::header::Header;
891
    /// # use rust_htslib::bcf::record::GenotypeAllele;
892
    /// # let mut header = Header::new();
893
    /// # let header_contig_line = r#"##contig=<ID=1,length=10>"#;
894
    /// # header.push_record(header_contig_line.as_bytes());
895
    /// # let header_af_line = r#"##FORMAT=<ID=AF,Number=1,Type=Float,Description="Frequency">"#;
896
    /// # header.push_record(header_af_line.as_bytes());
897
    /// # header.push_sample("test_sample".as_bytes());
898
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
899
    /// # let mut record = vcf.empty_record();
900
    /// record.push_format_float(cstr8!("AF"), &[0.5]);
901
    /// assert_eq!(0.5, record.format(b"AF").float().unwrap()[0][0]);
902
    /// ```
903
    pub fn push_format_float(&mut self, tag: &CStr8, data: &[f32]) -> Result<()> {
7✔
904
        self.push_format(tag, data, htslib::BCF_HT_REAL)
10✔
905
    }
906

907
    /// Add/replace a single-char-typed FORMAT tag.
908
    ///
909
    /// # Arguments
910
    ///
911
    /// - `tag` - The tag's string.
912
    /// - `data` - a flattened, two-dimensional array, the first dimension contains one array
913
    ///   for each sample.
914
    ///
915
    /// # Errors
916
    ///
917
    /// Returns error if tag is not present in header.
918
    pub fn push_format_char(&mut self, tag: &CStr8, data: &[u8]) -> Result<()> {
2✔
919
        self.push_format(tag, data, htslib::BCF_HT_STR)
5✔
920
    }
921

922
    /// Add a format tag. Data is a flattened two-dimensional array.
923
    /// The first dimension contains one array for each sample.
924
    fn push_format<T>(&mut self, tag: &CStr8, data: &[T], ht: u32) -> Result<()> {
20✔
925
        unsafe {
926
            if htslib::bcf_update_format(
40✔
927
                self.header().inner,
32✔
928
                self.inner,
32✔
929
                tag.as_ptr() as *mut c_char,
32✔
930
                data.as_ptr() as *const ::std::os::raw::c_void,
32✔
931
                data.len() as i32,
20✔
932
                ht as i32,
12✔
933
            ) == 0
12✔
934
            {
935
                Ok(())
20✔
936
            } else {
NEW
937
                Err(Error::BcfSetTag { tag: tag.into() })
×
938
            }
939
        }
940
    }
941

942
    // TODO: should we add convenience methods clear_format_*?
943

944
    /// Add a string-typed FORMAT tag. Note that genotypes are treated as a special case
945
    /// and cannot be added with this method. See instead [push_genotypes](#method.push_genotypes).
946
    ///
947
    /// # Arguments
948
    ///
949
    /// - `tag` - The tag's string.
950
    /// - `data` - a two-dimensional array, the first dimension contains one array
951
    ///   for each sample. Must be non-empty.
952
    ///
953
    /// # Errors
954
    ///
955
    /// Returns error if tag is not present in header.
956
    pub fn push_format_string<D: Borrow<[u8]>>(&mut self, tag: &CStr8, data: &[D]) -> Result<()> {
2✔
957
        assert!(
1✔
958
            !data.is_empty(),
1✔
959
            "given string data must have at least 1 element"
×
960
        );
961
        let c_data = data
2✔
962
            .iter()
963
            .map(|s| ffi::CString::new(s.borrow()).unwrap())
12✔
964
            .collect::<Vec<ffi::CString>>();
965
        let c_ptrs = c_data
3✔
966
            .iter()
967
            .map(|s| s.as_ptr() as *mut i8)
6✔
968
            .collect::<Vec<*mut i8>>();
969
        unsafe {
970
            if htslib::bcf_update_format_string(
4✔
971
                self.header().inner,
4✔
972
                self.inner,
3✔
973
                tag.as_ptr() as *mut c_char,
3✔
974
                c_ptrs.as_slice().as_ptr() as *mut *const c_char,
3✔
975
                data.len() as i32,
2✔
976
            ) == 0
1✔
977
            {
978
                Ok(())
2✔
979
            } else {
NEW
980
                Err(Error::BcfSetTag { tag: tag.into() })
×
981
            }
982
        }
983
    }
984

985
    /// Add/replace an integer-typed INFO entry.
986
    pub fn push_info_integer(&mut self, tag: &CStr8, data: &[i32]) -> Result<()> {
2✔
987
        self.push_info(tag, data, htslib::BCF_HT_INT)
5✔
988
    }
989

990
    /// Remove the integer-typed INFO entry.
NEW
991
    pub fn clear_info_integer(&mut self, tag: &CStr8) -> Result<()> {
×
992
        self.push_info::<i32>(tag, &[], htslib::BCF_HT_INT)
×
993
    }
994

995
    /// Add/replace a float-typed INFO entry.
996
    pub fn push_info_float(&mut self, tag: &CStr8, data: &[f32]) -> Result<()> {
2✔
997
        self.push_info(tag, data, htslib::BCF_HT_REAL)
5✔
998
    }
999

1000
    /// Remove the float-typed INFO entry.
NEW
1001
    pub fn clear_info_float(&mut self, tag: &CStr8) -> Result<()> {
×
1002
        self.push_info::<u8>(tag, &[], htslib::BCF_HT_REAL)
×
1003
    }
1004

1005
    /// Add/replace an INFO tag.
1006
    ///
1007
    /// # Arguments
1008
    /// * `tag` - the tag to add/replace
1009
    /// * `data` - the data to set
1010
    /// * `ht` - the HTSLib type to use
1011
    fn push_info<T>(&mut self, tag: &CStr8, data: &[T], ht: u32) -> Result<()> {
4✔
1012
        unsafe {
1013
            if htslib::bcf_update_info(
8✔
1014
                self.header().inner,
6✔
1015
                self.inner,
6✔
1016
                tag.as_ptr() as *mut c_char,
6✔
1017
                data.as_ptr() as *const ::std::os::raw::c_void,
6✔
1018
                data.len() as i32,
4✔
1019
                ht as i32,
2✔
1020
            ) == 0
2✔
1021
            {
1022
                Ok(())
4✔
1023
            } else {
NEW
1024
                Err(Error::BcfSetTag { tag: tag.into() })
×
1025
            }
1026
        }
1027
    }
1028

1029
    /// Set flag into the INFO column.
1030
    pub fn push_info_flag(&mut self, tag: &CStr8) -> Result<()> {
2✔
1031
        self.push_info_string_impl(tag, &[b""], htslib::BCF_HT_FLAG)
5✔
1032
    }
1033

1034
    /// Remove the flag from the INFO column.
NEW
1035
    pub fn clear_info_flag(&mut self, tag: &CStr8) -> Result<()> {
×
1036
        self.push_info_string_impl(tag, &[], htslib::BCF_HT_FLAG)
×
1037
    }
1038

1039
    /// Add/replace a string-typed INFO entry.
1040
    pub fn push_info_string(&mut self, tag: &CStr8, data: &[&[u8]]) -> Result<()> {
2✔
1041
        self.push_info_string_impl(tag, data, htslib::BCF_HT_STR)
5✔
1042
    }
1043

1044
    /// Remove the string field from the INFO column.
NEW
1045
    pub fn clear_info_string(&mut self, tag: &CStr8) -> Result<()> {
×
1046
        self.push_info_string_impl(tag, &[], htslib::BCF_HT_STR)
×
1047
    }
1048

1049
    /// Add an string-valued INFO tag.
1050
    fn push_info_string_impl(&mut self, tag: &CStr8, data: &[&[u8]], ht: u32) -> Result<()> {
3✔
1051
        if data.is_empty() {
5✔
1052
            // Clear the tag
NEW
1053
            let c_str = unsafe { CStr8::from_utf8_with_nul_unchecked(b"\0") };
×
NEW
1054
            let len = 0;
1✔
1055
            unsafe {
NEW
1056
                return if htslib::bcf_update_info(
×
NEW
1057
                    self.header().inner,
×
NEW
1058
                    self.inner,
×
NEW
1059
                    tag.as_ptr() as *mut c_char,
×
NEW
1060
                    c_str.as_ptr() as *const ::std::os::raw::c_void,
×
NEW
1061
                    len as i32,
NEW
1062
                    ht as i32,
NEW
1063
                ) == 0
1064
                {
NEW
1065
                    Ok(())
×
1066
                } else {
NEW
1067
                    Err(Error::BcfSetTag { tag: tag.into() })
×
1068
                };
1069
            }
1070
        }
1071

1072
        if data == &[b""] {
1✔
1073
            // This is a flag
1074
            let c_str = unsafe { CStr8::from_utf8_with_nul_unchecked(b"\0") };
4✔
1075
            let len = 1;
3✔
1076
            unsafe {
1077
                return if htslib::bcf_update_info(
4✔
1078
                    self.header().inner,
3✔
1079
                    self.inner,
3✔
1080
                    tag.as_ptr() as *mut c_char,
3✔
1081
                    c_str.as_ptr() as *const ::std::os::raw::c_void,
3✔
1082
                    len as i32,
1✔
1083
                    ht as i32,
1✔
1084
                ) == 0
1✔
1085
                {
1086
                    Ok(())
2✔
1087
                } else {
NEW
1088
                    Err(Error::BcfSetTag { tag: tag.into() })
×
1089
                };
1090
            }
1091
        }
1092

1093
        let data_bytes = data.iter().map(|x| x.len() + 2).sum(); // estimate for buffer pre-alloc
5✔
1094
        let mut buf: Vec<u8> = Vec::with_capacity(data_bytes);
1✔
1095
        for (i, &s) in data.iter().enumerate() {
3✔
1096
            if i > 0 {
1✔
1097
                buf.extend(b",");
×
1098
            }
1099
            buf.extend(s);
2✔
1100
        }
1101
        let c_str = ffi::CString::new(buf).unwrap();
1✔
1102
        let len = if ht == htslib::BCF_HT_FLAG {
1✔
UNCOV
1103
            data.len()
×
1104
        } else {
1105
            c_str.to_bytes().len()
3✔
1106
        };
1107
        unsafe {
1108
            if htslib::bcf_update_info(
2✔
1109
                self.header().inner,
1✔
1110
                self.inner,
1✔
1111
                tag.as_ptr() as *mut c_char,
1✔
1112
                c_str.as_ptr() as *const ::std::os::raw::c_void,
1✔
1113
                len as i32,
1✔
1114
                ht as i32,
1115
            ) == 0
1116
            {
1117
                Ok(())
2✔
1118
            } else {
NEW
1119
                Err(Error::BcfSetTag { tag: tag.into() })
×
1120
            }
1121
        }
1122
    }
1123

1124
    /// Remove unused alleles.
1125
    pub fn trim_alleles(&mut self) -> Result<()> {
63✔
1126
        match unsafe { htslib::bcf_trim_alleles(self.header().inner, self.inner) } {
125✔
1127
            -1 => Err(Error::BcfRemoveAlleles),
×
1128
            _ => Ok(()),
63✔
1129
        }
1130
    }
1131

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

1135
        for (i, &r) in remove.iter().enumerate() {
8✔
1136
            if r {
1✔
1137
                unsafe {
1138
                    htslib::kbs_insert(rm_set, i as i32);
3✔
1139
                }
1140
            }
1141
        }
1142

1143
        let ret = unsafe { htslib::bcf_remove_allele_set(self.header().inner, self.inner, rm_set) };
6✔
1144

1145
        unsafe {
1146
            htslib::kbs_destroy(rm_set);
2✔
1147
        }
1148

1149
        match ret {
2✔
1150
            -1 => Err(Error::BcfRemoveAlleles),
×
1151
            _ => Ok(()),
2✔
1152
        }
1153
    }
1154

1155
    /// Get the length of the reference allele. If the record has no reference allele, then the
1156
    /// result will be `0`.
1157
    ///
1158
    /// # Example
1159
    /// ```rust
1160
    /// # use rust_htslib::bcf::{Format, Writer};
1161
    /// # use rust_htslib::bcf::header::Header;
1162
    /// #
1163
    /// # // Create minimal VCF header with a single sample
1164
    /// # let mut header = Header::new();
1165
    /// # header.push_sample("sample".as_bytes());
1166
    /// #
1167
    /// # // Write uncompressed VCF to stdout with above header and get an empty record
1168
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
1169
    /// # let mut record = vcf.empty_record();
1170
    /// # assert_eq!(record.rlen(), 0);
1171
    /// let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1172
    /// record.set_alleles(alleles).expect("Failed to set alleles");
1173
    /// assert_eq!(record.rlen(), 3)
1174
    /// ```
1175
    pub fn rlen(&self) -> i64 {
15✔
1176
        self.inner().rlen
12✔
1177
    }
1178

1179
    /// Clear all parts of the record. Useful if you plan to reuse a record object multiple times.
1180
    ///
1181
    /// # Example
1182
    /// ```rust
1183
    /// # use rust_htslib::bcf::{Format, Writer};
1184
    /// # use rust_htslib::bcf::header::Header;
1185
    /// #
1186
    /// # // Create minimal VCF header with a single sample
1187
    /// # let mut header = Header::new();
1188
    /// # header.push_sample("sample".as_bytes());
1189
    /// #
1190
    /// # // Write uncompressed VCF to stdout with above header and get an empty record
1191
    /// # let mut vcf = Writer::from_stdout(&header, true, Format::Vcf).unwrap();
1192
    /// # let mut record = vcf.empty_record();
1193
    /// let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1194
    /// record.set_alleles(alleles).expect("Failed to set alleles");
1195
    /// record.set_pos(6);
1196
    /// record.clear();
1197
    /// assert_eq!(record.rlen(), 0);
1198
    /// assert_eq!(record.pos(), 0)
1199
    /// ```
1200
    pub fn clear(&self) {
7✔
1201
        unsafe { htslib::bcf_clear(self.inner) }
6✔
1202
    }
1203

1204
    /// Provide short description of record for locating it in the BCF/VCF file.
1205
    pub fn desc(&self) -> String {
×
1206
        if let Some(rid) = self.rid() {
×
1207
            if let Ok(contig) = self.header.rid2name(rid) {
×
1208
                return format!("{}:{}", str::from_utf8(contig).unwrap(), self.pos());
1209
            }
1210
        }
1211
        "".to_owned()
×
1212
    }
1213

1214
    /// Convert to VCF String
1215
    ///
1216
    /// Intended for debug only. Use Writer for efficient VCF output.
1217
    ///
1218
    pub fn to_vcf_string(&self) -> Result<String> {
3✔
1219
        let mut buf = htslib::kstring_t {
1220
            l: 0,
1221
            m: 0,
1222
            s: ptr::null_mut(),
3✔
1223
        };
1224
        let ret = unsafe { htslib::vcf_format(self.header().inner, self.inner, &mut buf) };
11✔
1225

1226
        if ret < 0 {
3✔
1227
            if !buf.s.is_null() {
2✔
1228
                unsafe {
1229
                    libc::free(buf.s as *mut libc::c_void);
×
1230
                }
1231
            }
1232
            return Err(Error::BcfToString);
2✔
1233
        }
1234

1235
        let vcf_str = unsafe {
1236
            let vcf_str = String::from(ffi::CStr::from_ptr(buf.s).to_str().unwrap());
1✔
1237
            if !buf.s.is_null() {
3✔
1238
                libc::free(buf.s as *mut libc::c_void);
2✔
1239
            }
1240
            vcf_str
1✔
1241
        };
1242

1243
        Ok(vcf_str)
1✔
1244
    }
1245
}
1246

1247
impl Clone for Record {
1248
    fn clone(&self) -> Self {
2✔
1249
        let inner = unsafe { htslib::bcf_dup(self.inner) };
4✔
1250
        Record {
1251
            inner,
1252
            header: self.header.clone(),
2✔
1253
        }
1254
    }
1255
}
1256

1257
impl genome::AbstractLocus for Record {
1258
    fn contig(&self) -> &str {
×
1259
        str::from_utf8(
1260
            self.header()
×
1261
                .rid2name(self.rid().expect("rid not set"))
×
1262
                .expect("unable to find rid in header"),
×
1263
        )
1264
        .expect("unable to interpret contig name as UTF-8")
1265
    }
1266

1267
    fn pos(&self) -> u64 {
×
1268
        self.pos() as u64
×
1269
    }
1270
}
1271

1272
/// Phased or unphased alleles, represented as indices.
1273
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1274
pub enum GenotypeAllele {
1275
    Unphased(i32),
1276
    Phased(i32),
1277
    UnphasedMissing,
1278
    PhasedMissing,
1279
}
1280

1281
impl GenotypeAllele {
1282
    /// Decode given integer according to BCF standard.
1283
    #[deprecated(
1284
        since = "0.36.0",
1285
        note = "Please use the conversion trait From<i32> for GenotypeAllele instead."
1286
    )]
1287
    pub fn from_encoded(encoded: i32) -> Self {
×
1288
        match (encoded, encoded & 1) {
×
1289
            (0, 0) => GenotypeAllele::UnphasedMissing,
×
1290
            (1, 1) => GenotypeAllele::PhasedMissing,
×
1291
            (e, 1) => GenotypeAllele::Phased((e >> 1) - 1),
×
1292
            (e, 0) => GenotypeAllele::Unphased((e >> 1) - 1),
×
1293
            _ => panic!("unexpected phasing type"),
×
1294
        }
1295
    }
1296

1297
    /// Get the index into the list of alleles.
1298
    pub fn index(self) -> Option<u32> {
86✔
1299
        match self {
95✔
1300
            GenotypeAllele::Unphased(i) | GenotypeAllele::Phased(i) => Some(i as u32),
162✔
1301
            GenotypeAllele::UnphasedMissing | GenotypeAllele::PhasedMissing => None,
10✔
1302
        }
1303
    }
1304
}
1305

1306
impl fmt::Display for GenotypeAllele {
1307
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57✔
1308
        match self.index() {
57✔
1309
            Some(a) => write!(f, "{}", a),
200✔
1310
            None => write!(f, "."),
6✔
1311
        }
1312
    }
1313
}
1314

1315
impl From<GenotypeAllele> for i32 {
1316
    fn from(allele: GenotypeAllele) -> i32 {
33✔
1317
        let (allele, phased) = match allele {
95✔
1318
            GenotypeAllele::UnphasedMissing => (-1, 0),
×
1319
            GenotypeAllele::PhasedMissing => (-1, 1),
2✔
1320
            GenotypeAllele::Unphased(a) => (a, 0),
27✔
1321
            GenotypeAllele::Phased(a) => (a, 1),
13✔
1322
        };
1323
        ((allele + 1) << 1) | phased
33✔
1324
    }
1325
}
1326

1327
impl From<i32> for GenotypeAllele {
1328
    fn from(encoded: i32) -> GenotypeAllele {
88✔
1329
        match (encoded, encoded & 1) {
88✔
1330
            (0, 0) => GenotypeAllele::UnphasedMissing,
7✔
1331
            (1, 1) => GenotypeAllele::PhasedMissing,
7✔
1332
            (e, 1) => GenotypeAllele::Phased((e >> 1) - 1),
30✔
1333
            (e, 0) => GenotypeAllele::Unphased((e >> 1) - 1),
138✔
1334
            _ => panic!("unexpected phasing type"),
×
1335
        }
1336
    }
1337
}
1338

1339
custom_derive! {
1340
    /// Genotype representation as a vector of `GenotypeAllele`.
1341
    #[derive(NewtypeDeref, Debug, Clone, PartialEq, Eq, Hash)]
1342
    pub struct Genotype(Vec<GenotypeAllele>);
1343
}
1344

1345
impl fmt::Display for Genotype {
1346
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32✔
1347
        let Genotype(alleles) = self;
60✔
1348
        write!(f, "{}", alleles[0])?;
116✔
1349
        for a in &alleles[1..] {
61✔
1350
            let sep = match a {
54✔
1351
                GenotypeAllele::Phased(_) | GenotypeAllele::PhasedMissing => '|',
14✔
1352
                GenotypeAllele::Unphased(_) | GenotypeAllele::UnphasedMissing => '/',
18✔
1353
            };
1354
            write!(f, "{}{}", sep, a)?;
79✔
1355
        }
1356
        Ok(())
32✔
1357
    }
1358
}
1359

1360
/// Lazy representation of genotypes, that does no computation until a particular genotype is queried.
1361
#[derive(Debug)]
1362
pub struct Genotypes<'a, B>
1363
where
1364
    B: Borrow<Buffer> + 'a,
1365
{
1366
    encoded: BufferBacked<'a, Vec<&'a [i32]>, B>,
1367
}
1368

1369
impl<'a, B: Borrow<Buffer> + 'a> Genotypes<'a, B> {
1370
    /// Get genotype of ith sample.
1371
    ///
1372
    /// Note that the result complies with the BCF spec. This means that the
1373
    /// first allele will always be marked as `Unphased`. That is, if you have 1|1 in the VCF,
1374
    /// this method will return `[Unphased(1), Phased(1)]`.
1375
    pub fn get(&self, i: usize) -> Genotype {
47✔
1376
        let igt = self.encoded[i];
89✔
1377
        let allelles = igt
84✔
1378
            .iter()
1379
            .take_while(|&&i| i != VECTOR_END_INTEGER)
138✔
1380
            .map(|&i| GenotypeAllele::from(i))
219✔
1381
            .collect();
1382
        Genotype(allelles)
47✔
1383
    }
1384
}
1385

1386
impl Drop for Record {
1387
    fn drop(&mut self) {
450✔
1388
        unsafe { htslib::bcf_destroy(self.inner) };
882✔
1389
    }
1390
}
1391

1392
unsafe impl Send for Record {}
1393

1394
unsafe impl Sync for Record {}
1395

1396
/// Info tag representation.
1397
#[derive(Debug)]
1398
pub struct Info<'a, B: BorrowMut<Buffer> + Borrow<Buffer>> {
1399
    record: &'a Record,
1400
    tag: &'a [u8],
1401
    buffer: B,
1402
}
1403

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

1406
impl<'b, B: BorrowMut<Buffer> + Borrow<Buffer> + 'b> Info<'_, B> {
1407
    /// Short description of info tag.
1408
    pub fn desc(&self) -> String {
×
1409
        str::from_utf8(self.tag).unwrap().to_owned()
×
1410
    }
1411

1412
    fn data(&mut self, data_type: u32) -> Result<Option<i32>> {
141✔
1413
        let mut n: i32 = self.buffer.borrow().len;
419✔
1414
        let c_str = ffi::CString::new(self.tag).unwrap();
558✔
1415
        let ret = unsafe {
1416
            htslib::bcf_get_info_values(
1417
                self.record.header().inner,
143✔
1418
                self.record.inner,
141✔
1419
                c_str.as_ptr() as *mut c_char,
141✔
1420
                &mut self.buffer.borrow_mut().inner,
141✔
1421
                &mut n,
139✔
1422
                data_type as i32,
139✔
1423
            )
1424
        };
1425
        self.buffer.borrow_mut().len = n;
280✔
1426

1427
        match ret {
141✔
1428
            -1 => Err(Error::BcfUndefinedTag { tag: self.desc() }),
×
1429
            -2 => Err(Error::BcfUnexpectedType { tag: self.desc() }),
×
1430
            -3 => Ok(None),
×
1431
            ret => Ok(Some(ret)),
280✔
1432
        }
1433
    }
1434

1435
    /// Get integers from tag. `None` if tag not present in record.
1436
    ///
1437
    /// Import `bcf::record::Numeric` for missing value handling.
1438
    ///
1439
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1440
    /// as along as the data is accessed. If parts of the data are accessed while
1441
    /// the BufferBacked object is already dropped, you will access unallocated
1442
    /// memory.
1443
    pub fn integer(mut self) -> Result<Option<BufferBacked<'b, &'b [i32], B>>> {
2✔
1444
        self.data(htslib::BCF_HT_INT).map(|data| {
7✔
1445
            data.map(|ret| {
5✔
1446
                let values = unsafe {
2✔
1447
                    slice::from_raw_parts(self.buffer.borrow().inner as *const i32, ret as usize)
5✔
1448
                };
1449
                BufferBacked::new(&values[..ret as usize], self.buffer)
4✔
1450
            })
1451
        })
1452
    }
1453

1454
    /// Get floats from tag. `None` if tag not present in record.
1455
    ///
1456
    /// Import `bcf::record::Numeric` for missing value handling.
1457
    ///
1458
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1459
    /// as along as the data is accessed. If parts of the data are accessed while
1460
    /// the BufferBacked object is already dropped, you will access unallocated
1461
    /// memory.
1462
    pub fn float(mut self) -> Result<Option<BufferBacked<'b, &'b [f32], B>>> {
129✔
1463
        self.data(htslib::BCF_HT_REAL).map(|data| {
515✔
1464
            data.map(|ret| {
386✔
1465
                let values = unsafe {
129✔
1466
                    slice::from_raw_parts(self.buffer.borrow().inner as *const f32, ret as usize)
386✔
1467
                };
1468
                BufferBacked::new(&values[..ret as usize], self.buffer)
385✔
1469
            })
1470
        })
1471
    }
1472

1473
    /// Get flags from tag. `false` if not set.
1474
    pub fn flag(&mut self) -> Result<bool> {
×
1475
        self.data(htslib::BCF_HT_FLAG).map(|data| match data {
×
1476
            Some(ret) => ret == 1,
×
1477
            None => false,
×
1478
        })
1479
    }
1480

1481
    /// Get strings from tag. `None` if tag not present in record.
1482
    ///
1483
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1484
    /// as along as the data is accessed. If parts of the data are accessed while
1485
    /// the BufferBacked object is already dropped, you will access unallocated
1486
    /// memory.
1487
    pub fn string(mut self) -> Result<BufferBackedOption<'b, B>> {
12✔
1488
        self.data(htslib::BCF_HT_STR).map(|data| {
46✔
1489
            data.map(|ret| {
34✔
1490
                BufferBacked::new(
12✔
1491
                    unsafe {
2✔
1492
                        slice::from_raw_parts(self.buffer.borrow().inner as *const u8, ret as usize)
24✔
1493
                    }
1494
                    .split(|c| *c == b',')
1,891✔
1495
                    .map(|s| {
38✔
1496
                        // stop at zero character
1497
                        s.split(|c| *c == 0u8)
1,915✔
1498
                            .next()
26✔
1499
                            .expect("Bug: returned string should not be empty.")
50✔
1500
                    })
1501
                    .collect(),
12✔
1502
                    self.buffer,
12✔
1503
                )
1504
            })
1505
        })
1506
    }
1507
}
1508

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

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

1513
fn trim_slice<T: PartialEq + NumericUtils>(s: &[T]) -> &[T] {
199✔
1514
    s.split(|v| v.is_vector_end())
1,444✔
1515
        .next()
1516
        .expect("Bug: returned slice should not be empty.")
1517
}
1518

1519
// Representation of per-sample data.
1520
#[derive(Debug)]
1521
pub struct Format<'a, B: BorrowMut<Buffer> + Borrow<Buffer>> {
1522
    record: &'a Record,
1523
    tag: &'a [u8],
1524
    inner: *mut htslib::bcf_fmt_t,
1525
    buffer: B,
1526
}
1527

1528
impl<'a, 'b, B: BorrowMut<Buffer> + Borrow<Buffer> + 'b> Format<'a, B> {
1529
    /// Create new format data in a given record.
1530
    fn new(record: &'a Record, tag: &'a [u8], buffer: B) -> Format<'a, B> {
180✔
1531
        let c_str = ffi::CString::new(tag).unwrap();
704✔
1532
        let inner = unsafe {
1533
            htslib::bcf_get_fmt(
1534
                record.header().inner,
188✔
1535
                record.inner,
180✔
1536
                c_str.as_ptr() as *mut c_char,
180✔
1537
            )
1538
        };
1539
        Format {
1540
            record,
1541
            tag,
1542
            inner,
1543
            buffer,
1544
        }
1545
    }
1546

1547
    /// Provide short description of format entry (just the tag name).
1548
    pub fn desc(&self) -> String {
×
1549
        str::from_utf8(self.tag).unwrap().to_owned()
×
1550
    }
1551

1552
    pub fn inner(&self) -> &htslib::bcf_fmt_t {
179✔
1553
        unsafe { &*self.inner }
179✔
1554
    }
1555

1556
    pub fn inner_mut(&mut self) -> &mut htslib::bcf_fmt_t {
×
1557
        unsafe { &mut *self.inner }
×
1558
    }
1559

1560
    fn values_per_sample(&self) -> usize {
179✔
1561
        self.inner().n as usize
179✔
1562
    }
1563

1564
    /// Read and decode format data into a given type.
1565
    fn data(&mut self, data_type: u32) -> Result<i32> {
180✔
1566
        let mut n: i32 = self.buffer.borrow().len;
524✔
1567
        let c_str = ffi::CString::new(self.tag).unwrap();
696✔
1568
        let ret = unsafe {
1569
            htslib::bcf_get_format_values(
1570
                self.record.header().inner,
188✔
1571
                self.record.inner,
180✔
1572
                c_str.as_ptr() as *mut c_char,
180✔
1573
                &mut self.buffer.borrow_mut().inner,
180✔
1574
                &mut n,
172✔
1575
                data_type as i32,
172✔
1576
            )
1577
        };
1578
        self.buffer.borrow_mut().len = n;
352✔
1579
        match ret {
180✔
1580
            -1 => Err(Error::BcfUndefinedTag { tag: self.desc() }),
×
1581
            -2 => Err(Error::BcfUnexpectedType { tag: self.desc() }),
×
1582
            -3 => Err(Error::BcfMissingTag {
×
1583
                tag: self.desc(),
×
1584
                record: self.record.desc(),
×
1585
            }),
1586
            ret => Ok(ret),
352✔
1587
        }
1588
    }
1589

1590
    /// Get format data as integers.
1591
    ///
1592
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1593
    /// as long as the data is accessed. If parts of the data are accessed while
1594
    /// the BufferBacked object is already dropped, you will access unallocated
1595
    /// memory.
1596
    pub fn integer(mut self) -> Result<BufferBacked<'b, Vec<&'b [i32]>, B>> {
161✔
1597
        self.data(htslib::BCF_HT_INT).map(|ret| {
638✔
1598
            BufferBacked::new(
161✔
1599
                unsafe {
6✔
1600
                    slice::from_raw_parts(
316✔
1601
                        self.buffer.borrow_mut().inner as *const i32,
167✔
1602
                        ret as usize,
161✔
1603
                    )
1604
                }
1605
                .chunks(self.values_per_sample())
471✔
1606
                .map(trim_slice)
161✔
1607
                .collect(),
161✔
1608
                self.buffer,
161✔
1609
            )
1610
        })
1611
    }
1612

1613
    /// Get format data as floats.
1614
    ///
1615
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1616
    /// as along as the data is accessed. If parts of the data are accessed while
1617
    /// the BufferBacked object is already dropped, you will access unallocated
1618
    /// memory.
1619
    pub fn float(mut self) -> Result<BufferBacked<'b, Vec<&'b [f32]>, B>> {
6✔
1620
        self.data(htslib::BCF_HT_REAL).map(|ret| {
22✔
1621
            BufferBacked::new(
6✔
1622
                unsafe {
2✔
1623
                    slice::from_raw_parts(
10✔
1624
                        self.buffer.borrow_mut().inner as *const f32,
8✔
1625
                        ret as usize,
6✔
1626
                    )
1627
                }
1628
                .chunks(self.values_per_sample())
14✔
1629
                .map(trim_slice)
6✔
1630
                .collect(),
6✔
1631
                self.buffer,
6✔
1632
            )
1633
        })
1634
    }
1635

1636
    /// Get format data as byte slices. To obtain the values strings, use `std::str::from_utf8`.
1637
    ///
1638
    /// **Attention:** the returned BufferBacked which holds the data has to be kept in scope
1639
    /// as along as the data is accessed. If parts of the data are accessed while
1640
    /// the BufferBacked object is already dropped, you will access unallocated
1641
    /// memory.
1642
    pub fn string(mut self) -> Result<BufferBacked<'b, Vec<&'b [u8]>, B>> {
15✔
1643
        self.data(htslib::BCF_HT_STR).map(|ret| {
58✔
1644
            if ret == 0 {
15✔
1645
                return BufferBacked::new(Vec::new(), self.buffer);
5✔
1646
            }
1647
            BufferBacked::new(
2✔
1648
                unsafe {
×
1649
                    slice::from_raw_parts(self.buffer.borrow_mut().inner as *const u8, ret as usize)
4✔
1650
                }
1651
                .chunks(self.values_per_sample())
2✔
1652
                .map(|s| {
28✔
1653
                    // stop at zero character
1654
                    s.split(|c| *c == 0u8)
234✔
1655
                        .next()
26✔
1656
                        .expect("Bug: returned string should not be empty.")
50✔
1657
                })
1658
                .collect(),
2✔
1659
                self.buffer,
2✔
1660
            )
1661
        })
1662
    }
1663
}
1664

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

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

1669
#[derive(Debug)]
1670
pub struct Filters<'a> {
1671
    /// Reference to the `Record` to enumerate records for.
1672
    record: &'a Record,
1673
    /// Index of the next filter to return, if not at end.
1674
    idx: i32,
1675
}
1676

1677
impl<'a> Filters<'a> {
1678
    pub fn new(record: &'a Record) -> Self {
2✔
1679
        Filters { record, idx: 0 }
1680
    }
1681
}
1682

1683
impl Iterator for Filters<'_> {
1684
    type Item = Id;
1685

1686
    fn next(&mut self) -> Option<Id> {
2✔
1687
        if self.record.inner().d.n_flt <= self.idx {
3✔
1688
            None
2✔
1689
        } else {
1690
            let i = self.idx as isize;
×
1691
            self.idx += 1;
1692
            Some(Id(unsafe { *self.record.inner().d.flt.offset(i) } as u32))
1693
        }
1694
    }
1695
}
1696

1697
#[cfg(test)]
1698
mod tests {
1699
    use super::*;
1700
    use crate::bcf::{Format, Header, Writer};
1701
    use tempfile::NamedTempFile;
1702

1703
    #[test]
1704
    fn test_missing_float() {
1705
        let expected: u32 = 0x7F80_0001;
1706
        assert_eq!(MISSING_FLOAT.bits(), expected);
1707
    }
1708

1709
    #[test]
1710
    fn test_vector_end_float() {
1711
        let expected: u32 = 0x7F80_0002;
1712
        assert_eq!(VECTOR_END_FLOAT.bits(), expected);
1713
    }
1714

1715
    #[test]
1716
    fn test_record_rlen() {
1717
        let tmp = NamedTempFile::new().unwrap();
1718
        let path = tmp.path();
1719
        let header = Header::new();
1720
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1721
        let mut record = vcf.empty_record();
1722
        assert_eq!(record.rlen(), 0);
1723
        let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1724
        record.set_alleles(alleles).expect("Failed to set alleles");
1725
        assert_eq!(record.rlen(), 3)
1726
    }
1727

1728
    #[test]
1729
    fn test_record_end() {
1730
        let tmp = NamedTempFile::new().unwrap();
1731
        let path = tmp.path();
1732
        let header = Header::new();
1733
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1734
        let mut record = vcf.empty_record();
1735
        let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1736
        record.set_alleles(alleles).expect("Failed to set alleles");
1737
        record.set_pos(5);
1738

1739
        assert_eq!(record.end(), 8)
1740
    }
1741

1742
    #[test]
1743
    fn test_record_clear() {
1744
        let tmp = NamedTempFile::new().unwrap();
1745
        let path = tmp.path();
1746
        let mut header = Header::new();
1747
        header.push_sample("sample".as_bytes());
1748
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1749
        let mut record = vcf.empty_record();
1750
        let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1751
        record.set_alleles(alleles).expect("Failed to set alleles");
1752
        record.set_pos(6);
1753
        record.clear();
1754

1755
        assert_eq!(record.rlen(), 0);
1756
        assert_eq!(record.sample_count(), 0);
1757
        assert_eq!(record.pos(), 0)
1758
    }
1759

1760
    #[test]
1761
    fn test_record_clone() {
1762
        let tmp = NamedTempFile::new().unwrap();
1763
        let path = tmp.path();
1764
        let header = Header::new();
1765
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1766
        let mut record = vcf.empty_record();
1767
        let alleles: &[&[u8]] = &[b"AGG", b"TG"];
1768
        record.set_alleles(alleles).expect("Failed to set alleles");
1769
        record.set_pos(6);
1770

1771
        let mut cloned_record = record.clone();
1772
        cloned_record.set_pos(5);
1773

1774
        assert_eq!(record.pos(), 6);
1775
        assert_eq!(record.allele_count(), 2);
1776
        assert_eq!(cloned_record.pos(), 5);
1777
        assert_eq!(cloned_record.allele_count(), 2);
1778
    }
1779

1780
    #[test]
1781
    fn test_record_has_filter_pass_is_default() {
1782
        let tmp = NamedTempFile::new().unwrap();
1783
        let path = tmp.path();
1784
        let header = Header::new();
1785
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1786
        let record = vcf.empty_record();
1787

1788
        assert!(record.has_filter("PASS".as_bytes()));
1789
        assert!(record.has_filter(".".as_bytes()));
1790
        assert!(record.has_filter(&Id(0)));
1791
        assert!(!record.has_filter("foo".as_bytes()));
1792
        assert!(!record.has_filter(&Id(2)));
1793
    }
1794

1795
    #[test]
1796
    fn test_record_has_filter_custom() {
1797
        let tmp = NamedTempFile::new().unwrap();
1798
        let path = tmp.path();
1799
        let mut header = Header::new();
1800
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1801
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1802
        let mut record = vcf.empty_record();
1803
        record.push_filter("foo".as_bytes()).unwrap();
1804

1805
        assert!(record.has_filter("foo".as_bytes()));
1806
        assert!(!record.has_filter("PASS".as_bytes()))
1807
    }
1808

1809
    #[test]
1810
    fn test_record_push_filter() {
1811
        let tmp = NamedTempFile::new().unwrap();
1812
        let path = tmp.path();
1813
        let mut header = Header::new();
1814
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1815
        header.push_record(br#"##FILTER=<ID=bar,Description="dranks">"#);
1816
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1817
        let mut record = vcf.empty_record();
1818
        assert!(record.has_filter("PASS".as_bytes()));
1819
        record.push_filter("foo".as_bytes()).unwrap();
1820
        let bar = record.header().name_to_id(cstr8!("bar")).unwrap();
1821
        record.push_filter(&bar).unwrap();
1822
        assert!(record.has_filter("foo".as_bytes()));
1823
        assert!(record.has_filter(&bar));
1824
        assert!(!record.has_filter("PASS".as_bytes()));
1825
        assert!(record.push_filter("baz".as_bytes()).is_err())
1826
    }
1827

1828
    #[test]
1829
    fn test_record_set_filters() {
1830
        let tmp = NamedTempFile::new().unwrap();
1831
        let path = tmp.path();
1832
        let mut header = Header::new();
1833
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1834
        header.push_record(br#"##FILTER=<ID=bar,Description="a horse walks into...">"#);
1835
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1836
        let mut record = vcf.empty_record();
1837
        assert!(record.has_filter("PASS".as_bytes()));
1838
        record
1839
            .set_filters(&["foo".as_bytes(), "bar".as_bytes()])
1840
            .unwrap();
1841
        assert!(record.has_filter("foo".as_bytes()));
1842
        assert!(record.has_filter("bar".as_bytes()));
1843
        assert!(!record.has_filter("PASS".as_bytes()));
1844
        let filters: &[&Id] = &[];
1845
        record.set_filters(filters).unwrap();
1846
        assert!(record.has_filter("PASS".as_bytes()));
1847
        assert!(!record.has_filter("foo".as_bytes()));
1848
        assert!(record
1849
            .set_filters(&["foo".as_bytes(), "baz".as_bytes()])
1850
            .is_err())
1851
    }
1852

1853
    #[test]
1854
    fn test_record_remove_filter() {
1855
        let tmp = NamedTempFile::new().unwrap();
1856
        let path = tmp.path();
1857
        let mut header = Header::new();
1858
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1859
        header.push_record(br#"##FILTER=<ID=bar,Description="a horse walks into...">"#);
1860
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1861
        let mut record = vcf.empty_record();
1862
        let foo = record.header().name_to_id(cstr8!("foo")).unwrap();
1863
        let bar = record.header().name_to_id(cstr8!("bar")).unwrap();
1864
        record.set_filters(&[&foo, &bar]).unwrap();
1865
        assert!(record.has_filter(&foo));
1866
        assert!(record.has_filter(&bar));
1867
        record.remove_filter(&foo, true).unwrap();
1868
        assert!(!record.has_filter(&foo));
1869
        assert!(record.has_filter(&bar));
1870
        assert!(record.remove_filter("baz".as_bytes(), true).is_err());
1871
        record.remove_filter(&bar, true).unwrap();
1872
        assert!(!record.has_filter(&bar));
1873
        assert!(record.has_filter("PASS".as_bytes()));
1874
    }
1875

1876
    #[test]
1877
    fn test_record_to_vcf_string_err() {
1878
        let tmp = NamedTempFile::new().unwrap();
1879
        let path = tmp.path();
1880
        let header = Header::new();
1881
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1882
        let record = vcf.empty_record();
1883
        assert!(record.to_vcf_string().is_err());
1884
    }
1885

1886
    #[test]
1887
    fn test_record_to_vcf_string() {
1888
        let tmp = NamedTempFile::new().unwrap();
1889
        let path = tmp.path();
1890
        let mut header = Header::new();
1891
        header.push_record(b"##contig=<ID=chr1,length=1000>");
1892
        header.push_record(br#"##FILTER=<ID=foo,Description="sample is a foo fighter">"#);
1893
        let vcf = Writer::from_path(path, &header, true, Format::Vcf).unwrap();
1894
        let mut record = vcf.empty_record();
1895
        record.push_filter("foo".as_bytes()).unwrap();
1896
        assert_eq!(
1897
            record.to_vcf_string().unwrap(),
1898
            "chr1\t1\t.\t.\t.\t0\tfoo\t.\n"
1899
        );
1900
    }
1901
}
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