use std::{
ffi::CString,
io::{self, Write},
num::NonZeroUsize,
};
use byteorder::{LittleEndian, WriteBytesExt};
use noodles_sam::{
self as sam,
header::{record::value::map, ReferenceSequences},
};
pub(super) fn write_header<W>(writer: &mut W, header: &sam::Header) -> io::Result<()>
where
W: Write,
{
write_raw_header(writer, header)?;
write_reference_sequences(writer, header.reference_sequences())?;
Ok(())
}
fn write_raw_header<W>(writer: &mut W, header: &sam::Header) -> io::Result<()>
where
W: Write,
{
use crate::MAGIC_NUMBER;
writer.write_all(MAGIC_NUMBER)?;
let text = header.to_string();
let l_text =
i32::try_from(text.len()).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
writer.write_i32::<LittleEndian>(l_text)?;
writer.write_all(text.as_bytes())?;
Ok(())
}
pub fn write_reference_sequences<W>(
writer: &mut W,
reference_sequences: &ReferenceSequences,
) -> io::Result<()>
where
W: Write,
{
let n_ref = i32::try_from(reference_sequences.len())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
writer.write_i32::<LittleEndian>(n_ref)?;
for (name, reference_sequence) in reference_sequences {
write_reference_sequence(writer, name, reference_sequence.length())?;
}
Ok(())
}
fn write_reference_sequence<W>(
writer: &mut W,
reference_sequence_name: &map::reference_sequence::Name,
length: NonZeroUsize,
) -> io::Result<()>
where
W: Write,
{
let c_name = CString::new(reference_sequence_name.as_bytes())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let name = c_name.as_bytes_with_nul();
let l_name =
u32::try_from(name.len()).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
writer.write_u32::<LittleEndian>(l_name)?;
writer.write_all(name)?;
let l_ref = i32::try_from(usize::from(length))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
writer.write_i32::<LittleEndian>(l_ref)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_write_raw_header() -> Result<(), Box<dyn std::error::Error>> {
use sam::header::record::value::{
map::{self, header::Version},
Map,
};
let header = sam::Header::builder()
.set_header(Map::<map::Header>::new(Version::new(1, 6)))
.build();
let mut buf = Vec::new();
write_raw_header(&mut buf, &header)?;
let mut expected = vec![
b'B', b'A', b'M', 0x01, 0x0b, 0x00, 0x00, 0x00, ];
expected.extend_from_slice(b"@HD\tVN:1.6\n"); assert_eq!(buf, expected);
Ok(())
}
#[test]
fn test_write_reference_sequences() -> Result<(), Box<dyn std::error::Error>> {
use sam::header::record::value::{map::ReferenceSequence, Map};
let reference_sequences = [(
"sq0".parse()?,
Map::<ReferenceSequence>::new(NonZeroUsize::try_from(8)?),
)]
.into_iter()
.collect();
let mut buf = Vec::new();
write_reference_sequences(&mut buf, &reference_sequences)?;
let expected = [
0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, b's', b'q', b'0', 0x00, 0x08, 0x00, 0x00, 0x00, ];
assert_eq!(buf, expected);
Ok(())
}
#[test]
fn test_write_reference_sequence() -> Result<(), Box<dyn std::error::Error>> {
let mut buf = Vec::new();
let reference_sequence_name = "sq0".parse()?;
let length = NonZeroUsize::try_from(8)?;
write_reference_sequence(&mut buf, &reference_sequence_name, length)?;
let expected = [
0x04, 0x00, 0x00, 0x00, 0x73, 0x71, 0x30, 0x00, 0x08, 0x00, 0x00, 0x00, ];
assert_eq!(buf, expected);
Ok(())
}
}