Skip to content

Commit

Permalink
feat(error): Cloneable errors
Browse files Browse the repository at this point in the history
This is needed for caching rendering results.
  • Loading branch information
epage committed Dec 27, 2018
1 parent 5a0854f commit e18c68e
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 34 deletions.
6 changes: 3 additions & 3 deletions liquid-compiler/src/filter_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::io::Write;

use itertools;

use liquid_error::{Result, ResultLiquidChainExt, ResultLiquidExt};
use liquid_error::{Result, ResultLiquidExt, ResultLiquidReplaceExt};
use liquid_interpreter::Context;
use liquid_interpreter::Expression;
use liquid_interpreter::Renderable;
Expand Down Expand Up @@ -39,7 +39,7 @@ impl FilterCall {
let arguments = arguments?;
self.filter
.filter(entry, &*arguments)
.chain("Filter error")
.trace("Filter error")
.context_key("filter")
.value_with(|| format!("{}", self).into())
.context_key("input")
Expand Down Expand Up @@ -101,7 +101,7 @@ impl fmt::Display for FilterChain {
impl Renderable for FilterChain {
fn render_to(&self, writer: &mut Write, context: &mut Context) -> Result<()> {
let entry = self.evaluate(context)?;
write!(writer, "{}", entry.to_str()).chain("Failed to render")?;
write!(writer, "{}", entry.to_str()).replace("Failed to render")?;
Ok(())
}
}
4 changes: 2 additions & 2 deletions liquid-compiler/src/text.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::io::Write;

use liquid_error::{Result, ResultLiquidChainExt};
use liquid_error::{Result, ResultLiquidReplaceExt};
use liquid_interpreter::Context;
use liquid_interpreter::Renderable;

Expand All @@ -19,7 +19,7 @@ impl Text {

impl Renderable for Text {
fn render_to(&self, writer: &mut Write, _context: &mut Context) -> Result<()> {
write!(writer, "{}", &self.text).chain("Failed to render")?;
write!(writer, "{}", &self.text).replace("Failed to render")?;
Ok(())
}
}
57 changes: 57 additions & 0 deletions liquid-error/src/clone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::error;
use std::fmt;

/// An error that can be cloned.
pub trait ErrorClone: error::Error + Send + Sync + 'static {
/// Clone the error.
fn clone_box(&self) -> Box<ErrorClone>;
}

impl<E> ErrorClone for E
where
E: error::Error + Clone + Send + Sync + 'static,
{
fn clone_box(&self) -> Box<ErrorClone> {
Box::new(self.clone())
}
}

impl Clone for Box<ErrorClone> {
fn clone(&self) -> Box<ErrorClone> {
self.clone_box()
}
}

/// A lossy way to have any error be clonable.
#[derive(Debug, Clone)]
pub struct CloneableError {
error: String,
}

impl CloneableError {
/// Lossily make any error cloneable.
pub fn new<E>(error: E) -> Self
where
E: error::Error + Send + Sync + 'static,
{
Self {
error: error.to_string(),
}
}
}

impl fmt::Display for CloneableError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "{}", self.error)
}
}

impl error::Error for CloneableError {
fn description(&self) -> &str {
self.error.as_str()
}

fn cause(&self) -> Option<&error::Error> {
None
}
}
9 changes: 5 additions & 4 deletions liquid-error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ use std::error;
use std::fmt;
use std::result;

use super::ErrorClone;
use super::Trace;

/// Convenience type alias for Liquid compiler errors
pub type Result<T> = result::Result<T, Error>;

type BoxedError = Box<error::Error + Send + Sync + 'static>;
type BoxedError = Box<ErrorClone>;

/// Compiler error
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Error {
inner: Box<InnerError>,
}
Expand All @@ -20,7 +21,7 @@ pub struct Error {
// `Result<T>` in the success case and spilling over from register-based returns to stack-based
// returns. There are already enough memory allocations below, one more
// shouldn't hurt.
#[derive(Debug)]
#[derive(Debug, Clone)]
struct InnerError {
msg: borrow::Cow<'static, str>,
user_backtrace: Vec<Trace>,
Expand Down Expand Up @@ -83,7 +84,7 @@ impl Error {
}

/// Add an external cause to the error for debugging purposes.
pub fn cause<E: error::Error + Send + Sync + 'static>(self, cause: E) -> Self {
pub fn cause<E: ErrorClone>(self, cause: E) -> Self {
let cause = Box::new(cause);
self.cause_error(cause)
}
Expand Down
2 changes: 2 additions & 0 deletions liquid-error/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
#![warn(missing_debug_implementations)]
#![warn(unused_extern_crates)]

mod clone;
mod error;
mod result_ext;
mod trace;

pub use clone::*;
pub use error::*;
pub use result_ext::*;
use trace::*;
92 changes: 84 additions & 8 deletions liquid-error/src/result_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,100 @@ use std::borrow;
use std::error;
use std::result;

use super::CloneableError;
use super::Error;
use super::ErrorClone;
use super::Result;

type CowStr = borrow::Cow<'static, str>;

/// `Result` extension methods for adapting third party errors to `Error`.
pub trait ResultLiquidChainExt<T> {
/// Create an `Error` with `E` as the cause.
#[must_use]
fn chain<S: Into<CowStr>>(self, msg: S) -> Result<T>;

/// Create an `Error` with `E` as the cause.
#[must_use]
fn chain_with<F>(self, msg: F) -> Result<T>
where
F: FnOnce() -> CowStr;
}

/// `Result` extension methods for adapting third party errors to `Error`.
pub trait ResultLiquidReplaceExt<T> {
/// Create an `Error` ignoring `E` as the cause.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use liquid_error::Result;
/// use liquid_error::ResultLiquidChainExt;
/// use liquid_error::ResultLiquidReplaceExt;
///
/// let error = Err(io::Error::new(io::ErrorKind::NotFound, "Oops"));
/// let error: Result<i32> = error.chain("Missing liquid partial");
/// let error: Result<i32> = error.lossy_chain("Missing liquid partial");
/// ```
#[must_use]
fn chain<S: Into<CowStr>>(self, msg: S) -> Result<T>;
fn lossy_chain<S: Into<CowStr>>(self, msg: S) -> Result<T>;

/// Create an `Error` with `E` as the cause.
/// Create an `Error` ignoring `E` as the cause.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use liquid_error::Result;
/// use liquid_error::ResultLiquidChainExt;
/// use liquid_error::ResultLiquidReplaceExt;
///
/// let filename = "foo";
/// let error = Err(io::Error::new(io::ErrorKind::NotFound, "Oops"));
/// let error: Result<i32> = error
/// .chain_with(|| format!("Missing liquid partial: {}", filename).into());
/// .lossy_chain_with(|| format!("Missing liquid partial: {}", filename).into());
/// ```
#[must_use]
fn chain_with<F>(self, msg: F) -> Result<T>
fn lossy_chain_with<F>(self, msg: F) -> Result<T>
where
F: FnOnce() -> CowStr;

/// Create an `Error` ignoring `E` as the cause.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use liquid_error::Result;
/// use liquid_error::ResultLiquidReplaceExt;
///
/// let error = Err(io::Error::new(io::ErrorKind::NotFound, "Oops"));
/// let error: Result<i32> = error.replace("Missing liquid partial");
/// ```
#[must_use]
fn replace<S: Into<CowStr>>(self, msg: S) -> Result<T>;

/// Create an `Error` ignoring `E` as the cause.
///
/// # Example
///
/// ```rust
/// use std::io;
/// use liquid_error::Result;
/// use liquid_error::ResultLiquidReplaceExt;
///
/// let filename = "foo";
/// let error = Err(io::Error::new(io::ErrorKind::NotFound, "Oops"));
/// let error: Result<i32> = error
/// .replace_with(|| format!("Missing liquid partial: {}", filename).into());
/// ```
#[must_use]
fn replace_with<F>(self, msg: F) -> Result<T>
where
F: FnOnce() -> CowStr;
}

impl<T, E> ResultLiquidChainExt<T> for result::Result<T, E>
where
E: error::Error + Send + Sync + 'static,
E: ErrorClone,
{
fn chain<S: Into<CowStr>>(self, msg: S) -> Result<T> {
self.map_err(|err| Error::with_msg(msg).cause(err))
Expand All @@ -60,6 +109,33 @@ where
}
}

impl<T, E> ResultLiquidReplaceExt<T> for result::Result<T, E>
where
E: error::Error + Send + Sync + 'static,
{
fn lossy_chain<S: Into<CowStr>>(self, msg: S) -> Result<T> {
self.map_err(|err| Error::with_msg(msg).cause(CloneableError::new(err)))
}

fn lossy_chain_with<F>(self, msg: F) -> Result<T>
where
F: FnOnce() -> CowStr,
{
self.map_err(|err| Error::with_msg(msg()).cause(CloneableError::new(err)))
}

fn replace<S: Into<CowStr>>(self, msg: S) -> Result<T> {
self.map_err(|_| Error::with_msg(msg))
}

fn replace_with<F>(self, msg: F) -> Result<T>
where
F: FnOnce() -> CowStr,
{
self.map_err(|_| Error::with_msg(msg()))
}
}

/// Add context to a `liquid_error::Error`.
pub trait ResultLiquidExt<T>
where
Expand Down
6 changes: 3 additions & 3 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path;
use std::sync;

use liquid_compiler as compiler;
use liquid_error::{Result, ResultLiquidChainExt, ResultLiquidExt};
use liquid_error::{Result, ResultLiquidExt, ResultLiquidReplaceExt};
use liquid_interpreter as interpreter;

use super::Template;
Expand Down Expand Up @@ -307,12 +307,12 @@ impl Parser {

fn parse_file_path(self, file: &path::Path) -> Result<Template> {
let mut f = File::open(file)
.chain("Cannot open file")
.replace("Cannot open file")
.context_key("path")
.value_with(|| file.to_string_lossy().into_owned().into())?;
let mut buf = String::new();
f.read_to_string(&mut buf)
.chain("Cannot read file")
.replace("Cannot read file")
.context_key("path")
.value_with(|| file.to_string_lossy().into_owned().into())?;

Expand Down
4 changes: 2 additions & 2 deletions src/tags/cycle_tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::io::Write;

use itertools;
use liquid_error::{Error, Result, ResultLiquidChainExt, ResultLiquidExt};
use liquid_error::{Error, Result, ResultLiquidExt, ResultLiquidReplaceExt};

use compiler::Language;
use compiler::TagToken;
Expand Down Expand Up @@ -34,7 +34,7 @@ impl Renderable for Cycle {
.cycle(&self.name, &self.values)
.trace_with(|| self.trace().into())?;
let value = expr.evaluate(context).trace_with(|| self.trace().into())?;
write!(writer, "{}", value.render()).chain("Failed to render")?;
write!(writer, "{}", value.render()).replace("Failed to render")?;
Ok(())
}
}
Expand Down
11 changes: 6 additions & 5 deletions src/tags/for_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fmt;
use std::io::Write;

use itertools;
use liquid_error::{Error, Result, ResultLiquidChainExt, ResultLiquidExt};
use liquid_error::{Error, Result, ResultLiquidExt, ResultLiquidReplaceExt};
use liquid_value::{Object, Scalar, Value};

use compiler::BlockElement;
Expand Down Expand Up @@ -403,9 +403,10 @@ impl Renderable for TableRow {

if col_first {
write!(writer, "<tr class=\"row{}\">", row_index + 1)
.chain("Failed to render")?;
.replace("Failed to render")?;
}
write!(writer, "<td class=\"col{}\">", col_index + 1).chain("Failed to render")?;
write!(writer, "<td class=\"col{}\">", col_index + 1)
.replace("Failed to render")?;

scope.stack_mut().set(self.var_name.to_owned(), v);
self.item_template
Expand All @@ -414,9 +415,9 @@ impl Renderable for TableRow {
.context_key("index")
.value_with(|| format!("{}", i + 1).into())?;

write!(writer, "</td>").chain("Failed to render")?;
write!(writer, "</td>").replace("Failed to render")?;
if col_last {
write!(writer, "</tr>").chain("Failed to render")?;
write!(writer, "</tr>").replace("Failed to render")?;
}
}
Ok(())
Expand Down
4 changes: 2 additions & 2 deletions src/tags/ifchanged_block.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::io::Write;

use liquid_error::{Result, ResultLiquidChainExt, ResultLiquidExt};
use liquid_error::{Result, ResultLiquidExt, ResultLiquidReplaceExt};

use compiler::Language;
use compiler::TagBlock;
Expand Down Expand Up @@ -29,7 +29,7 @@ impl Renderable for IfChanged {

let rendered = String::from_utf8(rendered).expect("render only writes UTF-8");
if context.get_register_mut::<State>().has_changed(&rendered) {
write!(writer, "{}", rendered).chain("Failed to render")?;
write!(writer, "{}", rendered).replace("Failed to render")?;
}

Ok(())
Expand Down

0 comments on commit e18c68e

Please sign in to comment.