use std::error::Error as StdError;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub enum Error {
#[error("Error processing header: {0}")]
HeaderError(#[from] HeaderError),
#[error("Error processing CBQ: {0}")]
CbqError(#[from] CbqError),
#[error("Error writing file: {0}")]
WriteError(#[from] WriteError),
#[error("Error reading file: {0}")]
ReadError(#[from] ReadError),
#[error("Error building file: {0}")]
BuilderError(#[from] BuilderError),
#[error("Error processing Index: {0}")]
IndexError(#[from] IndexError),
#[error("Error with IO: {0}")]
IoError(#[from] std::io::Error),
#[error("Error with UTF8: {0}")]
Utf8Error(#[from] std::str::Utf8Error),
ExtensionError(#[from] ExtensionError),
#[error("Bitnuc error: {0}")]
BitnucError(#[from] bitnuc::Error),
#[cfg(feature = "anyhow")]
#[error("Generic error: {0}")]
AnyhowError(#[from] anyhow::Error),
#[error("Generic error: {0}")]
GenericError(#[from] Box<dyn StdError + Send + Sync>),
#[cfg(feature = "paraseq")]
#[error("Fastx encoding error: {0}")]
FastxEncodingError(#[from] FastxEncodingError),
}
#[derive(thiserror::Error, Debug)]
pub enum HeaderError {
#[error("Invalid magic number: {0}")]
InvalidMagicNumber(u32),
#[error("Invalid format version: {0}")]
InvalidFormatVersion(u8),
#[error("Invalid reserved bytes")]
InvalidReservedBytes,
#[error("Invalid bit size found in header: {0} - expecting [2,4]")]
InvalidBitSize(u8),
#[error("Invalid number of bytes provided: {0}. Expected: {1}")]
InvalidSize(usize, usize),
}
#[derive(thiserror::Error, Debug)]
pub enum ReadError {
#[error("File is not regular")]
IncompatibleFile,
#[error(
"Number of bytes in file does not match expectation - possibly truncated at byte pos {0}"
)]
FileTruncation(usize),
#[error("Requested record index ({requested_index}) is out of record range ({max_index})")]
OutOfRange {
requested_index: usize,
max_index: usize,
},
#[error("Invalid range specified: start ({start}) is greater than end ({end})")]
InvalidRange { start: usize, end: usize },
#[error("End of stream reached")]
EndOfStream,
#[error("Partial record at end of stream ({0} bytes)")]
PartialRecord(usize),
#[error("Unexpected Block Magic Number found: {0} at position {1}")]
InvalidBlockMagicNumber(u64, usize),
#[error("Unable to find an expected full block at position {0}")]
UnexpectedEndOfFile(usize),
#[error("Unexpected file metadata")]
InvalidFileType,
#[error("Missing index end magic number")]
MissingIndexEndMagic,
}
#[derive(thiserror::Error, Debug)]
pub enum BuilderError {
#[error("Missing sequence length")]
MissingSlen,
}
#[derive(thiserror::Error, Debug)]
pub enum WriteError {
#[error(
"Cannot push record ({attribute}: {actual}) with writer configuration ({attribute}: {expected})"
)]
ConfigurationMismatch {
attribute: &'static str,
expected: bool,
actual: bool,
},
#[error("Cannot ingest writer with incompatible formats")]
FormatMismatch,
#[error(
"Missing required sequence length, expected (primary: {exp_primary}, extended: {exp_extended}), got (primary: {obs_primary}, extended: {obs_extended})"
)]
MissingSequenceLength {
exp_primary: bool,
exp_extended: bool,
obs_primary: bool,
obs_extended: bool,
},
#[error("Sequence length ({got}) does not match the header ({expected})")]
UnexpectedSequenceLength { expected: u32, got: usize },
#[error("Invalid nucleotides found in sequence: {0}")]
InvalidNucleotideSequence(String),
#[error("Missing header in writer builder")]
MissingHeader,
#[error("Quality flag is set in header but trying to write without quality scores.")]
QualityFlagSet,
#[error("Paired flag is set in header but trying to write without record pair.")]
PairedFlagSet,
#[error("Quality flag not set in header but trying to write quality scores.")]
QualityFlagNotSet,
#[error("Paired flag not set in header but trying to write with record pair.")]
PairedFlagNotSet,
#[error("Header flag is set in header but trying to write without headers.")]
HeaderFlagSet,
#[error(
"Encountered a record with embedded size {0} but the maximum block size is {1}. Rerun with increased block size."
)]
RecordSizeExceedsMaximumBlockSize(usize, usize),
#[error(
"Incompatible block sizes encountered in BlockWriter Ingest. Found ({1}) Expected ({0})"
)]
IncompatibleBlockSizes(usize, usize),
#[error("Incompatible headers found in vbq::Writer::ingest. Found ({1:?}) Expected ({0:?})")]
IncompatibleHeaders(crate::vbq::FileHeader, crate::vbq::FileHeader),
#[error("SequencingRecordBuilder requires a primary sequence (s_seq)")]
MissingSequence,
}
#[derive(thiserror::Error, Debug)]
pub enum IndexError {
#[error("Invalid magic number: {0}")]
InvalidMagicNumber(u64),
#[error("Invalid reserved bytes in index header")]
InvalidReservedBytes,
}
#[derive(thiserror::Error, Debug)]
pub enum CbqError {
#[error(
"Record size ({record_size}) exceeds maximum block size ({max_block_size}) - Try increasing block size."
)]
ExceedsMaximumBlockSize {
max_block_size: usize,
record_size: usize,
},
#[error("Cannot ingest block of size {other_block_size} into block of size {self_block_size}")]
CannotIngestBlock {
self_block_size: usize,
other_block_size: usize,
},
#[error(
"Block(size: {block_size}) will be exceeded by record size {record_size}. Current size: {current_size}"
)]
BlockFull {
current_size: usize,
record_size: usize,
block_size: usize,
},
#[error("Invalid block header MAGIC found")]
InvalidBlockHeaderMagic,
#[error("Invalid file header MAGIC found")]
InvalidFileHeaderMagic,
#[error("Invalid index header MAGIC found")]
InvalidIndexHeaderMagic,
#[error("Invalid index footer MAGIC found")]
InvalidIndexFooterMagic,
#[error("Unable to cast bytes to Index - likely an alignment error")]
IndexCastingError,
#[error("SequenceRecordBuilder failed on build due to missing primary sequence (`s_seq`)")]
MissingSequenceOnSequencingRecord,
}
#[cfg(feature = "paraseq")]
#[derive(thiserror::Error, Debug)]
pub enum FastxEncodingError {
#[error("Empty FASTX file")]
EmptyFastxFile,
#[error("Builder not provided with any input")]
MissingInput,
}
#[derive(thiserror::Error, Debug)]
pub enum ExtensionError {
#[error("Unsupported extension in path: {0}")]
UnsupportedExtension(String),
}
pub trait IntoBinseqError {
fn into_binseq_error(self) -> Error;
}
impl<E> IntoBinseqError for E
where
E: StdError + Send + Sync + 'static,
{
fn into_binseq_error(self) -> Error {
Error::GenericError(Box::new(self))
}
}
mod testing {
#[allow(unused)]
use super::*;
use thiserror::Error;
#[allow(unused)]
#[derive(Error, Debug)]
pub enum MyError {
#[error("Custom error: {0}")]
CustomError(String),
}
#[test]
fn test_into_binseq_error() {
let my_error = MyError::CustomError(String::from("some error"));
let binseq_error = my_error.into_binseq_error();
assert!(matches!(binseq_error, Error::GenericError(_)));
}
#[test]
fn test_header_error_invalid_magic_number() {
let error = HeaderError::InvalidMagicNumber(0xDEADBEEF);
let error_str = format!("{}", error);
assert!(error_str.contains("0xdeadbeef") || error_str.contains("3735928559"));
}
#[test]
fn test_header_error_invalid_format_version() {
let error = HeaderError::InvalidFormatVersion(99);
let error_str = format!("{}", error);
assert!(error_str.contains("99"));
}
#[test]
fn test_header_error_invalid_bit_size() {
let error = HeaderError::InvalidBitSize(8);
let error_str = format!("{}", error);
assert!(error_str.contains("8"));
assert!(error_str.contains("[2,4]"));
}
#[test]
fn test_header_error_invalid_size() {
let error = HeaderError::InvalidSize(100, 200);
let error_str = format!("{}", error);
assert!(error_str.contains("100"));
assert!(error_str.contains("200"));
}
#[test]
fn test_read_error_out_of_range() {
let error = ReadError::OutOfRange {
requested_index: 150,
max_index: 100,
};
let error_str = format!("{}", error);
assert!(error_str.contains("150"));
assert!(error_str.contains("100"));
}
#[test]
fn test_read_error_file_truncation() {
let error = ReadError::FileTruncation(12345);
let error_str = format!("{}", error);
assert!(error_str.contains("12345"));
}
#[test]
fn test_read_error_partial_record() {
let error = ReadError::PartialRecord(42);
let error_str = format!("{}", error);
assert!(error_str.contains("42"));
}
#[test]
fn test_read_error_invalid_block_magic_number() {
let error = ReadError::InvalidBlockMagicNumber(0xBADC0DE, 1000);
let error_str = format!("{}", error);
assert!(error_str.contains("1000"));
}
#[test]
fn test_write_error_configuration_mismatch() {
let error = WriteError::ConfigurationMismatch {
attribute: "paired",
expected: true,
actual: false,
};
let error_str = format!("{}", error);
assert!(error_str.contains("paired"));
assert!(error_str.contains("true"));
assert!(error_str.contains("false"));
}
#[test]
fn test_write_error_unexpected_sequence_length() {
let error = WriteError::UnexpectedSequenceLength {
expected: 100,
got: 150,
};
let error_str = format!("{}", error);
assert!(error_str.contains("100"));
assert!(error_str.contains("150"));
}
#[test]
fn test_write_error_invalid_nucleotide_sequence() {
let error = WriteError::InvalidNucleotideSequence("ACGTNX".to_string());
let error_str = format!("{}", error);
assert!(error_str.contains("ACGTNX"));
}
#[test]
fn test_write_error_record_size_exceeds_max() {
let error = WriteError::RecordSizeExceedsMaximumBlockSize(2000, 1024);
let error_str = format!("{}", error);
assert!(error_str.contains("2000"));
assert!(error_str.contains("1024"));
}
#[test]
fn test_write_error_missing_sequence_length() {
let error = WriteError::MissingSequenceLength {
exp_primary: true,
exp_extended: false,
obs_primary: false,
obs_extended: false,
};
let error_str = format!("{}", error);
assert!(error_str.contains("Missing required sequence length"));
}
#[test]
fn test_cbq_error_exceeds_maximum_block_size() {
let error = CbqError::ExceedsMaximumBlockSize {
max_block_size: 1024,
record_size: 2048,
};
let error_str = format!("{}", error);
assert!(error_str.contains("1024"));
assert!(error_str.contains("2048"));
}
#[test]
fn test_cbq_error_block_full() {
let error = CbqError::BlockFull {
current_size: 900,
record_size: 200,
block_size: 1024,
};
let error_str = format!("{}", error);
assert!(error_str.contains("900"));
assert!(error_str.contains("200"));
assert!(error_str.contains("1024"));
}
#[test]
fn test_cbq_error_cannot_ingest_block() {
let error = CbqError::CannotIngestBlock {
self_block_size: 1024,
other_block_size: 2048,
};
let error_str = format!("{}", error);
assert!(error_str.contains("1024"));
assert!(error_str.contains("2048"));
}
#[test]
fn test_builder_error_missing_slen() {
let error = BuilderError::MissingSlen;
let error_str = format!("{}", error);
assert!(error_str.contains("Missing sequence length"));
}
#[test]
fn test_extension_error_unsupported() {
let error = ExtensionError::UnsupportedExtension("test.xyz".to_string());
let error_str = format!("{}", error);
assert!(error_str.contains("test.xyz"));
}
#[test]
fn test_error_from_header_error() {
let header_error = HeaderError::InvalidMagicNumber(0x1234);
let error: Error = header_error.into();
assert!(matches!(error, Error::HeaderError(_)));
}
#[test]
fn test_error_from_write_error() {
let write_error = WriteError::MissingHeader;
let error: Error = write_error.into();
assert!(matches!(error, Error::WriteError(_)));
}
#[test]
fn test_error_from_read_error() {
let read_error = ReadError::EndOfStream;
let error: Error = read_error.into();
assert!(matches!(error, Error::ReadError(_)));
}
#[test]
fn test_error_from_index_error() {
let index_error = IndexError::InvalidMagicNumber(0x5678);
let error: Error = index_error.into();
assert!(matches!(error, Error::IndexError(_)));
}
#[test]
fn test_error_from_cbq_error() {
let cbq_error = CbqError::InvalidBlockHeaderMagic;
let error: Error = cbq_error.into();
assert!(matches!(error, Error::CbqError(_)));
}
#[test]
fn test_error_from_builder_error() {
let builder_error = BuilderError::MissingSlen;
let error: Error = builder_error.into();
assert!(matches!(error, Error::BuilderError(_)));
}
#[test]
fn test_error_debug_output() {
let error = Error::WriteError(WriteError::MissingHeader);
let debug_str = format!("{:?}", error);
assert!(debug_str.contains("WriteError"));
}
#[cfg(feature = "paraseq")]
#[test]
fn test_fastx_error_empty_file() {
use super::FastxEncodingError;
let error = FastxEncodingError::EmptyFastxFile;
let error_str = format!("{}", error);
assert!(error_str.contains("Empty FASTX file"));
}
#[cfg(feature = "paraseq")]
#[test]
fn test_fastx_error_missing_input() {
use super::FastxEncodingError;
let error = FastxEncodingError::MissingInput;
let error_str = format!("{}", error);
assert!(error_str.contains("not provided with any input"));
}
}