From e82295348474f3fb9751a301125a3790b3cc5bad Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Tue, 11 Sep 2018 08:41:11 -0600 Subject: [PATCH] Replace quick_error with failure --- Cargo.toml | 1 + src/lib.rs | 88 +-------------------------------- src/mo/information_element.rs | 36 +++++++++----- src/mo/message.rs | 93 ++++++++++++++++++++++------------- src/mo/session_status.rs | 12 +++-- src/storage/filesystem.rs | 76 +++++++++++++++------------- src/storage/memory.rs | 9 ++-- src/storage/mod.rs | 8 +-- 8 files changed, 147 insertions(+), 176 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ad43623d..3c2f3646 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ categories = ["science"] byteorder = "1.1" chrono = "0.4" docopt = "0.8" +failure = "*" log = "0.3" quick-error = "1.2" serde = "1.0" diff --git a/src/lib.rs b/src/lib.rs index c16d187c..b6666474 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,9 +50,9 @@ extern crate byteorder; extern crate chrono; #[macro_use] -extern crate log; +extern crate failure; #[macro_use] -extern crate quick_error; +extern crate log; #[macro_use] extern crate serde_derive; extern crate walkdir; @@ -60,87 +60,3 @@ extern crate walkdir; pub mod directip; pub mod mo; pub mod storage; - -/// Create-specific `Result`. -pub type Result = std::result::Result; - -quick_error! { - /// Crate-specific errors - #[derive(Debug)] - pub enum Error { - /// A wrapper around a `std::io::Error`. - Io(err: std::io::Error) { - from() - cause(err) - description(err.description()) - display("io error: {}", err) - } - /// Invalid protocol revision number. - InvalidProtocolRevisionNumber(n: u8) { - description("invalid protocol revision number") - display("invalid protocol revision number: {}", n) - } - /// Invalid information element identifier. - InvalidInformationElementIdentifier(n: u8) { - description("invalid information element identifier") - display("invalid information element identifier: {}", n) - } - /// The timestamp is negative, but only positive ones are supported. - NegativeTimestamp(timestamp: i64) { - description("only positive timestamps are allowed in mo messages") - display("negative timestamp: {}", timestamp) - } - /// No header on a MO message. - NoHeader { - description("no header on a mo message") - } - /// No payload on a MO message. - NoPayload { - description("no payload on a mo message") - } - /// We expected a directory, but this isn't one. - /// - /// TODO can this be a PathBuf? - NotADirectory(s: std::ffi::OsString) { - description("the os string is not a directory") - display("this os string is not a directory: {}", s.to_string_lossy()) - } - /// The overall message length is too long. - OverallMessageLength(len: usize) { - description("the overall message length is too long") - display("the overall message length is too long: {}", len) - } - /// The payload is too long. - PayloadTooLong(len: usize) { - description("the mo payload is too long") - display("the payload is too long: {}", len) - } - /// Two headers in an MO message. - TwoHeaders { - description("two headers in a MO message") - } - /// Two payloads in an MO message. - TwoPayloads { - description("two payloads in a MO message") - } - /// Wrapper around `std::str::Utf8Error`. - Utf8(err: std::str::Utf8Error) { - from() - cause(err) - description(err.description()) - display("utf8 error: {}", err) - } - /// The session status is unknown. - UnknownSessionStatus(n: u8) { - description("unknown session status") - display("uknown session status code: {}", n) - } - /// Wrapper around `walkdir::Error`. - WalkDir(err: walkdir::Error) { - from() - cause(err) - description(err.description()) - display("walkdir error: {}", err) - } - } -} diff --git a/src/mo/information_element.rs b/src/mo/information_element.rs index 82200afb..43d88cfb 100644 --- a/src/mo/information_element.rs +++ b/src/mo/information_element.rs @@ -3,7 +3,6 @@ //! Information elements come after the SBD header. They come in many types, //! including more header-type information and the actual data payload. -use Result; use chrono::Utc; use mo::{Header, SessionStatus}; use std::io::{Read, Write}; @@ -26,10 +25,25 @@ pub enum InformationElement { LocationInformation([u8; 7]), } +/// Mobile-originated information element errors. +#[derive(Clone, Copy, Debug, Fail)] +pub enum Error { + /// The identifier is invalid. + #[fail(display = "invalid information element identifier: {}", _0)] + InvalidInformationElementIdentifier(u8), + + /// The timestamp is negative. + #[fail(display = "negative timestamp: {}", _0)] + NegativeTimestamp(i64), + + /// The payload is too long. + #[fail(display = "the payload is too long at {} bytes", _0)] + PayloadTooLong(usize), +} + impl InformationElement { /// Reads this information element from a `Read`. - pub fn read_from(mut read: R) -> Result { - use Error; + pub fn read_from(mut read: R) -> Result { use byteorder::{BigEndian, ReadBytesExt}; use chrono::TimeZone; @@ -43,9 +57,10 @@ impl InformationElement { let session_status = SessionStatus::new(read.read_u8()?)?; let momsn = read.read_u16::()?; let mtmsn = read.read_u16::()?; - let time_of_session = read.read_u32::().map_err(Error::from).map(|n| { - Utc.timestamp(i64::from(n), 0) - })?; + let time_of_session = read + .read_u32::() + .map_err(::failure::Error::from) + .map(|n| Utc.timestamp(i64::from(n), 0))?; Ok(InformationElement::Header(Header { auto_id: auto_id, imei: imei, @@ -66,7 +81,7 @@ impl InformationElement { Ok(InformationElement::LocationInformation(bytes)) } 5 => unimplemented!(), - _ => Err(Error::InvalidInformationElementIdentifier(iei)), + _ => Err(Error::InvalidInformationElementIdentifier(iei).into()), } } @@ -90,9 +105,8 @@ impl InformationElement { } /// Writes this information element to a `Write`. - pub fn write_to(&self, mut write: W) -> Result<()> { + pub fn write_to(&self, mut write: W) -> Result<(), ::failure::Error> { use byteorder::{BigEndian, WriteBytesExt}; - use Error; use std::u16; match *self { @@ -106,7 +120,7 @@ impl InformationElement { write.write_u16::(header.mtmsn)?; let timestamp = header.time_of_session.timestamp(); if timestamp < 0 { - return Err(Error::NegativeTimestamp(timestamp)); + return Err(Error::NegativeTimestamp(timestamp).into()); } else { write.write_u32::(timestamp as u32)?; }; @@ -115,7 +129,7 @@ impl InformationElement { write.write_u8(2)?; let len = payload.len(); if len > u16::MAX as usize { - return Err(Error::PayloadTooLong(len)); + return Err(Error::PayloadTooLong(len).into()); } else { write.write_u16::(len as u16)?; } diff --git a/src/mo/message.rs b/src/mo/message.rs index ca23a007..0ef52755 100644 --- a/src/mo/message.rs +++ b/src/mo/message.rs @@ -1,11 +1,41 @@ -use Result; use chrono::{DateTime, Utc}; use mo::{Header, InformationElement, SessionStatus}; use std::cmp::Ordering; use std::io::{Read, Write}; use std::path::Path; -const PROTOCOL_REVISION_NUMBER: u8 = 1; +/// The only valid protocol revision number. +pub const PROTOCOL_REVISION_NUMBER: u8 = 1; + +/// Errors returned when creating a message. +#[derive(Debug, Fail)] +pub enum Error { + /// The message has an invalid protocol revision number. + #[fail(display = "invalid protocol revision number: {}", _0)] + InvalidProtocolRevisionNumber(u8), + + /// There are two headers in the message. + #[fail(display = "two headers")] + TwoHeaders(Header, Header), + + /// There are two payloads in the message. + #[fail(display = "two payloads")] + TwoPayloads(Vec, Vec), + + /// There is no header in the message. + #[fail(display = "no header")] + NoHeader, + + /// There is no payload in the message. + #[fail(display = "no payload")] + NoPayload, + + /// The overall message length is too big. + #[fail(display = "the overall message length is too big: {}", _0)] + OverallMessageLength(usize), +} + +/// Error returned when there is no /// A mobile-origined Iridium SBD message. /// @@ -26,7 +56,7 @@ impl Message { /// use sbd::mo::Message; /// let message = Message::from_path("data/0-mo.sbd").unwrap(); /// ``` - pub fn from_path>(path: P) -> Result { + pub fn from_path>(path: P) -> Result { use std::fs::File; let file = File::open(path)?; Message::read_from(file) @@ -44,16 +74,13 @@ impl Message { /// let mut file = File::open("data/0-mo.sbd").unwrap(); /// let message = Message::read_from(file).unwrap(); /// ``` - pub fn read_from(mut read: R) -> Result { - use Error; + pub fn read_from(mut read: R) -> Result { use byteorder::{BigEndian, ReadBytesExt}; use std::io::Cursor; let protocol_revision_number = read.read_u8()?; if protocol_revision_number != PROTOCOL_REVISION_NUMBER { - return Err(Error::InvalidProtocolRevisionNumber( - protocol_revision_number, - )); + return Err(Error::InvalidProtocolRevisionNumber(protocol_revision_number).into()); } let overall_message_length = read.read_u16::()?; let mut message = vec![0; overall_message_length as usize]; @@ -65,7 +92,7 @@ impl Message { information_elements.push(InformationElement::read_from(&mut cursor)?); } - Message::new(information_elements) + Message::new(information_elements).map_err(::failure::Error::from) } /// Creates a new message from information elements. @@ -90,25 +117,23 @@ impl Message { /// let message = Message::new(vec![header, payload]); /// # } /// ``` - pub fn new>(iter: I) -> Result { - use Error; - + pub fn new>(iter: I) -> Result { let mut header = None; let mut payload = None; let mut information_elements = Vec::new(); for information_element in iter { match information_element { - InformationElement::Header(h) => if header.is_some() { - return Err(Error::TwoHeaders); + InformationElement::Header(h) => if let Some(header) = header { + return Err(Error::TwoHeaders(h, header)); } else { header = Some(h); - } - InformationElement::Payload(p) => if payload.is_some() { - return Err(Error::TwoPayloads); + }, + InformationElement::Payload(p) => if let Some(payload) = payload { + return Err(Error::TwoPayloads(p, payload)); } else { payload = Some(p); - } - ie => information_elements.push(ie) + }, + ie => information_elements.push(ie), } } Ok(Message { @@ -226,16 +251,20 @@ impl Message { /// let mut cursor = Cursor::new(Vec::new()); /// message.write_to(&mut cursor); /// ``` - pub fn write_to(&self, mut write: W) -> Result<()> { - use byteorder::{WriteBytesExt, BigEndian}; + pub fn write_to(&self, mut write: W) -> Result<(), ::failure::Error> { + use byteorder::{BigEndian, WriteBytesExt}; use std::u16; - use Error; let header = InformationElement::from(self.header); let payload = InformationElement::from(self.payload.clone()); - let overall_message_length = header.len() + payload.len() + self.information_elements.iter().map(|ie| ie.len()).sum::(); + let overall_message_length = header.len() + payload.len() + + self + .information_elements + .iter() + .map(|ie| ie.len()) + .sum::(); if overall_message_length > u16::MAX as usize { - return Err(Error::OverallMessageLength(overall_message_length)); + return Err(Error::OverallMessageLength(overall_message_length).into()); } write.write_u8(PROTOCOL_REVISION_NUMBER)?; @@ -353,7 +382,11 @@ mod tests { #[test] fn write() { - let message = Message::new(vec![header().into(), vec![1].into(), InformationElement::LocationInformation([0; 7])]).unwrap(); + let message = Message::new(vec![ + header().into(), + vec![1].into(), + InformationElement::LocationInformation([0; 7]), + ]).unwrap(); let mut cursor = Cursor::new(Vec::new()); message.write_to(&mut cursor).unwrap(); cursor.set_position(0); @@ -366,14 +399,8 @@ mod tests { let header1 = header(); let mut header2 = header(); header2.time_of_session = Utc.ymd(2010, 6, 11).and_hms(0, 0, 0); - let message1 = Message::new(vec![ - header1.into(), - Vec::new().into(), - ]).unwrap(); - let message2 = Message::new(vec![ - header2.into(), - Vec::new().into(), - ]).unwrap(); + let message1 = Message::new(vec![header1.into(), Vec::new().into()]).unwrap(); + let message2 = Message::new(vec![header2.into(), Vec::new().into()]).unwrap(); assert!(message2 < message1); } } diff --git a/src/mo/session_status.rs b/src/mo/session_status.rs index 7387b155..e6658e96 100644 --- a/src/mo/session_status.rs +++ b/src/mo/session_status.rs @@ -1,5 +1,3 @@ -use Result; - /// The status of a mobile-originated session. /// /// The descriptions for these codes are taken directly from the `DirectIP` documentation. @@ -27,6 +25,11 @@ pub enum SessionStatus { Prohibited = 15, } +/// An unknown status message code. +#[derive(Clone, Copy, Debug, Fail)] +#[fail(display = "unknown session status: {}", _0)] +pub struct UnknownSessionStatus(pub u8); + impl SessionStatus { /// Creates a new session status from a code. /// @@ -39,8 +42,7 @@ impl SessionStatus { /// assert!(SessionStatus::new(0).is_ok()); /// assert!(SessionStatus::new(3).is_err()); /// ``` - pub fn new(n: u8) -> Result { - use Error; + pub fn new(n: u8) -> Result { match n { 0 => Ok(SessionStatus::Ok), 1 => Ok(SessionStatus::OkMobileTerminatedTooLarge), @@ -50,7 +52,7 @@ impl SessionStatus { 13 => Ok(SessionStatus::RFLinkLoss), 14 => Ok(SessionStatus::IMEIProtocolAnomaly), 15 => Ok(SessionStatus::Prohibited), - _ => Err(Error::UnknownSessionStatus(n)), + _ => Err(UnknownSessionStatus(n)), } } } diff --git a/src/storage/filesystem.rs b/src/storage/filesystem.rs index f1c36241..63a2cb37 100644 --- a/src/storage/filesystem.rs +++ b/src/storage/filesystem.rs @@ -1,8 +1,9 @@ //! Store SBD messages on the filesystem. - -use {Error, Result}; +use failure::Error; use mo::Message; +use std::ffi::OsString; +use std::fmt; use std::fs; use std::path::{Path, PathBuf}; use storage; @@ -21,6 +22,25 @@ pub struct Storage { root: PathBuf, } +/// An iterator over the messages in a `Storage`. +/// +/// For now, this iterator will just return all messages with an `sbd` extension under the root of +/// the storage tree. We could try to get smarter and mirror the pattern logic for saving, but for +/// now that's more work and complexity than we need. +/// +/// # Errors +/// +/// This iterator's `Item` is a `sbd::Result`, because a file with an `sbd` extension +/// might not convert to a message successfully. +#[allow(missing_debug_implementations)] +pub struct StorageIterator { + iter: walkdir::Iter, +} + +/// An error returned when trying to create a storage for a non-directoy. +#[derive(Debug, Fail)] +pub struct NotADirectory(OsString); + impl Storage { /// Opens a new storage for a given directory. /// @@ -35,14 +55,14 @@ impl Storage { /// let storage = FilesystemStorage::open("data").unwrap(); /// assert!(FilesystemStorage::open("not/a/directory").is_err()); /// ``` - pub fn open>(root: P) -> Result { + pub fn open>(root: P) -> Result { let metadata = fs::metadata(root.as_ref())?; if !metadata.is_dir() { - Err(Error::NotADirectory( - root.as_ref().as_os_str().to_os_string(), - )) + Err(NotADirectory(root.as_ref().as_os_str().to_os_string()).into()) } else { - Ok(Storage { root: root.as_ref().to_path_buf() }) + Ok(Storage { + root: root.as_ref().to_path_buf(), + }) } } @@ -62,7 +82,7 @@ impl Storage { } impl storage::Storage for Storage { - fn store(&mut self, message: Message) -> Result<()> { + fn store(&mut self, message: Message) -> Result<(), ::failure::Error> { let mut path_buf = self.root.clone(); path_buf.push(message.imei()); path_buf.push(message.time_of_session().format("%Y").to_string()); @@ -79,59 +99,49 @@ impl storage::Storage for Storage { Ok(()) } - fn messages(&self) -> Result> { + fn messages(&self) -> Result, ::failure::Error> { self.iter().collect() } - fn messages_from_imei(&self, imei: &str) -> Result> { + fn messages_from_imei(&self, imei: &str) -> Result, ::failure::Error> { let mut path = self.root.clone(); path.push(imei); StorageIterator::new(&path).collect() } } -/// An iterator over the messages in a `Storage`. -/// -/// For now, this iterator will just return all messages with an `sbd` extension under the root of -/// the storage tree. We could try to get smarter and mirror the pattern logic for saving, but for -/// now that's more work and complexity than we need. -/// -/// # Errors -/// -/// This iterator's `Item` is a `sbd::Result`, because a file with an `sbd` extension -/// might not convert to a message successfully. -#[allow(missing_debug_implementations)] -pub struct StorageIterator { - iter: walkdir::Iter, -} - impl StorageIterator { fn new(root: &Path) -> StorageIterator { - StorageIterator { iter: walkdir::WalkDir::new(root).into_iter() } + StorageIterator { + iter: walkdir::WalkDir::new(root).into_iter(), + } } } impl Iterator for StorageIterator { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { self.iter .by_ref() .skip_while(|r| { r.as_ref() - .map(|d| { - d.path().extension().map_or(true, |e| e != SBD_EXTENSION) - }) + .map(|d| d.path().extension().map_or(true, |e| e != SBD_EXTENSION)) .unwrap_or(true) }) .next() .map(|r| { - r.map_err(Error::from).and_then( - |d| Message::from_path(d.path()), - ) + r.map_err(Error::from) + .and_then(|d| Message::from_path(d.path())) }) } } +impl fmt::Display for NotADirectory { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "not a directory: {}", self.0.to_string_lossy()) + } +} + #[cfg(test)] mod tests { use std::path::PathBuf; diff --git a/src/storage/memory.rs b/src/storage/memory.rs index db50ab85..0a1b68ae 100644 --- a/src/storage/memory.rs +++ b/src/storage/memory.rs @@ -2,7 +2,6 @@ //! //! Useful primarily for testing. -use Result; use mo::Message; use storage; @@ -21,17 +20,19 @@ impl Storage { /// let storage = sbd::storage::MemoryStorage::new(); /// ``` pub fn new() -> Storage { - Storage { messages: Vec::new() } + Storage { + messages: Vec::new(), + } } } impl storage::Storage for Storage { - fn store(&mut self, message: Message) -> Result<()> { + fn store(&mut self, message: Message) -> Result<(), ::failure::Error> { self.messages.push(message); Ok(()) } - fn messages(&self) -> Result> { + fn messages(&self) -> Result, ::failure::Error> { Ok(self.messages.clone()) } } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 06c783f0..cb883803 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -6,7 +6,7 @@ mod memory; pub use self::filesystem::Storage as FilesystemStorage; pub use self::memory::Storage as MemoryStorage; -use Result; +use failure::Error; use mo::Message; /// Basic storage operations. @@ -22,7 +22,7 @@ pub trait Storage { /// let mut storage = MemoryStorage::new(); /// storage.store(message); /// ``` - fn store(&mut self, message: Message) -> Result<()>; + fn store(&mut self, message: Message) -> Result<(), Error>; /// Retrieves all messages in this storage as a vector. /// @@ -37,7 +37,7 @@ pub trait Storage { /// let messages = storage.messages().unwrap(); /// assert_eq!(vec![message], messages); /// ``` - fn messages(&self) -> Result>; + fn messages(&self) -> Result, Error>; /// Retrieves all messages for a given IMEI. /// @@ -57,7 +57,7 @@ pub trait Storage { /// let messages = storage.messages_from_imei("300234063904191").unwrap(); /// assert!(messages.is_empty()); /// ``` - fn messages_from_imei(&self, imei: &str) -> Result> { + fn messages_from_imei(&self, imei: &str) -> Result, Error> { self.messages().map(|mut v| { v.retain(|m| m.imei() == imei); v