Skip to content

Commit

Permalink
Merge pull request #49 from ololduck/feat/precise-error-types
Browse files Browse the repository at this point in the history
Replace the use of eyre with our own error type
  • Loading branch information
crowdagger authored Oct 5, 2024
2 parents ad5d9de + 5fe4304 commit 14faaee
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 71 deletions.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ zip-command = ["tempfile"]
zip-library = ["libzip", "libzip/time"]

[dependencies]
eyre = "0.6"
thiserror = "1.0"
once_cell = "1"
upon = "0.7"
chrono = { version = "0.4", default-features = false, features = ["clock", "std", "wasmbind"] }
uuid = { version = "1", features = ["v4"] }
tempfile = { version = "3", optional = true }
libzip = { version = "0.6", optional = true, default-features = false, features = ["deflate"], package = "zip"}
tempfile = { version = "3", optional = true }
libzip = { version = "0.6", optional = true, default-features = false, features = ["deflate"], package = "zip" }
html-escape = "0.2"
log = "0.4"

Expand Down
39 changes: 25 additions & 14 deletions src/epub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ use crate::templates;
use crate::toc::{Toc, TocElement};
use crate::zip::Zip;
use crate::ReferenceType;
use crate::Result;
use crate::{common, EpubContent};

use std::io;
use std::io::Read;
use std::path::Path;
use std::str::FromStr;

use eyre::{bail, Context, Result};

/// Represents the EPUB version.
///
/// Currently, this library supports EPUB 2.0.1 and 3.0.1.
Expand Down Expand Up @@ -47,15 +46,15 @@ impl ToString for PageDirection {
}
}

impl std::str::FromStr for PageDirection {
type Err = eyre::Report;
impl FromStr for PageDirection {
type Err = crate::Error;

fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_ref() {
"rtl" => Ok(PageDirection::Rtl),
"ltr" => Ok(PageDirection::Ltr),
_ => bail!("Invalid page direction: {}", s),
_ => Err(crate::Error::PageDirectionError(s)),
}
}
}
Expand Down Expand Up @@ -243,7 +242,7 @@ impl<Z: Zip> EpubBuilder<Z> {
}
"license" => self.metadata.license = Some(value.into()),
"toc_name" => self.metadata.toc_name = value.into(),
s => bail!("invalid metadata '{}'", s),
s => Err(crate::Error::InvalidMetadataError(s.to_string()))?,
}
Ok(self)
}
Expand All @@ -269,16 +268,16 @@ impl<Z: Zip> EpubBuilder<Z> {
}

/// Tells whether fields should be HTML-escaped.
///
///
/// * `true`: fields such as titles, description, and so on will be HTML-escaped everywhere (default)
/// * `false`: fields will be left as is (letting you in charge of making
/// * `false`: fields will be left as is (letting you in charge of making
/// sure they do not contain anything illegal, e.g. < and > characters)
pub fn escape_html(&mut self, val: bool) {
self.escape_html = val;
}

/// Sets the language of the EPUB
///
///
/// This is quite important as EPUB renderers rely on it
/// for e.g. hyphenating words.
pub fn set_lang<S: Into<String>>(&mut self, value: S) {
Expand Down Expand Up @@ -649,7 +648,7 @@ impl<Z: Zip> EpubBuilder<Z> {
items: common::indent(items.join("\n"), 2), // Not escaped: XML content
itemrefs: common::indent(itemrefs.join("\n"), 2), // Not escaped: XML content
date_modified: html_escape::encode_text(&date_modified.to_string()),
uuid: html_escape::encode_text(&uuid),
uuid: html_escape::encode_text(&uuid),
guide: common::indent(guide.join("\n"), 2), // Not escaped: XML content
date_published: if let Some(date) = date_published { date.to_string() } else { String::new() },
}
Expand All @@ -659,7 +658,12 @@ impl<Z: Zip> EpubBuilder<Z> {
match self.version {
EpubVersion::V20 => templates::v2::CONTENT_OPF.render(&data).to_writer(&mut res),
EpubVersion::V30 => templates::v3::CONTENT_OPF.render(&data).to_writer(&mut res),
}.wrap_err("could not render template for content.opf")?;
}
.map_err(|e| crate::Error::TemplateError {
msg: "could not render template for content.opf".to_string(),
cause: e.into(),
})?;
//.wrap_err("could not render template for content.opf")?;

Ok(res)
}
Expand All @@ -678,7 +682,10 @@ impl<Z: Zip> EpubBuilder<Z> {
templates::TOC_NCX
.render(&data)
.to_writer(&mut res)
.wrap_err("error rendering toc.ncx template")?;
.map_err(|e| crate::Error::TemplateError {
msg: "error rendering toc.ncx template".to_string(),
cause: e.into(),
})?;
Ok(res)
}

Expand Down Expand Up @@ -738,12 +745,16 @@ impl<Z: Zip> EpubBuilder<Z> {
String::new()
},
};

let mut res: Vec<u8> = vec![];
match self.version {
EpubVersion::V20 => templates::v2::NAV_XHTML.render(&data).to_writer(&mut res),
EpubVersion::V30 => templates::v3::NAV_XHTML.render(&data).to_writer(&mut res),
}.wrap_err("error rendering nav.xhtml template")?;
}
.map_err(|e| crate::Error::TemplateError {
msg: "error rendering nav.xhtml template".to_string(),
cause: e.into(),
})?;
Ok(res)
}
}
Expand Down
59 changes: 57 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub use epub::EpubVersion;
pub use epub::PageDirection;
pub use epub_content::EpubContent;
pub use epub_content::ReferenceType;
use libzip::result::ZipError;
pub use toc::Toc;
pub use toc::TocElement;
#[cfg(feature = "zip-command")]
Expand All @@ -153,5 +154,59 @@ pub use zip_command_or_library::ZipCommandOrLibrary;
#[cfg(feature = "libzip")]
pub use zip_library::ZipLibrary;

/// Re-exports the result type used across the library.
pub use eyre::Result;
/// Error type of this crate. Each variant represent a type of event that may happen during this crate's operations.
#[derive(thiserror::Error, Debug)]
pub enum Error {
/// An error caused while processing a template or its rendering.
#[error("{msg}: {cause:?}")]
TemplateError {
/// A message explaining what was happening when we recieved this error.
msg: String,
/// The root cause of the error.
// Box the error, since it is quite large (at least 136 bytes, thanks clippy!)
cause: Box<upon::Error>,
},
/// An error returned when encountering an unknown [`PageDirection`].
#[error("Invalid page direction specification: {0}")]
PageDirectionError(String),
/// An error returned when an unknown metadata key has been encountered.
#[error("Invalid metadata key: {0}")]
InvalidMetadataError(String),
/// An error returned when attempting to access the filesystem
#[error("{msg}: {cause:?}")]
IoError {
/// A message explaining what was happening when we recieved this error.
msg: String,
/// The root cause of the error.
cause: std::io::Error,
},
/// An error returned when something happened while invoking a zip program. See [`ZipCommand`].
#[error("Error while executing zip command: {0}")]
ZipCommandError(String),
/// An error returned when the zip library itself returned an error. See [`ZipLibrary`].
#[error(transparent)]
ZipError(#[from] ZipError),
/// An error returned when the zip library itself returned an error, but with an additional message. See [`ZipLibrary`].
#[error("{msg}: {cause:?}")]
ZipErrorWithMessage {
/// A message explaining what was happening when we recieved this error.
msg: String,
/// The root cause of the error.
cause: ZipError,
},
/// An error returned when an invalid [`Path`] has been encountered during epub processing.
#[error("Invalid path: {0}")]
InvalidPath(String),
}

impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::IoError {
msg: format!("{value:?}"),
cause: value,
}
}
}

/// A more convenient shorthand for functions returning an error in this crate.
pub type Result<T> = std::result::Result<T, Error>;
2 changes: 1 addition & 1 deletion src/zip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::io::Read;
use std::io::Write;
use std::path::Path;

use eyre::Result;
use crate::Result;

/// An abstraction over possible Zip implementations.
///
Expand Down
89 changes: 56 additions & 33 deletions src/zip_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// this file, You can obtain one at https://mozilla.org/MPL/2.0/.

use crate::zip::Zip;
use crate::Result;

use std::fs;
use std::fs::DirBuilder;
Expand All @@ -14,10 +15,6 @@ use std::path::Path;
use std::path::PathBuf;
use std::process::Command;

use eyre::bail;
use eyre::Context;
use eyre::Result;

/// Zip files using the system `zip` command.
///
/// Create a temporary directory, write temp files in that directory, and then
Expand All @@ -37,7 +34,10 @@ pub struct ZipCommand {
impl ZipCommand {
/// Creates a new ZipCommand, using default setting to create a temporary directory.
pub fn new() -> Result<ZipCommand> {
let temp_dir = tempfile::TempDir::new().wrap_err("could not create temporary directory")?;
let temp_dir = tempfile::TempDir::new().map_err(|e| crate::Error::IoError {
msg: "could not create temporary directory".to_string(),
cause: e,
})?;
let zip = ZipCommand {
command: String::from("zip"),
temp_dir,
Expand All @@ -51,8 +51,10 @@ impl ZipCommand {
/// # Arguments
/// * `temp_path`: the path where a temporary directory should be created.
pub fn new_in<P: AsRef<Path>>(temp_path: P) -> Result<ZipCommand> {
let temp_dir =
tempfile::TempDir::new_in(temp_path).wrap_err("could not create temporary directory")?;
let temp_dir = tempfile::TempDir::new_in(temp_path).map_err(|e| crate::Error::IoError {
msg: "could not create temporary directory".to_string(),
cause: e,
})?;
let zip = ZipCommand {
command: String::from("zip"),
temp_dir,
Expand All @@ -73,13 +75,16 @@ impl ZipCommand {
.current_dir(self.temp_dir.path())
.arg("-v")
.output()
.wrap_err_with(|| format!("failed to run command {name}", name = self.command))?;
.map_err(|e| crate::Error::IoError {
msg: format!("failed to run command {name}", name = self.command),
cause: e,
})?;
if !output.status.success() {
bail!(
"command {name} didn't return successfully: {output}",
return Err(crate::Error::ZipCommandError(format!(
"command {name} did not exit successfully: {output}",
name = self.command,
output = String::from_utf8_lossy(&output.stderr)
);
)));
}
Ok(())
}
Expand All @@ -93,25 +98,28 @@ impl ZipCommand {
DirBuilder::new()
.recursive(true)
.create(dest_dir)
.wrap_err_with(|| {
format!(
.map_err(|e| crate::Error::IoError {
msg: format!(
"could not create temporary directory in {path}",
path = dest_dir.display()
)
),
cause: e,
})?;
}

let mut f = File::create(&dest_file).wrap_err_with(|| {
format!(
let mut f = File::create(&dest_file).map_err(|e| crate::Error::IoError {
msg: format!(
"could not write to temporary file {file}",
file = path.as_ref().display()
)
),
cause: e,
})?;
io::copy(&mut content, &mut f).wrap_err_with(|| {
format!(
io::copy(&mut content, &mut f).map_err(|e| crate::Error::IoError {
msg: format!(
"could not write to temporary file {file}",
file = path.as_ref().display()
)
),
cause: e,
})?;
Ok(())
}
Expand All @@ -121,11 +129,11 @@ impl Zip for ZipCommand {
fn write_file<P: AsRef<Path>, R: Read>(&mut self, path: P, content: R) -> Result<()> {
let path = path.as_ref();
if path.starts_with("..") || path.is_absolute() {
bail!(
return Err(crate::Error::InvalidPath(format!(
"file {} refers to a path outside the temporary directory. This is \
verbotten!",
path.display()
);
)));
}

self.add_to_tmp_dir(path, content)?;
Expand All @@ -142,13 +150,18 @@ impl Zip for ZipCommand {
.arg("output.epub")
.arg("mimetype")
.output()
.wrap_err_with(|| format!("failed to run command {name}", name = self.command))?;
.map_err(|e| {
crate::Error::ZipCommandError(format!(
"failed to run command {name}: {e:?}",
name = self.command
))
})?;
if !output.status.success() {
bail!(
return Err(crate::Error::ZipCommandError(format!(
"command {name} didn't return successfully: {output}",
name = self.command,
output = String::from_utf8_lossy(&output.stderr)
);
)));
}

let mut command = Command::new(&self.command);
Expand All @@ -160,20 +173,30 @@ impl Zip for ZipCommand {
command.arg(format!("{}", file.display()));
}

let output = command
.output()
.wrap_err_with(|| format!("failed to run command {name}", name = self.command))?;
let output = command.output().map_err(|e| {
crate::Error::ZipCommandError(format!(
"failed to run command {name}: {e:?}",
name = self.command
))
})?;
if output.status.success() {
let mut f = File::open(self.temp_dir.path().join("output.epub"))
.wrap_err("error reading temporary epub file")?;
io::copy(&mut f, &mut to).wrap_err("error writing result of the zip command")?;
let mut f = File::open(self.temp_dir.path().join("output.epub")).map_err(|e| {
crate::Error::IoError {
msg: "error reading temporary epub file".to_string(),
cause: e,
}
})?;
io::copy(&mut f, &mut to).map_err(|e| crate::Error::IoError {
msg: "error writing result of the zip command".to_string(),
cause: e,
})?;
Ok(())
} else {
bail!(
Err(crate::Error::ZipCommandError(format!(
"command {name} didn't return successfully: {output}",
name = self.command,
output = String::from_utf8_lossy(&output.stderr)
);
)))
}
}
}
Expand Down
Loading

0 comments on commit 14faaee

Please sign in to comment.