harden obiskio error handling with explicit variants and bounds checking
Extend `SKError` with `BadMagic`, `Truncated`, and `InvalidData` variants to replace `expect()` calls and unsafe slice indexing. Implement proper error chaining via `source()` and update `Display` formatting. Improve magic byte validation and serde error mapping for clearer debugging. Documents a broader roadmap for further crate hardening, including LRU eviction, concurrency, and benchmarking.
This commit is contained in:
@@ -5,6 +5,12 @@ use std::io;
|
||||
pub enum SKError {
|
||||
Io(io::Error),
|
||||
Compression(niffler::Error),
|
||||
/// Binary file has unrecognized magic bytes.
|
||||
BadMagic { expected: &'static str, got: [u8; 4] },
|
||||
/// File is too short to contain a required field.
|
||||
Truncated { context: &'static str },
|
||||
/// A field value is outside its valid range or cannot be parsed.
|
||||
InvalidData { context: &'static str, detail: String },
|
||||
}
|
||||
|
||||
pub type SKResult<T> = Result<T, SKError>;
|
||||
@@ -14,6 +20,13 @@ impl fmt::Display for SKError {
|
||||
match self {
|
||||
SKError::Io(e) => write!(f, "I/O error: {e}"),
|
||||
SKError::Compression(e) => write!(f, "compression error: {e}"),
|
||||
SKError::BadMagic { expected, got } => {
|
||||
write!(f, "bad magic in {expected}: expected {:?}, got {:?}", expected.as_bytes(), got)
|
||||
}
|
||||
SKError::Truncated { context } => write!(f, "truncated file: {context}"),
|
||||
SKError::InvalidData { context, detail } => {
|
||||
write!(f, "invalid data in {context}: {detail}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +36,7 @@ impl std::error::Error for SKError {
|
||||
match self {
|
||||
SKError::Io(e) => Some(e),
|
||||
SKError::Compression(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::error::SKResult;
|
||||
use crate::error::{SKError, SKResult};
|
||||
|
||||
/// Statistics sidecar written alongside each SuperKmer file on close.
|
||||
///
|
||||
@@ -31,7 +31,10 @@ impl SKFileMeta {
|
||||
pub fn read(sk_path: &Path) -> SKResult<Option<Self>> {
|
||||
let path = Self::sidecar_path(sk_path);
|
||||
match std::fs::File::open(&path) {
|
||||
Ok(f) => Ok(Some(serde_json::from_reader(f).map_err(std::io::Error::other)?)),
|
||||
Ok(f) => Ok(Some(serde_json::from_reader(f).map_err(|e| SKError::InvalidData {
|
||||
context: "SKFileMeta sidecar",
|
||||
detail: e.to_string(),
|
||||
})?)),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
@@ -40,7 +43,10 @@ impl SKFileMeta {
|
||||
pub(crate) fn write(&self, sk_path: &Path) -> SKResult<()> {
|
||||
let path = Self::sidecar_path(sk_path);
|
||||
let f = std::fs::File::create(&path)?;
|
||||
serde_json::to_writer_pretty(f, self).map_err(std::io::Error::other)?;
|
||||
serde_json::to_writer_pretty(f, self).map_err(|e| SKError::InvalidData {
|
||||
context: "SKFileMeta sidecar",
|
||||
detail: e.to_string(),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,23 +218,31 @@ fn read_idx(path: &Path) -> SKResult<(Vec<u8>, Vec<u32>)> {
|
||||
let data = std::fs::read(path).map_err(SKError::Io)?;
|
||||
let mut pos = 0;
|
||||
|
||||
if &data[pos..pos + 4] != &MAGIC {
|
||||
return Err(SKError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"unitig index: bad magic",
|
||||
)));
|
||||
let magic_bytes = data.get(pos..pos + 4)
|
||||
.ok_or(SKError::Truncated { context: "unitig index: magic" })?;
|
||||
if magic_bytes != &MAGIC {
|
||||
return Err(SKError::BadMagic {
|
||||
expected: "UIDX",
|
||||
got: magic_bytes.try_into().unwrap(),
|
||||
});
|
||||
}
|
||||
pos += 4;
|
||||
|
||||
let n = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
|
||||
let n_bytes = data.get(pos..pos + 4)
|
||||
.ok_or(SKError::Truncated { context: "unitig index: n_unitigs" })?;
|
||||
let n = u32::from_le_bytes(n_bytes.try_into().unwrap()) as usize;
|
||||
pos += 4;
|
||||
|
||||
let seqls = data[pos..pos + n].to_vec();
|
||||
let seqls = data.get(pos..pos + n)
|
||||
.ok_or(SKError::Truncated { context: "unitig index: seqls" })?
|
||||
.to_vec();
|
||||
pos += n;
|
||||
|
||||
let mut packed_offsets = Vec::with_capacity(n + 1);
|
||||
for _ in 0..=n {
|
||||
packed_offsets.push(u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()));
|
||||
let off_bytes = data.get(pos..pos + 4)
|
||||
.ok_or(SKError::Truncated { context: "unitig index: packed_offsets" })?;
|
||||
packed_offsets.push(u32::from_le_bytes(off_bytes.try_into().unwrap()));
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user