use rand::{Rng, RngExt};
use crate::error::{Result, WriteError};
pub(crate) const RNG_SEED: u64 = 42;
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub enum Policy {
#[default]
IgnoreSequence,
BreakOnInvalid,
RandomDraw,
SetToA,
SetToC,
SetToG,
SetToT,
}
impl Policy {
fn fill_with_known(sequence: &[u8], val: u8, ibuf: &mut Vec<u8>) {
for &n in sequence {
ibuf.push(match n {
b'A' | b'C' | b'G' | b'T' => n,
_ => val,
});
}
}
fn fill_with_random<R: Rng>(sequence: &[u8], rng: &mut R, ibuf: &mut Vec<u8>) {
for &n in sequence {
ibuf.push(match n {
b'A' | b'C' | b'G' | b'T' => n,
_ => b"ACGT"[rng.random_range(0..4)],
});
}
}
pub(crate) fn handle<R: Rng>(
self,
sequence: &[u8],
ibuf: &mut Vec<u8>,
rng: &mut R,
) -> Result<bool> {
ibuf.clear();
match self {
Self::IgnoreSequence => Ok(false),
Self::BreakOnInvalid => {
let seq_str = std::str::from_utf8(sequence)?.to_string();
Err(WriteError::InvalidNucleotideSequence(seq_str).into())
}
Self::RandomDraw => {
Self::fill_with_random(sequence, rng, ibuf);
Ok(true)
}
Self::SetToA => {
Self::fill_with_known(sequence, b'A', ibuf);
Ok(true)
}
Self::SetToC => {
Self::fill_with_known(sequence, b'C', ibuf);
Ok(true)
}
Self::SetToG => {
Self::fill_with_known(sequence, b'G', ibuf);
Ok(true)
}
Self::SetToT => {
Self::fill_with_known(sequence, b'T', ibuf);
Ok(true)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand::rngs::StdRng;
fn run(policy: Policy, seq: &[u8]) -> (Result<bool>, Vec<u8>) {
let mut output = Vec::new();
let mut rng = StdRng::seed_from_u64(RNG_SEED);
let result = policy.handle(seq, &mut output, &mut rng);
(result, output)
}
#[test]
fn test_default_policy() {
assert!(matches!(Policy::default(), Policy::IgnoreSequence));
}
#[test]
fn test_ignore_sequence_policy() {
let (result, output) = run(Policy::IgnoreSequence, b"ACGTNX");
assert!(!result.unwrap()); assert!(output.is_empty());
}
#[test]
fn test_break_on_invalid_policy() {
let (result, _) = run(Policy::BreakOnInvalid, b"ACGTNX");
assert!(matches!(
result.unwrap_err(),
crate::error::Error::WriteError(WriteError::InvalidNucleotideSequence(_))
));
}
#[test]
fn test_set_to_policies() {
let cases: &[(Policy, &[u8], &[u8])] = &[
(Policy::SetToA, b"ACGTNX", b"ACGTAA"),
(Policy::SetToC, b"ACGTNX", b"ACGTCC"),
(Policy::SetToG, b"ACGTNX", b"ACGTGG"),
(Policy::SetToT, b"ACGTNX", b"ACGTTT"),
(Policy::SetToA, b"ACGTACGT", b"ACGTACGT"),
(Policy::SetToA, b"", b""),
(Policy::SetToG, b"NNNXXX", b"GGGGGG"),
(Policy::SetToA, b"acgt", b"AAAA"),
(Policy::SetToC, b"AcGt", b"ACGC"),
(Policy::SetToT, b"RYWSMK", b"TTTTTT"),
];
for &(policy, seq, expected) in cases {
let (result, output) = run(policy, seq);
assert!(result.unwrap(), "{policy:?} on {seq:?}");
assert_eq!(output, expected, "{policy:?} on {seq:?}");
}
}
#[test]
fn test_random_draw_policy() {
let (result, output) = run(Policy::RandomDraw, b"ACGTNX");
assert!(result.unwrap());
assert_eq!(&output[0..4], b"ACGT"); assert!(output[4..].iter().all(|n| b"ACGT".contains(n)));
}
#[test]
fn test_random_draw_deterministic_with_seed() {
let (_, output1) = run(Policy::RandomDraw, b"NNNN");
let (_, output2) = run(Policy::RandomDraw, b"NNNN");
assert_eq!(output1, output2); }
#[test]
fn test_buffer_cleared_before_processing() {
let policy = Policy::SetToA;
let mut output = vec![b'X', b'Y', b'Z']; let mut rng = StdRng::seed_from_u64(RNG_SEED);
policy.handle(b"ACGT", &mut output, &mut rng).unwrap();
assert_eq!(output, b"ACGT");
policy.handle(b"TT", &mut output, &mut rng).unwrap();
assert_eq!(output, b"TT"); }
}