mirror of
https://github.com/JockeTF/fimfareader.git
synced 2024-11-27 07:28:00 +01:00
Use struct for error handling
This commit is contained in:
parent
b250e8e1cb
commit
dbebf22d42
5 changed files with 172 additions and 59 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
128
src/error.rs
128
src/error.rs
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue