Skip to content

Commit

Permalink
feat: replace the use of eyre with our own error type
Browse files Browse the repository at this point in the history
This is done to enable users of our library to react to certain types without resorting to string comparison and parsing to understand what happened.
This is done in accordance to  eyre's docs, who disapprove of leaking the `Report` type in public interfaces.
  • Loading branch information
ololduck committed Sep 26, 2024
1 parent ad5d9de commit fd99d56
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.64"
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 fd99d56

Please sign in to comment.