Use struct for error handling

This commit is contained in:
Joakim Soderlund 2019-07-27 16:04:38 +00:00
parent b250e8e1cb
commit dbebf22d42
5 changed files with 172 additions and 59 deletions

View file

@ -1,6 +1,7 @@
//! Archive fetcher.
use std::fs::File;
use std::io::ErrorKind as IoErrorKind;
use std::io::{BufReader, Read, Seek};
use std::path::Path;
use std::sync::Mutex;
@ -8,9 +9,9 @@ use std::sync::Mutex;
use zip::read::ZipArchive;
use zip::result::ZipError;
use crate::error::{Error, Result};
use super::parser::parse;
use super::story::Story;
use crate::error::{Error, Result};
pub struct Fetcher<T>
where
@ -22,10 +23,11 @@ where
impl Fetcher<BufReader<File>> {
pub fn from(path: impl AsRef<Path>) -> Result<Self> {
use Error::*;
use IoErrorKind::*;
let file = File::open(path).map_err(|e| match e {
_ => SourceError("Could not open archive file."),
let file = File::open(path).map_err(|e| match e.kind() {
NotFound => Error::archive("File not found"),
_ => Error::archive("Could not open file"),
})?;
Self::from_reader(BufReader::with_capacity(8_000_000, file))
@ -45,26 +47,24 @@ where
}
fn open(archive: T) -> Result<ZipArchive<T>> {
use Error::*;
use ZipError::*;
ZipArchive::new(archive).map_err(|e| match e {
InvalidArchive(e) => SourceError(e),
UnsupportedArchive(e) => SourceError(e),
_ => SourceError("Could not read archive."),
InvalidArchive(e) => Error::archive(e),
UnsupportedArchive(e) => Error::archive(e),
_ => Error::archive("Unknown ZIP-file issue"),
})
}
fn load(archive: &mut ZipArchive<T>) -> Result<Vec<Story>> {
use Error::*;
use ZipError::*;
let file = archive.by_name("index.json").map_err(|e| match e {
FileNotFound => SourceError("Missing archive index."),
_ => SourceError("Could not open archive index."),
FileNotFound => Error::archive("Missing story index"),
_ => Error::archive("Could not open story index"),
})?;
parse(BufReader::with_capacity(8_000_000, file))
parse(BufReader::with_capacity(8_000_000, file)).map_err(Error::index)
}
pub fn fetch(&self, key: i64) -> Option<&Story> {
@ -75,23 +75,23 @@ where
}
pub fn read(&self, path: &str) -> Result<Vec<u8>> {
use Error::*;
use ZipError::*;
let mut archive = self.archive.lock().map_err(|e| match e {
_ => SourceError("Could not acquire archive lock."),
_ => Error::archive("Could not acquire fetcher lock"),
})?;
let mut file = archive.by_name(path).map_err(|e| match e {
FileNotFound => SourceError("File not found."),
_ => SourceError("Could not open file."),
FileNotFound => Error::archive("Missing story data"),
_ => Error::archive("Could not open story data"),
})?;
let size = file.size() as usize;
let mut buf = Vec::with_capacity(size);
file.read_to_end(&mut buf)
.map_err(|_| SourceError("Could not read file."))?;
file.read_to_end(&mut buf).map_err(|e| match e {
_ => Error::archive("Could not read story data"),
})?;
Ok(buf)
}

View file

@ -4,22 +4,21 @@ use std::io::BufRead;
use std::sync::mpsc::{channel, Receiver};
use std::thread::spawn;
use serde::de::Error;
use serde_json::error::Result;
use serde_json::from_str;
use crate::error::{Error, Result};
use super::story::Story;
const TRIM: &[char] = &['"', ',', ' ', '\t', '\n', '\r'];
pub fn parse(reader: impl BufRead) -> Result<Vec<Story>> {
use Error::*;
let (tx, rx) = channel();
let rx = spawn_parser(rx);
for line in reader.lines() {
let line = line.map_err(|e| match e {
_ => SourceError("Could not read index line."),
_ => Error::custom("Could not read line"),
})?;
if tx.send(line).is_ok() {
@ -27,8 +26,8 @@ pub fn parse(reader: impl BufRead) -> Result<Vec<Story>> {
}
return Err(match rx.recv() {
Err(_) => SourceError("Parser disappeared unexpectedly."),
Ok(Ok(_)) => SourceError("Parser returned unexpectedly."),
Err(_) => Error::custom("Parser disappeared unexpectedly"),
Ok(Ok(_)) => Error::custom("Parser returned unexpectedly"),
Ok(Err(error)) => error,
});
}
@ -36,13 +35,11 @@ pub fn parse(reader: impl BufRead) -> Result<Vec<Story>> {
drop(tx);
rx.recv().map_err(|e| match e {
_ => SourceError("Missing parser result."),
_ => Error::custom("Missing parser result"),
})?
}
fn spawn_parser(stream: Receiver<String>) -> Receiver<Result<Vec<Story>>> {
use Error::*;
let (tx, rx) = channel();
spawn(move || {
@ -68,11 +65,11 @@ fn spawn_parser(stream: Receiver<String>) -> Receiver<Result<Vec<Story>>> {
stories.shrink_to_fit();
if wrappers != "{}" {
return tx.send(Err(SourceError("Invalid index structure.")));
return tx.send(Err(Error::custom("Invalid file structure")));
}
if count != stories.len() {
return tx.send(Err(SourceError("Index contains duplicates.")));
return tx.send(Err(Error::custom("Found duplicate story")));
}
tx.send(Ok(stories))
@ -82,8 +79,6 @@ fn spawn_parser(stream: Receiver<String>) -> Receiver<Result<Vec<Story>>> {
}
fn deserialize(line: String) -> Result<Story> {
use Error::*;
let split = line
.splitn(2, ':')
.map(|value| value.trim_matches(TRIM))
@ -91,19 +86,17 @@ fn deserialize(line: String) -> Result<Story> {
let (skey, json) = match split[..] {
[skey, json] => Ok((skey, json)),
_ => Err(SourceError("Invalid line format.")),
_ => Err(Error::custom("Invalid line format")),
}?;
let key: i64 = skey.parse().map_err(|e| match e {
_ => SourceError("Invalid meta key."),
})?;
let story: Story = from_str(json)?;
let story: Story = from_str(json).map_err(|e| match e {
_ => SourceError("Invalid meta value."),
let key: i64 = skey.parse().map_err(|e| match e {
_ => Error::custom("Invalid line key"),
})?;
if key != story.id {
return Err(SourceError("Meta key mismatch."));
return Err(Error::custom("Line key mismatch"));
}
Ok(story)

View file

@ -182,10 +182,10 @@ where
Ok(value.as_i64().unwrap())
} else if value.is_string() {
value.as_str().unwrap().parse().map_err(|e| match e {
_ => Error::custom("Could not parse ID string."),
_ => Error::custom("Could not parse ID string"),
})
} else {
Err(Error::custom("Invalid type for ID value."))
Err(Error::custom("Invalid type for ID value"))
}
}
@ -199,15 +199,15 @@ impl<'de> Deserialize<'de> for Color {
let text = object
.get("hex")
.and_then(|value| value.as_str())
.ok_or_else(|| Error::custom("Color is missing hex value."))?;
.ok_or_else(|| Error::custom("Color is missing hex value"))?;
let array = hex::decode(text).map_err(|e| match e {
_ => Error::custom("Color hex has invalid value."),
_ => Error::custom("Color hex has invalid value"),
})?;
match array[..] {
[red, green, blue] => Ok(Color { red, green, blue }),
_ => Err(Error::custom("Color hex has invalid length.")),
_ => Err(Error::custom("Color hex has invalid length")),
}
}
}

View file

@ -1,10 +1,126 @@
//! Error types.
#[derive(Debug)]
pub enum Error {
InvalidStory(),
SourceError(&'static str),
UserError(&'static str),
use std::error::Error as StdError;
use std::fmt::Result as FmtResult;
use std::fmt::{Display, Formatter};
use std::result::Result as StdResult;
use serde_json::error::Error as SerdeError;
use self::ErrorKind::*;
pub type Result<T> = StdResult<T, Error>;
#[derive(Clone, Debug)]
pub enum ErrorKind {
ArchiveError,
IndexError,
InvalidStory,
UsageError,
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
message: Option<String>,
source: Option<Box<dyn StdError + 'static>>,
}
pub struct ErrorBuilder(Error);
impl ErrorBuilder {
pub fn new(kind: ErrorKind) -> Self {
ErrorBuilder(Error {
kind,
message: None,
source: None,
})
}
pub fn message(mut self, message: impl ToString) -> Self {
self.0.message = Some(message.to_string());
self
}
pub fn source(mut self, source: impl StdError + 'static) -> Self {
self.0.source = Some(Box::new(source));
self
}
pub fn build(self) -> Error {
self.0
}
}
impl Error {
pub fn archive(message: impl ToString) -> Self {
ErrorBuilder::new(ArchiveError).message(message).build()
}
pub fn index(error: SerdeError) -> Self {
ErrorBuilder::new(IndexError)
.message(&error)
.source(error)
.build()
}
pub fn invalid() -> Self {
ErrorBuilder::new(InvalidStory).build()
}
pub fn usage(message: impl ToString) -> Self {
ErrorBuilder::new(UsageError).message(message).build()
}
pub fn kind(&self) -> ErrorKind {
self.kind.clone()
}
pub fn message(&self) -> Option<&String> {
self.message.as_ref()
}
}
fn lower(message: &str) -> String {
let mut chars = message.chars();
let head: String = match chars.next() {
Some(c) => c.to_lowercase().collect(),
None => return String::from(message),
};
format!("{}{}", head, chars.as_str())
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let message = match self {
ArchiveError => "Archive error",
IndexError => "Index error",
InvalidStory => "Invalid story",
UsageError => "Usage error",
};
write!(f, "{}", message)
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let message = match &self.message {
Some(message) => message,
None => "Unknown cause",
};
let kind = self.kind();
let info = lower(message);
write!(f, "{}, {}.", kind, info)
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.source.as_ref().map(Box::as_ref)
}
}

View file

@ -7,26 +7,30 @@ use std::env::args;
use std::time::Instant;
use crate::archive::Fetcher;
use crate::error::{Error, Result};
use crate::error::Error;
fn main() -> Result<()> {
use Error::*;
fn exit(error: Error) -> ! {
eprintln!("{}", error);
std::process::exit(1)
}
fn main() {
let argv = args().collect::<Vec<String>>();
let path = match argv.len() {
2 => Ok(argv.get(1).unwrap()),
_ => Err(UserError("Usage: fimfareader <ARCHIVE>")),
}?;
if argv.len() != 2 {
eprintln!("Usage: fimfareader <ARCHIVE>");
std::process::exit(1);
}
println!("Hellopaca, World!");
let start = Instant::now();
let fetcher = Fetcher::from(path)?;
let result = Fetcher::from(&argv[1]);
let finish = Instant::now() - start;
let fetcher = result.map_err(exit).unwrap();
println!("Finished loading in {} milliseconds.", finish.as_millis());
println!("The archive contains {} stories.", fetcher.iter().count());
Ok(())
}