use crate::error::DryIceError;
pub trait QualityCodec: Sized {
const TYPE_TAG: [u8; 16];
const LOSSY: bool;
const IS_IDENTITY: bool = false;
fn encode_into(quality: &[u8], output: &mut Vec<u8>) -> Result<(), DryIceError>;
fn decode_into(
encoded: &[u8],
original_len: usize,
output: &mut Vec<u8>,
) -> Result<(), DryIceError>;
fn encode(quality: &[u8]) -> Result<Vec<u8>, DryIceError> {
let mut out = Vec::new();
Self::encode_into(quality, &mut out)?;
Ok(out)
}
fn decode(encoded: &[u8], original_len: usize) -> Result<Vec<u8>, DryIceError> {
let mut out = Vec::new();
Self::decode_into(encoded, original_len, &mut out)?;
Ok(out)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RawQualityCodec;
impl QualityCodec for RawQualityCodec {
const TYPE_TAG: [u8; 16] = *b"dryi:qual:raw!!!";
const LOSSY: bool = false;
const IS_IDENTITY: bool = true;
fn encode_into(quality: &[u8], output: &mut Vec<u8>) -> Result<(), DryIceError> {
output.extend_from_slice(quality);
Ok(())
}
fn decode_into(
encoded: &[u8],
_original_len: usize,
output: &mut Vec<u8>,
) -> Result<(), DryIceError> {
output.extend_from_slice(encoded);
Ok(())
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct BinnedQualityCodec;
const PHRED_OFFSET: u8 = 33;
fn bin_phred(phred: u8) -> u8 {
match phred {
0..=1 => 0,
2..=9 => 6,
10..=19 => 15,
20..=24 => 22,
25..=29 => 27,
30..=34 => 33,
35..=39 => 37,
_ => 40,
}
}
impl QualityCodec for BinnedQualityCodec {
const TYPE_TAG: [u8; 16] = *b"dryi:qual:binned";
const LOSSY: bool = true;
fn encode_into(quality: &[u8], output: &mut Vec<u8>) -> Result<(), DryIceError> {
output.extend(quality.iter().map(|&q| {
let phred = q.saturating_sub(PHRED_OFFSET);
bin_phred(phred) + PHRED_OFFSET
}));
Ok(())
}
fn decode_into(
encoded: &[u8],
_original_len: usize,
output: &mut Vec<u8>,
) -> Result<(), DryIceError> {
output.extend_from_slice(encoded);
Ok(())
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct OmittedQualityCodec;
impl QualityCodec for OmittedQualityCodec {
const TYPE_TAG: [u8; 16] = *b"dryi:qual:omittd";
const LOSSY: bool = true;
fn encode_into(_quality: &[u8], _output: &mut Vec<u8>) -> Result<(), DryIceError> {
Ok(())
}
fn decode_into(
_encoded: &[u8],
_original_len: usize,
_output: &mut Vec<u8>,
) -> Result<(), DryIceError> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_round_trip() {
let qual = b"!!!!####";
let encoded = RawQualityCodec::encode(qual).expect("encode should succeed");
let decoded = RawQualityCodec::decode(&encoded, qual.len()).expect("decode should succeed");
assert_eq!(&decoded, qual);
}
#[test]
fn binned_produces_valid_phred33() {
let qual: Vec<u8> = (33..=73).collect();
let encoded = BinnedQualityCodec::encode(&qual).expect("encode should succeed");
for &q in &encoded {
assert!(
q >= PHRED_OFFSET,
"binned quality should be >= Phred+33 offset"
);
}
}
#[test]
fn binned_is_idempotent() {
let qual: Vec<u8> = (33..=73).collect();
let once = BinnedQualityCodec::encode(&qual).expect("first encode");
let twice = BinnedQualityCodec::encode(&once).expect("second encode");
assert_eq!(once, twice, "binning should be idempotent");
}
#[test]
fn binned_preserves_length() {
let qual = b"!!!!!!!!!!!";
let encoded = BinnedQualityCodec::encode(qual).expect("encode should succeed");
assert_eq!(encoded.len(), qual.len());
}
#[test]
fn binned_high_quality_bins_correctly() {
let q40 = vec![40 + PHRED_OFFSET];
let encoded = BinnedQualityCodec::encode(&q40).expect("encode should succeed");
assert_eq!(encoded[0], 40 + PHRED_OFFSET);
}
#[test]
fn omitted_produces_empty() {
let qual = b"!!!!####";
let encoded = OmittedQualityCodec::encode(qual).expect("encode should succeed");
assert!(encoded.is_empty());
}
}