use crate::error::DryIceError;
pub trait NameCodec: Sized {
const TYPE_TAG: [u8; 16];
const LOSSY: bool;
const IS_IDENTITY: bool = false;
type Decoded;
fn encode_into(name: &[u8], output: &mut Vec<u8>) -> Result<(), DryIceError>;
fn decode(encoded: &[u8], original_len: usize) -> Result<Self::Decoded, DryIceError>;
fn as_bytes(decoded: &Self::Decoded) -> &[u8];
fn encode(name: &[u8]) -> Result<Vec<u8>, DryIceError> {
let mut out = Vec::new();
Self::encode_into(name, &mut out)?;
Ok(out)
}
fn decode_to_bytes_into(
encoded: &[u8],
original_len: usize,
output: &mut Vec<u8>,
) -> Result<(), DryIceError> {
let decoded = Self::decode(encoded, original_len)?;
output.extend_from_slice(Self::as_bytes(&decoded));
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RawName(pub Vec<u8>);
impl RawName {
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RawNameCodec;
impl NameCodec for RawNameCodec {
const TYPE_TAG: [u8; 16] = *b"dryi:name:raw!!!";
const LOSSY: bool = false;
const IS_IDENTITY: bool = true;
type Decoded = RawName;
fn encode_into(name: &[u8], output: &mut Vec<u8>) -> Result<(), DryIceError> {
output.extend_from_slice(name);
Ok(())
}
fn decode(encoded: &[u8], _original_len: usize) -> Result<RawName, DryIceError> {
Ok(RawName(encoded.to_vec()))
}
fn as_bytes(decoded: &RawName) -> &[u8] {
&decoded.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OmittedName;
#[derive(Debug, Clone, Copy, Default)]
pub struct OmittedNameCodec;
impl NameCodec for OmittedNameCodec {
const TYPE_TAG: [u8; 16] = *b"dryi:name:omittd";
const LOSSY: bool = true;
type Decoded = OmittedName;
fn encode_into(_name: &[u8], _output: &mut Vec<u8>) -> Result<(), DryIceError> {
Ok(())
}
fn decode(_encoded: &[u8], _original_len: usize) -> Result<OmittedName, DryIceError> {
Ok(OmittedName)
}
fn as_bytes(_decoded: &OmittedName) -> &[u8] {
&[]
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SplitName {
pub id: Vec<u8>,
pub description: Vec<u8>,
full: Vec<u8>,
}
impl SplitName {
#[must_use]
pub fn id(&self) -> &[u8] {
&self.id
}
#[must_use]
pub fn description(&self) -> &[u8] {
&self.description
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.full
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SplitNameCodec;
impl NameCodec for SplitNameCodec {
const TYPE_TAG: [u8; 16] = *b"dryi:name:split!";
const LOSSY: bool = false;
type Decoded = SplitName;
fn encode_into(name: &[u8], output: &mut Vec<u8>) -> Result<(), DryIceError> {
let split_pos = name.iter().position(|&b| b == b' ');
let (id, desc) = match split_pos {
Some(pos) => (&name[..pos], &name[pos + 1..]),
None => (name, &[] as &[u8]),
};
let id_len = u32::try_from(id.len()).map_err(|_| DryIceError::SectionOverflow {
field: "name identifier length",
})?;
output.extend_from_slice(&id_len.to_le_bytes());
output.extend_from_slice(id);
output.extend_from_slice(desc);
Ok(())
}
fn decode(encoded: &[u8], _original_len: usize) -> Result<SplitName, DryIceError> {
if encoded.len() < 4 {
return Err(DryIceError::CorruptBlockLayout {
message: "SplitNameCodec encoded buffer too short for id_len",
});
}
let id_len = u32::from_le_bytes([encoded[0], encoded[1], encoded[2], encoded[3]]) as usize;
let id_end = 4 + id_len;
if id_end > encoded.len() {
return Err(DryIceError::CorruptBlockLayout {
message: "SplitNameCodec id_len exceeds buffer",
});
}
let id = encoded[4..id_end].to_vec();
let description = encoded[id_end..].to_vec();
let full = if description.is_empty() {
id.clone()
} else {
let mut f = Vec::with_capacity(id.len() + 1 + description.len());
f.extend_from_slice(&id);
f.push(b' ');
f.extend_from_slice(&description);
f
};
Ok(SplitName {
id,
description,
full,
})
}
fn as_bytes(decoded: &SplitName) -> &[u8] {
&decoded.full
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_round_trip() {
let name = b"@instrument:run:flowcell 1:N:0:ATCACG";
let encoded = RawNameCodec::encode(name).expect("encode should succeed");
let decoded = RawNameCodec::decode(&encoded, name.len()).expect("decode should succeed");
assert_eq!(decoded.as_bytes(), name);
}
#[test]
fn omitted_produces_empty() {
let name = b"@some_read_name";
let encoded = OmittedNameCodec::encode(name).expect("encode should succeed");
assert!(encoded.is_empty());
let decoded =
OmittedNameCodec::decode(&encoded, name.len()).expect("decode should succeed");
assert_eq!(OmittedNameCodec::as_bytes(&decoded), b"");
}
#[test]
fn split_round_trip_with_space() {
let name = b"instrument:run:flowcell 1:N:0:ATCACG";
let encoded = SplitNameCodec::encode(name).expect("encode should succeed");
let decoded = SplitNameCodec::decode(&encoded, name.len()).expect("decode should succeed");
assert_eq!(decoded.as_bytes(), name);
assert_eq!(decoded.id(), b"instrument:run:flowcell");
assert_eq!(decoded.description(), b"1:N:0:ATCACG");
}
#[test]
fn split_round_trip_without_space() {
let name = b"simple_read_name";
let encoded = SplitNameCodec::encode(name).expect("encode should succeed");
let decoded = SplitNameCodec::decode(&encoded, name.len()).expect("decode should succeed");
assert_eq!(decoded.as_bytes(), name);
assert_eq!(decoded.id(), name.as_slice());
assert!(decoded.description().is_empty());
}
#[test]
fn split_round_trip_empty_name() {
let name = b"";
let encoded = SplitNameCodec::encode(name).expect("encode should succeed");
let decoded = SplitNameCodec::decode(&encoded, name.len()).expect("decode should succeed");
assert_eq!(decoded.as_bytes(), name);
}
#[test]
fn split_round_trip_multiple_spaces() {
let name = b"id part1 part2 part3";
let encoded = SplitNameCodec::encode(name).expect("encode should succeed");
let decoded = SplitNameCodec::decode(&encoded, name.len()).expect("decode should succeed");
assert_eq!(decoded.as_bytes(), name);
assert_eq!(decoded.id(), b"id");
assert_eq!(decoded.description(), b"part1 part2 part3");
}
#[test]
fn split_trailing_space_drops_empty_description() {
let name = b"id ";
let encoded = SplitNameCodec::encode(name).expect("encode should succeed");
let decoded = SplitNameCodec::decode(&encoded, name.len()).expect("decode should succeed");
assert_eq!(decoded.as_bytes(), b"id");
assert_eq!(decoded.id(), b"id");
assert!(decoded.description().is_empty());
}
}