pub mod field;
use std::io;
use noodles::sam::alignment::record::{Data, data::field::Tag};
use noodles::sam::alignment::record_buf::data::field::Value as RecordBufValue;
use self::field::write_field;
use super::num::{write_f32_le, write_i16_le, write_u32_le};
#[allow(clippy::needless_pass_by_value)]
pub(super) fn write_data<D>(dst: &mut Vec<u8>, data: D) -> io::Result<()>
where
D: Data,
{
for result in data.iter() {
let (tag, value) = result?;
if &tag == Tag::CIGAR.as_ref() {
continue;
}
write_field(dst, tag, &value)?;
}
Ok(())
}
pub(super) fn write_data_fast(
dst: &mut Vec<u8>,
data: &noodles::sam::alignment::record_buf::Data,
) -> io::Result<()> {
for (tag, value) in data.iter() {
if tag == Tag::CIGAR {
continue;
}
if !try_write_common_tag(dst, tag, value) {
write_field_from_value(dst, tag, value)?;
}
}
Ok(())
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
fn try_write_common_tag(dst: &mut Vec<u8>, tag: Tag, value: &RecordBufValue) -> bool {
let tag_bytes = tag.as_ref();
match (tag_bytes, value) {
([b'N', b'H'], RecordBufValue::UInt8(n)) => {
dst.extend([b'N', b'H', b'C', *n]);
true
}
([b'N', b'M'], RecordBufValue::Int32(n)) => {
dst.extend([b'N', b'M', b'i']);
dst.extend(n.to_le_bytes());
true
}
([b'A', b'S'], RecordBufValue::Int32(n)) => {
dst.extend([b'A', b'S', b'i']);
dst.extend(n.to_le_bytes());
true
}
(
[b'R' | b'Q' | b'O', b'X']
| [b'M', b'I' | b'C']
| [b'B', b'Z' | b'C']
| [b'R', b'G']
| [b'Q', b'T'],
RecordBufValue::String(s),
) => {
dst.extend([tag_bytes[0], tag_bytes[1], b'Z']);
dst.extend_from_slice(s.as_ref());
dst.push(0); true
}
([b'c' | b'a' | b'b', b'D' | b'M'], RecordBufValue::Int16(n)) => {
dst.extend([tag_bytes[0], tag_bytes[1], b's']);
write_i16_le(dst, *n);
true
}
([b'c' | b'a' | b'b', b'E'], RecordBufValue::Float(n)) => {
dst.extend([tag_bytes[0], tag_bytes[1], b'f']);
write_f32_le(dst, *n);
true
}
([b'c' | b'a' | b'b', b'd' | b'e'], RecordBufValue::Array(arr)) => {
if let noodles::sam::alignment::record_buf::data::field::value::Array::Int16(values) =
arr
{
dst.extend([tag_bytes[0], tag_bytes[1], b'B', b's']);
debug_assert!(u32::try_from(values.len()).is_ok(), "Int16 array too large for BAM");
write_u32_le(dst, values.len() as u32);
for &v in values {
write_i16_le(dst, v);
}
true
} else {
false
}
}
([b'a' | b'b', b'c' | b'q'], RecordBufValue::String(s)) => {
dst.extend([tag_bytes[0], tag_bytes[1], b'Z']);
dst.extend_from_slice(s.as_ref());
dst.push(0);
true
}
_ => false,
}
}
#[inline]
fn write_field_from_value(dst: &mut Vec<u8>, tag: Tag, value: &RecordBufValue) -> io::Result<()> {
use noodles::sam::alignment::record::data::field::Value;
let value_ref: Value<'_> = value.into();
write_field(dst, tag, &value_ref)
}
#[cfg(test)]
mod tests {
use noodles::sam::alignment::record_buf::{Data as DataBuf, data::field::Value};
use super::*;
#[test]
fn test_write_data() -> io::Result<()> {
fn t(buf: &mut Vec<u8>, data: &DataBuf, expected: &[u8]) -> io::Result<()> {
buf.clear();
write_data(buf, data)?;
assert_eq!(buf, expected);
Ok(())
}
let mut buf = Vec::new();
let data = DataBuf::default();
t(&mut buf, &data, &[])?;
let data = [(Tag::ALIGNMENT_HIT_COUNT, Value::from(1))].into_iter().collect();
t(&mut buf, &data, &[b'N', b'H', b'C', 0x01])?;
let data =
[(Tag::ALIGNMENT_HIT_COUNT, Value::from(1)), (Tag::READ_GROUP, Value::from("rg0"))]
.into_iter()
.collect();
t(
&mut buf,
&data,
&[
b'N', b'H', b'C', 0x01, b'R', b'G', b'Z', b'r', b'g', b'0', 0x00, ],
)?;
Ok(())
}
}