use std::borrow::Cow;
use crate::error::Result;
use crate::format::Format;
use crate::qual::{self, PHRED33};
use crate::record::Sequence;
use crate::seq::{self, BaseCounts};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SequenceRef<'a> {
id: &'a [u8],
description: Option<&'a [u8]>,
seq: &'a [u8],
quality: Option<&'a [u8]>,
}
impl<'a> SequenceRef<'a> {
pub(crate) fn new(
id: &'a [u8],
description: Option<&'a [u8]>,
seq: &'a [u8],
quality: Option<&'a [u8]>,
) -> SequenceRef<'a> {
SequenceRef {
id,
description,
seq,
quality,
}
}
pub fn id(&self) -> &'a [u8] {
self.id
}
pub fn id_str(&self) -> Cow<'a, str> {
String::from_utf8_lossy(self.id)
}
pub fn description(&self) -> Option<&'a [u8]> {
self.description
}
pub fn description_str(&self) -> Option<Cow<'a, str>> {
self.description.map(String::from_utf8_lossy)
}
pub fn seq(&self) -> &'a [u8] {
self.seq
}
pub fn quality(&self) -> Option<&'a [u8]> {
self.quality
}
pub fn len(&self) -> usize {
self.seq.len()
}
pub fn is_empty(&self) -> bool {
self.seq.is_empty()
}
pub fn has_quality(&self) -> bool {
self.quality.is_some()
}
pub fn format(&self) -> Format {
if self.has_quality() {
Format::Fastq
} else {
Format::Fasta
}
}
pub fn base_counts(&self) -> BaseCounts {
BaseCounts::of(self.seq)
}
pub fn gc_content(&self) -> Option<f64> {
seq::gc_content(self.seq)
}
pub fn mean_quality(&self) -> Option<f64> {
qual::mean_quality(self.quality?, PHRED33)
}
pub fn expected_errors(&self) -> Option<f64> {
Some(qual::expected_errors(self.quality?, PHRED33))
}
pub fn kmers(&self, k: usize) -> impl Iterator<Item = &'a [u8]> {
seq::kmers(self.seq, k)
}
pub fn to_owned(&self) -> Sequence {
Sequence {
id: self.id_str().into_owned(),
description: self.description_str().map(Cow::into_owned),
seq: self.seq.to_vec(),
quality: self.quality.map(<[u8]>::to_vec),
}
}
pub fn write<W: std::io::Write>(
&self,
out: &mut W,
format: Format,
line_width: Option<usize>,
) -> Result<()> {
match format {
Format::Fasta => {
out.write_all(b">")?;
self.write_header(out)?;
match line_width.filter(|w| *w > 0) {
None => {
out.write_all(self.seq)?;
out.write_all(b"\n")?;
}
Some(width) => {
if self.seq.is_empty() {
out.write_all(b"\n")?;
}
for chunk in self.seq.chunks(width) {
out.write_all(chunk)?;
out.write_all(b"\n")?;
}
}
}
}
Format::Fastq => {
let quality = self.quality.ok_or_else(|| crate::Error::MissingQuality {
id: self.id_str().into_owned(),
})?;
out.write_all(b"@")?;
self.write_header(out)?;
out.write_all(self.seq)?;
out.write_all(b"\n+\n")?;
out.write_all(quality)?;
out.write_all(b"\n")?;
}
}
Ok(())
}
fn write_header<W: std::io::Write>(&self, out: &mut W) -> Result<()> {
out.write_all(self.id)?;
if let Some(description) = self.description {
out.write_all(b" ")?;
out.write_all(description)?;
}
out.write_all(b"\n")?;
Ok(())
}
}
impl PartialEq<Sequence> for SequenceRef<'_> {
fn eq(&self, other: &Sequence) -> bool {
self.id == other.id.as_bytes()
&& self.description == other.description.as_deref().map(str::as_bytes)
&& self.seq == other.seq.as_slice()
&& self.quality == other.quality.as_deref()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accessors_and_conversion() {
let record = SequenceRef::new(b"r1", Some(b"a sample"), b"ACGTN", Some(b"IIII!"));
assert_eq!(record.id(), b"r1");
assert_eq!(record.id_str(), "r1");
assert_eq!(record.description_str().unwrap(), "a sample");
assert_eq!(record.len(), 5);
assert!(record.has_quality());
assert_eq!(record.format(), Format::Fastq);
assert_eq!(record.gc_content(), Some(0.5));
assert_eq!(record.kmers(4).count(), 2);
let owned = record.to_owned();
assert_eq!(owned.id, "r1");
assert_eq!(owned.description.as_deref(), Some("a sample"));
assert_eq!(owned.seq, b"ACGTN");
assert_eq!(owned.quality.as_deref(), Some(&b"IIII!"[..]));
assert!(record == owned);
}
#[test]
fn non_utf8_ids_survive_as_bytes() {
let record = SequenceRef::new(&[b'i', 0xff], None, b"AC", None);
assert_eq!(record.id(), &[b'i', 0xff]);
assert_eq!(record.id_str(), "i\u{fffd}");
}
#[test]
fn writes_both_formats() {
let record = SequenceRef::new(b"r", Some(b"d"), b"ACGTAC", Some(b"IIIIII"));
let mut out = Vec::new();
record.write(&mut out, Format::Fastq, None).unwrap();
assert_eq!(out, b"@r d\nACGTAC\n+\nIIIIII\n");
let mut out = Vec::new();
record.write(&mut out, Format::Fasta, Some(4)).unwrap();
assert_eq!(out, b">r d\nACGT\nAC\n");
let fasta = SequenceRef::new(b"r", None, b"AC", None);
let mut out = Vec::new();
assert!(fasta.write(&mut out, Format::Fastq, None).is_err());
}
}