mirror of
https://github.com/dnlbauer/xdrfile.git
synced 2026-09-10 22:25:30 +00:00
Merge pull request #3 from Yoshanuikabundi/rusty-error-codes
Rusty error codes
This commit is contained in:
312
src/errors.rs
Normal file
312
src/errors.rs
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
use crate::c_abi;
|
||||||
|
use crate::FileMode;
|
||||||
|
use crate::Frame;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// Error type for the xdrfile library
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
/// An error code from the C API
|
||||||
|
CApiError {
|
||||||
|
code: ErrorCode,
|
||||||
|
task: ErrorTask,
|
||||||
|
},
|
||||||
|
/// Passed in a frame of the wrong size
|
||||||
|
WrongSizeFrame {
|
||||||
|
expected: usize,
|
||||||
|
found: usize,
|
||||||
|
},
|
||||||
|
/// C API failed to open a file (No return code provided)
|
||||||
|
CouldNotOpen {
|
||||||
|
path: PathBuf,
|
||||||
|
mode: FileMode,
|
||||||
|
},
|
||||||
|
/// A path could not be converted to &OsStr
|
||||||
|
InvalidOsStr,
|
||||||
|
/// A path could not be converted to &CStr because it had a null byte
|
||||||
|
NullInStr(std::ffi::NulError),
|
||||||
|
CouldNotCheckNAtoms(Box<Error>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
/// Get the error code returned by the C API, if any
|
||||||
|
pub fn code(&self) -> Option<ErrorCode> {
|
||||||
|
if let Error::CApiError { code, .. } = self {
|
||||||
|
Some(*code)
|
||||||
|
} else if let Some(e) = self.source() {
|
||||||
|
e.downcast_ref::<Self>().and_then(Self::code)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the task being attempted when the C API returned an error, if any
|
||||||
|
pub fn task(&self) -> Option<ErrorTask> {
|
||||||
|
if let Error::CApiError { task, .. } = self {
|
||||||
|
Some(*task)
|
||||||
|
} else if let Some(e) = self.source() {
|
||||||
|
e.downcast_ref::<Self>().and_then(Self::task)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if the error is an end of file error, false otherwise
|
||||||
|
pub fn is_eof(&self) -> bool {
|
||||||
|
self.code().map_or(false, |e| e.is_eof())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
use Error::*;
|
||||||
|
match &self {
|
||||||
|
NullInStr(err) => Some(err),
|
||||||
|
CouldNotCheckNAtoms(err) => Some(err.as_ref()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(ErrorCode, ErrorTask)> for Error {
|
||||||
|
fn from(value: (ErrorCode, ErrorTask)) -> Self {
|
||||||
|
let (code, task) = value;
|
||||||
|
Self::CApiError { code, task }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::ffi::NulError> for Error {
|
||||||
|
fn from(err: std::ffi::NulError) -> Self {
|
||||||
|
Self::NullInStr(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&Path, FileMode)> for Error {
|
||||||
|
fn from(value: (&Path, FileMode)) -> Self {
|
||||||
|
let (path, mode) = value;
|
||||||
|
Error::CouldNotOpen {
|
||||||
|
path: path.to_owned(),
|
||||||
|
mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&Frame, usize)> for Error {
|
||||||
|
fn from(value: (&Frame, usize)) -> Self {
|
||||||
|
let (frame, num_atoms) = value;
|
||||||
|
Error::WrongSizeFrame {
|
||||||
|
expected: num_atoms,
|
||||||
|
found: frame.coords.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
use Error::*;
|
||||||
|
match &self {
|
||||||
|
CApiError { code, task } => write!(
|
||||||
|
f,
|
||||||
|
"Error while {task}: C API returned error code {code}",
|
||||||
|
task = task,
|
||||||
|
code = code
|
||||||
|
),
|
||||||
|
WrongSizeFrame { expected, found } => write!(
|
||||||
|
f,
|
||||||
|
"Expected frame of size {:?}, found {:?}",
|
||||||
|
expected, found
|
||||||
|
),
|
||||||
|
CouldNotOpen { path, mode } => {
|
||||||
|
write!(f, "Could not open file at {:?} in mode {:?}", path, mode)
|
||||||
|
}
|
||||||
|
InvalidOsStr => write!(f, "Paths must be valid unicode on this platform"),
|
||||||
|
NullInStr(_err) => write!(f, "Paths cannot include null bytes"),
|
||||||
|
CouldNotCheckNAtoms(_err) => write!(
|
||||||
|
f,
|
||||||
|
"Failed to read number of atoms in trajectory file"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The task being attempted when the C API returns an error
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum ErrorTask {
|
||||||
|
/// The number of atoms was being read from a file
|
||||||
|
ReadNumAtoms,
|
||||||
|
/// A frame was being read from a file
|
||||||
|
Read,
|
||||||
|
/// A frame was being written to a file
|
||||||
|
Write,
|
||||||
|
/// An file was being flushed to disk
|
||||||
|
Flush,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ErrorTask {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
use ErrorTask::*;
|
||||||
|
match &self {
|
||||||
|
ReadNumAtoms => write!(f, "reading atom number from trajectory"),
|
||||||
|
Read => write!(f, "reading trajectory"),
|
||||||
|
Write => write!(f, "writing trajectory"),
|
||||||
|
Flush => write!(f, "flushing trajectory"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error codes returned from the C API
|
||||||
|
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||||
|
pub enum ErrorCode {
|
||||||
|
/// No error, C API returned successfully
|
||||||
|
ExdrOk,
|
||||||
|
/// TRR file had corrupt Header
|
||||||
|
ExdrHeader,
|
||||||
|
/// Failed to read a string where expected
|
||||||
|
ExdrString,
|
||||||
|
/// Failed to read a double where expected
|
||||||
|
ExdrDouble,
|
||||||
|
/// Failed to read an int where expected
|
||||||
|
ExdrInt,
|
||||||
|
/// Failed to read a float where expected
|
||||||
|
ExdrFloat,
|
||||||
|
/// Failed to read a uint where expected
|
||||||
|
ExdrUint,
|
||||||
|
/// Failed to read compressed XTC coordinates
|
||||||
|
Exdr3dx,
|
||||||
|
/// Error encountered while closing file
|
||||||
|
ExdrClose,
|
||||||
|
/// File had incorrect "magic number" (not an XTC file)
|
||||||
|
ExdrMagic,
|
||||||
|
/// Failed to allocate memory for TRR file
|
||||||
|
ExdrNoMem,
|
||||||
|
/// End of file was reached while trying to read
|
||||||
|
ExdrEndOfFile,
|
||||||
|
/// File was not found when trying to open
|
||||||
|
ExdrFileNotFound,
|
||||||
|
/// Failed to seek within file
|
||||||
|
ExdrNr,
|
||||||
|
/// Something unexpected happened
|
||||||
|
UnmatchedCode(c_abi::xdrfile::BindgenTy1),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorCode {
|
||||||
|
/// True if the error is an end of file error, false otherwise
|
||||||
|
pub fn is_eof(&self) -> bool {
|
||||||
|
matches!(self, Self::ExdrEndOfFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<c_abi::xdrfile::BindgenTy1> for ErrorCode {
|
||||||
|
fn from(code: c_abi::xdrfile::BindgenTy1) -> Self {
|
||||||
|
match code {
|
||||||
|
c_abi::xdrfile::exdrOK => Self::ExdrOk,
|
||||||
|
c_abi::xdrfile::exdrHEADER => Self::ExdrHeader,
|
||||||
|
c_abi::xdrfile::exdrSTRING => Self::ExdrString,
|
||||||
|
c_abi::xdrfile::exdrDOUBLE => Self::ExdrDouble,
|
||||||
|
c_abi::xdrfile::exdrINT => Self::ExdrInt,
|
||||||
|
c_abi::xdrfile::exdrFLOAT => Self::ExdrFloat,
|
||||||
|
c_abi::xdrfile::exdrUINT => Self::ExdrUint,
|
||||||
|
c_abi::xdrfile::exdr3DX => Self::Exdr3dx,
|
||||||
|
c_abi::xdrfile::exdrCLOSE => Self::ExdrClose,
|
||||||
|
c_abi::xdrfile::exdrMAGIC => Self::ExdrMagic,
|
||||||
|
c_abi::xdrfile::exdrNOMEM => Self::ExdrNoMem,
|
||||||
|
c_abi::xdrfile::exdrENDOFFILE => Self::ExdrEndOfFile,
|
||||||
|
c_abi::xdrfile::exdrFILENOTFOUND => Self::ExdrFileNotFound,
|
||||||
|
c_abi::xdrfile::exdrNR => Self::ExdrNr,
|
||||||
|
code => Self::UnmatchedCode(code),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ErrorCode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if let Self::UnmatchedCode(i) = self {
|
||||||
|
write!(f, "{}", i)
|
||||||
|
} else {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Result` type for errors in the `xdrfile` crate
|
||||||
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::ffi::NulError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_eof() {
|
||||||
|
use Error::CApiError;
|
||||||
|
let error = CApiError {
|
||||||
|
code: c_abi::xdrfile::exdrENDOFFILE.into(),
|
||||||
|
task: ErrorTask::Read,
|
||||||
|
};
|
||||||
|
assert!(error.is_eof());
|
||||||
|
|
||||||
|
let error = CApiError {
|
||||||
|
code: ErrorCode::ExdrEndOfFile,
|
||||||
|
task: ErrorTask::Read,
|
||||||
|
};
|
||||||
|
assert!(error.is_eof());
|
||||||
|
|
||||||
|
let error = CApiError {
|
||||||
|
code: (c_abi::xdrfile::exdrENDOFFILE + 1).into(),
|
||||||
|
task: ErrorTask::Read,
|
||||||
|
};
|
||||||
|
assert!(!error.is_eof());
|
||||||
|
|
||||||
|
let error = CApiError {
|
||||||
|
code: 0.into(),
|
||||||
|
task: ErrorTask::Read,
|
||||||
|
};
|
||||||
|
assert!(!error.is_eof());
|
||||||
|
|
||||||
|
let error = CApiError {
|
||||||
|
code: 255.into(),
|
||||||
|
task: ErrorTask::Read,
|
||||||
|
};
|
||||||
|
assert!(!error.is_eof());
|
||||||
|
|
||||||
|
let error = Error::CouldNotOpen {
|
||||||
|
path: PathBuf::from("not/a/file"),
|
||||||
|
mode: FileMode::Read,
|
||||||
|
};
|
||||||
|
assert!(!error.is_eof());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_correct_type() {
|
||||||
|
let nul_err: NulError = CString::new(b"foo\0".to_vec()).unwrap_err();
|
||||||
|
let nul_err2: NulError = CString::new(b"foo\0".to_vec()).unwrap_err();
|
||||||
|
let err = Error::from(nul_err);
|
||||||
|
let expected = Error::NullInStr(nul_err2);
|
||||||
|
assert_eq!(expected, err);
|
||||||
|
|
||||||
|
let code = 3.into();
|
||||||
|
let task = ErrorTask::Read;
|
||||||
|
let expected = Error::CApiError { code, task };
|
||||||
|
let err = Error::from((code, task));
|
||||||
|
assert_eq!(expected, err);
|
||||||
|
|
||||||
|
let path = Path::new(".");
|
||||||
|
let mode = FileMode::Read;
|
||||||
|
let expected = Error::CouldNotOpen {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
mode: mode.to_owned(),
|
||||||
|
};
|
||||||
|
let err = Error::from((path, mode));
|
||||||
|
assert_eq!(expected, err);
|
||||||
|
|
||||||
|
let frame = Frame::with_capacity(0);
|
||||||
|
let expected = Error::WrongSizeFrame {
|
||||||
|
expected: 10,
|
||||||
|
found: 0,
|
||||||
|
};
|
||||||
|
let err = Error::from((&frame, 10));
|
||||||
|
assert_eq!(expected, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
fn into_iter_inner<T: Trajectory>(mut traj: T) -> TrajectoryIterator<T> {
|
||||||
|
let num_atoms = traj.get_num_atoms();
|
||||||
|
let frame = match &num_atoms {
|
||||||
|
Ok(num_atoms) => Frame::with_capacity(*num_atoms),
|
||||||
|
Err(_) => Frame::new(),
|
||||||
|
};
|
||||||
|
TrajectoryIterator {
|
||||||
|
trajectory: traj,
|
||||||
|
item: Rc::new(frame),
|
||||||
|
has_error: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoIterator for XTCTrajectory {
|
impl IntoIterator for XTCTrajectory {
|
||||||
type Item = Result<Rc<Frame>>;
|
type Item = Result<Rc<Frame>>;
|
||||||
type IntoIter = TrajectoryIterator<XTCTrajectory>;
|
type IntoIter = TrajectoryIterator<XTCTrajectory>;
|
||||||
|
|
||||||
fn into_iter(mut self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
let frame = match self.get_num_atoms() {
|
into_iter_inner(self)
|
||||||
Ok(num_atoms) => Frame::with_capacity(num_atoms),
|
|
||||||
Err(_) => Frame::new(),
|
|
||||||
};
|
|
||||||
TrajectoryIterator {
|
|
||||||
trajectory: self,
|
|
||||||
item: Rc::new(frame),
|
|
||||||
has_error: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,16 +27,8 @@ impl IntoIterator for TRRTrajectory {
|
|||||||
type Item = Result<Rc<Frame>>;
|
type Item = Result<Rc<Frame>>;
|
||||||
type IntoIter = TrajectoryIterator<TRRTrajectory>;
|
type IntoIter = TrajectoryIterator<TRRTrajectory>;
|
||||||
|
|
||||||
fn into_iter(mut self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
let frame = match self.get_num_atoms() {
|
into_iter_inner(self)
|
||||||
Ok(num_atoms) => Frame::with_capacity(num_atoms),
|
|
||||||
Err(_) => Frame::new(),
|
|
||||||
};
|
|
||||||
TrajectoryIterator {
|
|
||||||
trajectory: self,
|
|
||||||
item: Rc::new(frame),
|
|
||||||
has_error: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +43,31 @@ pub struct TrajectoryIterator<T> {
|
|||||||
has_error: bool,
|
has_error: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Trajectory> TrajectoryIterator<T> {
|
||||||
|
/// Inner function for `next()` to seperate error handling from iteration logic
|
||||||
|
fn next_inner(&mut self) -> <Self as Iterator>::Item {
|
||||||
|
// If we couldn't read the number of frames when we called into_iter, return that error now
|
||||||
|
// It's OK to do this every frame because the result is cached by Trajectory
|
||||||
|
let num_atoms = match &self.trajectory.get_num_atoms() {
|
||||||
|
&Ok(n) => n,
|
||||||
|
Err(e) => Err(Error::CouldNotCheckNAtoms(Box::new(e.clone())))?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reuse old frame
|
||||||
|
let item: &mut Frame = match Rc::get_mut(&mut self.item) {
|
||||||
|
Some(item) => item,
|
||||||
|
None => {
|
||||||
|
// caller kept frame. Create new one
|
||||||
|
self.item = Rc::new(Frame::with_capacity(num_atoms));
|
||||||
|
Rc::get_mut(&mut self.item).expect("Could not get mutable access to new Rc")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.trajectory.read(item)?;
|
||||||
|
Ok(Rc::clone(&self.item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Iterator for TrajectoryIterator<T>
|
impl<T> Iterator for TrajectoryIterator<T>
|
||||||
where
|
where
|
||||||
T: Trajectory,
|
T: Trajectory,
|
||||||
@@ -53,21 +75,12 @@ where
|
|||||||
type Item = Result<Rc<Frame>>;
|
type Item = Result<Rc<Frame>>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
// Reuse old frame
|
|
||||||
if self.has_error {
|
if self.has_error {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let item: &mut Frame = match Rc::get_mut(&mut self.item) {
|
match self.next_inner() {
|
||||||
Some(item) => item,
|
Ok(item) => Some(Ok(item)),
|
||||||
None => {
|
|
||||||
// caller kept frame. Create new one
|
|
||||||
self.item = Rc::new(Frame::with_capacity(self.item.num_atoms));
|
|
||||||
Rc::get_mut(&mut self.item).expect("Could not get mutable access to new Rc")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match self.trajectory.read(item) {
|
|
||||||
Ok(()) => Some(Ok(Rc::clone(&self.item))),
|
|
||||||
Err(e) if e.is_eof() => None,
|
Err(e) if e.is_eof() => None,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.has_error = true;
|
self.has_error = true;
|
||||||
|
|||||||
308
src/lib.rs
308
src/lib.rs
@@ -62,8 +62,10 @@ extern crate assert_approx_eq;
|
|||||||
extern crate lazy_init;
|
extern crate lazy_init;
|
||||||
|
|
||||||
pub mod c_abi;
|
pub mod c_abi;
|
||||||
|
mod errors;
|
||||||
mod frame;
|
mod frame;
|
||||||
mod iterator;
|
mod iterator;
|
||||||
|
pub use errors::*;
|
||||||
pub use frame::Frame;
|
pub use frame::Frame;
|
||||||
pub use iterator::*;
|
pub use iterator::*;
|
||||||
|
|
||||||
@@ -78,75 +80,6 @@ use std::cell::Cell;
|
|||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum Error {
|
|
||||||
CouldNotOpenFile(std::path::PathBuf, FileMode),
|
|
||||||
CouldNotReadAtomNumber(u32),
|
|
||||||
CouldNotRead(u32),
|
|
||||||
CouldNotWrite(u32),
|
|
||||||
CouldNotFlush(u32),
|
|
||||||
PathInvalidCstring,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
use Error::*;
|
|
||||||
match self {
|
|
||||||
CouldNotOpenFile(path, mode) => write!(
|
|
||||||
f,
|
|
||||||
"Failed to open file at {path:?} with mode {mode:?}",
|
|
||||||
path = path,
|
|
||||||
mode = mode
|
|
||||||
),
|
|
||||||
CouldNotReadAtomNumber(code) => write!(
|
|
||||||
f,
|
|
||||||
"Failed to read atom number from trajectory: C API returned error code {}",
|
|
||||||
code
|
|
||||||
),
|
|
||||||
CouldNotRead(code) => write!(
|
|
||||||
f,
|
|
||||||
"Failed to read trajectory: C API returned error code {}",
|
|
||||||
code
|
|
||||||
),
|
|
||||||
CouldNotWrite(code) => write!(
|
|
||||||
f,
|
|
||||||
"Failed to write trajectory: C API returned error code {}",
|
|
||||||
code
|
|
||||||
),
|
|
||||||
CouldNotFlush(code) => write!(
|
|
||||||
f,
|
|
||||||
"Failed to flush trajectory: C API returned error code {}",
|
|
||||||
code
|
|
||||||
),
|
|
||||||
PathInvalidCstring => write!(
|
|
||||||
f,
|
|
||||||
"Path cannot be converted to a C string because it has a 0 byte"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error {
|
|
||||||
pub fn is_eof(&self) -> bool {
|
|
||||||
use Error::*;
|
|
||||||
match self {
|
|
||||||
&CouldNotReadAtomNumber(code)
|
|
||||||
| &CouldNotRead(code)
|
|
||||||
| &CouldNotWrite(code)
|
|
||||||
| &CouldNotFlush(code)
|
|
||||||
if code == xdrfile::exdrENDOFFILE =>
|
|
||||||
{
|
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum FileMode {
|
pub enum FileMode {
|
||||||
Write,
|
Write,
|
||||||
@@ -168,8 +101,22 @@ impl FileMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn path_to_cstring(path: impl AsRef<Path>) -> Result<CString> {
|
fn path_to_cstring(path: impl AsRef<Path>) -> Result<CString> {
|
||||||
let s = path.as_ref().to_str().ok_or(Error::PathInvalidCstring)?;
|
let s = path.as_ref().to_str().ok_or(Error::InvalidOsStr)?;
|
||||||
CString::new(s).map_err(|_| Error::PathInvalidCstring)
|
Ok(CString::new(s)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert an error code from a C call to an Error
|
||||||
|
///
|
||||||
|
/// `code` should be an integer return code returned from the C API.
|
||||||
|
/// If `code` indicates the function returned successfully, Nothing is returned;
|
||||||
|
/// otherwise, the code is converted into the appropriate `Error`.
|
||||||
|
pub fn check_code(code: impl Into<ErrorCode>, task: ErrorTask) -> Option<Error> {
|
||||||
|
let code: ErrorCode = code.into();
|
||||||
|
if let ErrorCode::ExdrOk = code {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Error::from((code, task)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A safe wrapper around the c implementation of an XDRFile
|
/// A safe wrapper around the c implementation of an XDRFile
|
||||||
@@ -202,7 +149,7 @@ impl XDRFile {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Something went wrong. But the C api does not tell us what
|
// Something went wrong. But the C api does not tell us what
|
||||||
Err(Error::CouldNotOpenFile(path.into(), filemode))
|
Err((path, filemode))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,24 +214,33 @@ impl XTCTrajectory {
|
|||||||
|
|
||||||
impl Trajectory for XTCTrajectory {
|
impl Trajectory for XTCTrajectory {
|
||||||
fn read(&mut self, frame: &mut Frame) -> Result<()> {
|
fn read(&mut self, frame: &mut Frame) -> Result<()> {
|
||||||
|
let mut step: i32 = 0;
|
||||||
|
|
||||||
|
let num_atoms =
|
||||||
|
self.get_num_atoms()
|
||||||
|
.map_err(|e| Error::CouldNotCheckNAtoms(Box::new(e)))? as usize;
|
||||||
|
if num_atoms != frame.coords.len() {
|
||||||
|
Err((&*frame, num_atoms))?;
|
||||||
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// C lib requires an i32 to be passed, but step is exposed it as u32
|
// C lib requires an i32 to be passed, but step is exposed it as u32
|
||||||
// (A step cannot be negative, can it?). So we need to create a step
|
// (A step cannot be negative, can it?). So we need to create a step
|
||||||
// variable to pass to read_xtc and cast it afterwards to u32
|
// variable to pass to read_xtc and cast it afterwards to u32
|
||||||
let mut step: i32 = 0;
|
|
||||||
let code = xdrfile_xtc::read_xtc(
|
let code = xdrfile_xtc::read_xtc(
|
||||||
self.handle.xdrfile,
|
self.handle.xdrfile,
|
||||||
frame.num_atoms as i32,
|
num_atoms as i32,
|
||||||
&mut step,
|
&mut step,
|
||||||
&mut frame.time,
|
&mut frame.time,
|
||||||
&mut frame.box_vector,
|
&mut frame.box_vector,
|
||||||
frame.coords.as_ptr() as *mut [f32; 3],
|
frame.coords.as_mut_ptr(),
|
||||||
&mut self.precision.get(),
|
&mut self.precision.get(),
|
||||||
) as u32;
|
) as u32;
|
||||||
frame.step = step as u32;
|
frame.step = step as u32;
|
||||||
match code {
|
if let Some(err) = check_code(code, ErrorTask::Read) {
|
||||||
xdrfile::exdrOK => Ok(()),
|
Err(err)
|
||||||
_ => Err(Error::CouldNotRead(code)),
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,9 +256,10 @@ impl Trajectory for XTCTrajectory {
|
|||||||
frame.coords[..].as_ptr() as *mut [f32; 3],
|
frame.coords[..].as_ptr() as *mut [f32; 3],
|
||||||
1000.0,
|
1000.0,
|
||||||
) as u32;
|
) as u32;
|
||||||
match code {
|
if let Some(err) = check_code(code, ErrorTask::Write) {
|
||||||
xdrfile::exdrOK => Ok(()),
|
Err(err)
|
||||||
_ => Err(Error::CouldNotWrite(code)),
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,9 +267,10 @@ impl Trajectory for XTCTrajectory {
|
|||||||
fn flush(&mut self) -> Result<()> {
|
fn flush(&mut self) -> Result<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let code = xdr_seek::xdr_flush(self.handle.xdrfile) as u32;
|
let code = xdr_seek::xdr_flush(self.handle.xdrfile) as u32;
|
||||||
match code {
|
if let Some(err) = check_code(code, ErrorTask::Read) {
|
||||||
xdrfile::exdrOK => Ok(()),
|
Err(err)
|
||||||
_ => Err(Error::CouldNotFlush(code)),
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,6 +279,7 @@ impl Trajectory for XTCTrajectory {
|
|||||||
self.num_atoms
|
self.num_atoms
|
||||||
.get_or_create(|| {
|
.get_or_create(|| {
|
||||||
let mut num_atoms: i32 = 0;
|
let mut num_atoms: i32 = 0;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let path = path_to_cstring(&self.handle.path)?;
|
let path = path_to_cstring(&self.handle.path)?;
|
||||||
let path_p = path.into_raw();
|
let path_p = path.into_raw();
|
||||||
@@ -328,9 +287,11 @@ impl Trajectory for XTCTrajectory {
|
|||||||
xdrfile_xtc::read_xtc_natoms(path_p, &mut num_atoms as *const i32) as u32;
|
xdrfile_xtc::read_xtc_natoms(path_p, &mut num_atoms as *const i32) as u32;
|
||||||
// Reconstitute the CString so it is deallocated correctly
|
// Reconstitute the CString so it is deallocated correctly
|
||||||
let _ = CString::from_raw(path_p);
|
let _ = CString::from_raw(path_p);
|
||||||
match code {
|
|
||||||
xdrfile::exdrOK => Ok(num_atoms as u32),
|
if let Some(err) = check_code(code, ErrorTask::ReadNumAtoms) {
|
||||||
_ => Err(Error::CouldNotReadAtomNumber(code)),
|
Err(err)
|
||||||
|
} else {
|
||||||
|
Ok(num_atoms as u32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -371,28 +332,37 @@ impl TRRTrajectory {
|
|||||||
|
|
||||||
impl Trajectory for TRRTrajectory {
|
impl Trajectory for TRRTrajectory {
|
||||||
fn read(&mut self, frame: &mut Frame) -> Result<()> {
|
fn read(&mut self, frame: &mut Frame) -> Result<()> {
|
||||||
|
let mut step: i32 = 0;
|
||||||
|
let mut lambda: f32 = 0.0;
|
||||||
|
|
||||||
|
let num_atoms =
|
||||||
|
self.get_num_atoms()
|
||||||
|
.map_err(|e| Error::CouldNotCheckNAtoms(Box::new(e)))? as usize;
|
||||||
|
if num_atoms != frame.coords.len() {
|
||||||
|
Err((&*frame, num_atoms))?;
|
||||||
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// C lib requires an i32 to be passed, but step is exposed it as u32
|
// C lib requires an i32 to be passed, but step is exposed it as u32
|
||||||
// (A step cannot be negative, can it?). So we need to create a step
|
// (A step cannot be negative, can it?). So we need to create a step
|
||||||
// variable to pass to read_trr and cast it afterwards to u32.
|
// variable to pass to read_trr and cast it afterwards to u32.
|
||||||
// Similar for lambda.
|
// Similar for lambda.
|
||||||
let mut step: i32 = 0;
|
|
||||||
let mut lambda: f32 = 0.0;
|
|
||||||
let code = xdrfile_trr::read_trr(
|
let code = xdrfile_trr::read_trr(
|
||||||
self.handle.xdrfile,
|
self.handle.xdrfile,
|
||||||
frame.num_atoms as i32,
|
num_atoms as i32,
|
||||||
&mut step,
|
&mut step,
|
||||||
&mut frame.time,
|
&mut frame.time,
|
||||||
&mut lambda,
|
&mut lambda,
|
||||||
&mut frame.box_vector,
|
&mut frame.box_vector,
|
||||||
frame.coords.as_ptr() as *mut [f32; 3],
|
frame.coords.as_mut_ptr(),
|
||||||
std::ptr::null_mut(),
|
std::ptr::null_mut(),
|
||||||
std::ptr::null_mut(),
|
std::ptr::null_mut(),
|
||||||
) as u32;
|
) as u32;
|
||||||
frame.step = step as u32;
|
frame.step = step as u32;
|
||||||
match code {
|
if let Some(err) = check_code(code, ErrorTask::Read) {
|
||||||
xdrfile::exdrOK => Ok(()),
|
Err(err)
|
||||||
_ => Err(Error::CouldNotRead(code)),
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -410,9 +380,10 @@ impl Trajectory for TRRTrajectory {
|
|||||||
std::ptr::null_mut(),
|
std::ptr::null_mut(),
|
||||||
std::ptr::null_mut(),
|
std::ptr::null_mut(),
|
||||||
) as u32;
|
) as u32;
|
||||||
match code {
|
if let Some(err) = check_code(code, ErrorTask::Write) {
|
||||||
xdrfile::exdrOK => Ok(()),
|
Err(err)
|
||||||
_ => Err(Error::CouldNotWrite(code)),
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,9 +391,10 @@ impl Trajectory for TRRTrajectory {
|
|||||||
fn flush(&mut self) -> Result<()> {
|
fn flush(&mut self) -> Result<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let code = xdr_seek::xdr_flush(self.handle.xdrfile) as u32;
|
let code = xdr_seek::xdr_flush(self.handle.xdrfile) as u32;
|
||||||
match code {
|
if let Some(err) = check_code(code, ErrorTask::Flush) {
|
||||||
xdrfile::exdrOK => Ok(()),
|
Err(err)
|
||||||
_ => Err(Error::CouldNotFlush(code)),
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,9 +410,11 @@ impl Trajectory for TRRTrajectory {
|
|||||||
xdrfile_trr::read_trr_natoms(path_p, &mut num_atoms as *const i32) as u32;
|
xdrfile_trr::read_trr_natoms(path_p, &mut num_atoms as *const i32) as u32;
|
||||||
// Reconstitute the CString so it is deallocated correctly
|
// Reconstitute the CString so it is deallocated correctly
|
||||||
let _ = CString::from_raw(path_p);
|
let _ = CString::from_raw(path_p);
|
||||||
match code {
|
|
||||||
xdrfile::exdrOK => Ok(num_atoms as u32),
|
if let Some(err) = check_code(code, ErrorTask::ReadNumAtoms) {
|
||||||
_ => Err(Error::CouldNotReadAtomNumber(code)),
|
Err(err)
|
||||||
|
} else {
|
||||||
|
Ok(num_atoms as u32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -535,28 +509,85 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_path_to_cstring() -> Result<(), Box<dyn std::error::Error>> {
|
pub fn test_manual_loop() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let result_invalid = path_to_cstring("invalid/\0path");
|
let mut xtc_frames = Vec::new();
|
||||||
|
let mut xtc_traj = XTCTrajectory::open_read("tests/1l2y.xtc")?;
|
||||||
|
let mut frame = Frame::with_capacity(xtc_traj.get_num_atoms()?);
|
||||||
|
|
||||||
assert_eq!(result_invalid, Err(Error::PathInvalidCstring));
|
while let Ok(()) = xtc_traj.read(&mut frame) {
|
||||||
|
xtc_frames.push(frame.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut trr_frames = Vec::new();
|
||||||
|
let mut trr_traj = TRRTrajectory::open_read("tests/1l2y.trr")?;
|
||||||
|
|
||||||
|
while let Ok(()) = trr_traj.read(&mut frame) {
|
||||||
|
trr_frames.push(frame.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (xtc, trr) in xtc_frames.into_iter().zip(trr_frames) {
|
||||||
|
assert_eq!(xtc.num_atoms, trr.num_atoms);
|
||||||
|
assert_eq!(xtc.step, trr.step);
|
||||||
|
assert_eq!(xtc.time, trr.time);
|
||||||
|
assert_eq!(xtc.box_vector, trr.box_vector);
|
||||||
|
for (xtc_xyz, trr_xyz) in xtc.coords.into_iter().zip(trr.coords) {
|
||||||
|
assert!(xtc_xyz[0] - trr_xyz[0] <= 1e-5);
|
||||||
|
assert!(xtc_xyz[1] - trr_xyz[1] <= 1e-5);
|
||||||
|
assert!(xtc_xyz[2] - trr_xyz[2] <= 1e-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_wrong_size_frame() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut xtc_traj = XTCTrajectory::open_read("tests/1l2y.xtc")?;
|
||||||
|
let mut frame = Frame::new();
|
||||||
|
|
||||||
|
let result = xtc_traj.read(&mut frame);
|
||||||
|
if let Err(e) = result {
|
||||||
|
assert!(matches!(e, Error::WrongSizeFrame { .. }));
|
||||||
|
} else {
|
||||||
|
panic!("A read with an incorrectly sized frame should not succeed")
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_to_cstring() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let result_invalid = path_to_cstring(PathBuf::from("invalid/\0path"));
|
||||||
|
|
||||||
|
if let Err(err) = result_invalid {
|
||||||
|
match err {
|
||||||
|
Error::NullInStr(_) => (),
|
||||||
|
Error::InvalidOsStr => (),
|
||||||
|
_ => panic!("Improper error type for path_to_cstring"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("path_to_cstring should return Err if there are null bytes");
|
||||||
|
}
|
||||||
|
|
||||||
let result_valid = path_to_cstring("valid/path");
|
let result_valid = path_to_cstring("valid/path");
|
||||||
|
|
||||||
assert_eq!(result_valid, Ok(CString::new("valid/path")?));
|
assert_eq!(result_valid, Ok(CString::new("valid/path")?));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_err_could_not_open() {
|
fn test_err_could_not_open() {
|
||||||
let file_name = "non-existent.xtc";
|
let file_name = "non-existent.xtc";
|
||||||
let expexted_path = Path::new(&file_name);
|
|
||||||
|
let path = Path::new(&file_name);
|
||||||
if let Err(e) = XDRFile::open(file_name, FileMode::Read) {
|
if let Err(e) = XDRFile::open(file_name, FileMode::Read) {
|
||||||
match e {
|
if let Error::CouldNotOpen {
|
||||||
Error::CouldNotOpenFile(err_path, err_mode) => {
|
path: err_path,
|
||||||
assert_eq!(expexted_path, err_path);
|
mode: err_mode,
|
||||||
assert!(FileMode::Read == err_mode)
|
} = e
|
||||||
}
|
{
|
||||||
_ => panic!("Wrong Error type"),
|
assert_eq!(path, err_path);
|
||||||
|
assert_eq!(FileMode::Read, err_mode)
|
||||||
|
} else {
|
||||||
|
panic!("Wrong Error type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -566,13 +597,10 @@ mod tests {
|
|||||||
let file_name = "README.md"; // not a trajectory
|
let file_name = "README.md"; // not a trajectory
|
||||||
let mut trr = TRRTrajectory::open_read(file_name)?;
|
let mut trr = TRRTrajectory::open_read(file_name)?;
|
||||||
if let Err(e) = trr.get_num_atoms() {
|
if let Err(e) = trr.get_num_atoms() {
|
||||||
match e {
|
assert_eq!(Some(ErrorCode::ExdrMagic), e.code());
|
||||||
Error::CouldNotReadAtomNumber(code) => {
|
} else {
|
||||||
assert_eq!(xdrfile::exdrMAGIC, code);
|
panic!("Should not be able to read number of atoms from readme");
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong Error type"),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,34 +610,13 @@ mod tests {
|
|||||||
let mut frame = Frame::with_capacity(1);
|
let mut frame = Frame::with_capacity(1);
|
||||||
let mut trr = TRRTrajectory::open_read(file_name)?;
|
let mut trr = TRRTrajectory::open_read(file_name)?;
|
||||||
if let Err(e) = trr.read(&mut frame) {
|
if let Err(e) = trr.read(&mut frame) {
|
||||||
match e {
|
assert_eq!(Some(ErrorCode::ExdrMagic), e.code());
|
||||||
Error::CouldNotRead(code) => {
|
} else {
|
||||||
assert_eq!(xdrfile::exdrMAGIC, code);
|
panic!("Should not be able to read number of atoms from readme");
|
||||||
}
|
|
||||||
_ => panic!("Wrong Error type"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_err_eof() {
|
|
||||||
let error = Error::CouldNotRead(xdrfile::exdrENDOFFILE);
|
|
||||||
assert!(error.is_eof());
|
|
||||||
|
|
||||||
let error = Error::CouldNotRead(xdrfile::exdrENDOFFILE + 1);
|
|
||||||
assert!(!error.is_eof());
|
|
||||||
|
|
||||||
let error = Error::CouldNotWrite(0);
|
|
||||||
assert!(!error.is_eof());
|
|
||||||
|
|
||||||
let error = Error::CouldNotFlush(255);
|
|
||||||
assert!(!error.is_eof());
|
|
||||||
|
|
||||||
let error = Error::CouldNotOpenFile(PathBuf::from("not/a/file"), FileMode::Read);
|
|
||||||
assert!(!error.is_eof());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_err_file_eof() -> Result<(), Box<dyn std::error::Error>> {
|
fn test_err_file_eof() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let tempfile = NamedTempFile::new()?;
|
let tempfile = NamedTempFile::new()?;
|
||||||
@@ -654,4 +661,15 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_code() {
|
||||||
|
let code: ErrorCode = 0.into();
|
||||||
|
assert!(!check_code(code, ErrorTask::Read).is_some());
|
||||||
|
|
||||||
|
for i in vec![1, 10, 100, 1000] {
|
||||||
|
let code: ErrorCode = i.into();
|
||||||
|
assert!(check_code(code, ErrorTask::Read).is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user