Skip to content

Commit e46c3e0

Browse files
authored
Merge pull request #164 from epage/error
Provide context to errors
2 parents 727d992 + 521ef0e commit e46c3e0

22 files changed

+709
-265
lines changed

src/compiler/block.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use error::Result;
2-
31
use interpreter::Renderable;
2+
use super::Result;
43
use super::Element;
54
use super::LiquidOptions;
65
use super::Token;

src/compiler/include.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::fs::File;
22
use std::io::prelude::Read;
33
use std::path;
44

5-
use error::{Error, Result};
5+
use super::{Error, Result, ResultLiquidChainExt, ResultLiquidExt};
66

77
pub trait Include: Send + Sync + IncludeClone {
88
fn include(&self, path: &str) -> Result<String>;
@@ -38,7 +38,7 @@ impl NullInclude {
3838

3939
impl Include for NullInclude {
4040
fn include(&self, relative_path: &str) -> Result<String> {
41-
Err(Error::from(&*format!("{:?} does not exist", relative_path)))
41+
Err(Error::with_msg("File does not exist").context("path", &relative_path.to_owned()))
4242
}
4343
}
4444

@@ -57,20 +57,33 @@ impl FilesystemInclude {
5757

5858
impl Include for FilesystemInclude {
5959
fn include(&self, relative_path: &str) -> Result<String> {
60-
let root = self.root.canonicalize()?;
60+
let root = self.root
61+
.canonicalize()
62+
.chain("Snippet does not exist")
63+
.context_with(|| {
64+
let key = "non-existent source".into();
65+
let value = self.root.to_string_lossy().into();
66+
(key, value)
67+
})?;
6168
let mut path = root.clone();
6269
path.extend(relative_path.split('/'));
63-
if !path.exists() {
64-
return Err(Error::from(&*format!("{:?} does not exist", path)));
65-
}
66-
let path = path.canonicalize()?;
70+
let path =
71+
path.canonicalize()
72+
.chain("Snippet does not exist")
73+
.context_with(|| ("non-existent path".into(), path.to_string_lossy().into()))?;
6774
if !path.starts_with(&root) {
68-
return Err(Error::from(&*format!("{:?} is outside the include path", path)));
75+
return Err(Error::with_msg("Snippet is outside of source")
76+
.context("source", &root.to_string_lossy())
77+
.context("full path", &path.to_string_lossy()));
6978
}
7079

71-
let mut file = File::open(path)?;
80+
let mut file = File::open(&path)
81+
.chain("Failed to open snippet")
82+
.context_with(|| ("full path".into(), path.to_string_lossy().into()))?;
7283
let mut content = String::new();
73-
file.read_to_string(&mut content)?;
84+
file.read_to_string(&mut content)
85+
.chain("Failed to read snippet")
86+
.context_with(|| ("full path".into(), path.to_string_lossy().into()))?;
7487
Ok(content)
7588
}
7689
}

src/compiler/lexer.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
//! This module contains elements than can be used for writing plugins
44
//! but can be ignored for simple usage.
55
6+
use std::fmt;
7+
68
use regex::Regex;
79

8-
use error::{Error, Result};
10+
use super::{Error, Result};
911

1012
use super::Token;
1113
use super::ComparisonOperator;
@@ -17,6 +19,16 @@ pub enum Element {
1719
Raw(String),
1820
}
1921

22+
impl fmt::Display for Element {
23+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24+
let out = match *self {
25+
Element::Expression(_, ref x) |
26+
Element::Tag(_, ref x) |
27+
Element::Raw(ref x) => x,
28+
};
29+
write!(f, "{}", out)
30+
}
31+
}
2032
lazy_static! {
2133
static ref MARKUP: Regex = {
2234
let t = "(?:[[:space:]]*\\{\\{-|\\{\\{).*?(?:-\\}\\}[[:space:]]*|\\}\\})";
@@ -145,15 +157,15 @@ pub fn granularize(block: &str) -> Result<Vec<Token>> {
145157
x if NUMBER_LITERAL.is_match(x) => {
146158
x.parse::<i32>().map(Token::IntegerLiteral).unwrap_or_else(
147159
|_e| {
148-
Token::FloatLiteral(x.parse::<f32>()
149-
.expect(&format!("Could not parse {:?} as float",
150-
x)))
160+
let x = x.parse::<f32>()
161+
.expect("matches to NUMBER_LITERAL are parseable as floats");
162+
Token::FloatLiteral(x)
151163
},
152164
)
153165
}
154166
x if BOOLEAN_LITERAL.is_match(x) => {
155167
Token::BooleanLiteral(x.parse::<bool>().expect(
156-
&format!("Could not parse {:?} as bool", x),
168+
"matches to BOOLEAN_LITERAL are parseable as bools",
157169
))
158170
}
159171
x if INDEX.is_match(x) => {
@@ -163,7 +175,12 @@ pub fn granularize(block: &str) -> Result<Vec<Token>> {
163175
Token::Dot
164176
}
165177
x if IDENTIFIER.is_match(x) => Token::Identifier(x.to_owned()),
166-
x => return Err(Error::Lexer(format!("{} is not a valid identifier", x))),
178+
x => {
179+
return Err(Error::with_msg("Invalid identifier").context(
180+
"identifier",
181+
&x,
182+
))
183+
}
167184
});
168185
if let Some(v) = push_more {
169186
result.extend(v);

src/compiler/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ mod parser;
66
mod tag;
77
mod token;
88

9+
pub use super::error::{Result, Error, ResultLiquidChainExt, ResultLiquidExt};
10+
911
pub use self::block::{ParseBlock, ParseBlockClone, BoxedBlockParser, FnParseBlock};
1012
pub use self::include::{Include, IncludeClone, NullInclude, FilesystemInclude};
1113
pub use self::lexer::{Element, tokenize};
1214
pub use self::options::LiquidOptions;
13-
pub use self::parser::{parse_output, expect, parse, consume_value_token, split_block, value_token,
14-
parse_indexes};
15+
pub use self::parser::{parse_output, expect, parse, consume_value_token, split_block, BlockSplit,
16+
value_token, parse_indexes, unexpected_token_error};
1517
pub use self::tag::{ParseTag, ParseTagClone, BoxedTagParser, FnParseTag};
1618
pub use self::token::{Token, ComparisonOperator};

src/compiler/parser.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::slice::Iter;
77
use std::collections::HashSet;
88
use std::iter::FromIterator;
99

10-
use error::{Error, Result};
10+
use super::{Error, Result};
1111

1212
use interpreter::Renderable;
1313
use interpreter::Text;
@@ -42,6 +42,18 @@ pub fn parse(elements: &[Element], options: &LiquidOptions) -> Result<Vec<Box<Re
4242
Ok(ret)
4343
}
4444

45+
const NOTHING: Option<&str> = None;
46+
47+
pub fn unexpected_token_error<S: ToString>(expected: &str, actual: Option<S>) -> Error {
48+
let actual = actual.map(|x| x.to_string());
49+
unexpected_token_error_string(expected, actual)
50+
}
51+
52+
pub fn unexpected_token_error_string(expected: &str, actual: Option<String>) -> Error {
53+
let actual = actual.unwrap_or_else(|| "nothing".to_owned());
54+
Error::with_msg(format!("Expected {}, found `{}`", expected, actual))
55+
}
56+
4557
// creates an expression, which wraps everything that gets rendered
4658
fn parse_expression(tokens: &[Token], options: &LiquidOptions) -> Result<Box<Renderable>> {
4759
match tokens.get(0) {
@@ -56,7 +68,7 @@ fn parse_expression(tokens: &[Token], options: &LiquidOptions) -> Result<Box<Ren
5668
Some(&Token::Identifier(ref x)) if options.tags.contains_key(x.as_str()) => {
5769
options.tags[x.as_str()].parse(x, &tokens[1..], options)
5870
}
59-
None => Error::parser("expression", None),
71+
None => Err(unexpected_token_error("expression", NOTHING)),
6072
_ => {
6173
let output = parse_output(tokens)?;
6274
Ok(Box::new(output))
@@ -75,7 +87,7 @@ pub fn parse_indexes(mut tokens: &[Token]) -> Result<Vec<Index>> {
7587
match tokens[1] {
7688
Token::Identifier(ref x) => indexes.push(Index::with_key(x.as_ref())),
7789
_ => {
78-
return Error::parser("identifier", Some(&tokens[0]));
90+
return Err(unexpected_token_error("identifier", Some(&tokens[0])));
7991
}
8092
};
8193
2
@@ -85,13 +97,14 @@ pub fn parse_indexes(mut tokens: &[Token]) -> Result<Vec<Index>> {
8597
Token::StringLiteral(ref x) => Index::with_key(x.as_ref()),
8698
Token::IntegerLiteral(ref x) => Index::with_index(*x as isize),
8799
_ => {
88-
return Error::parser("integer | string", Some(&tokens[0]));
100+
return Err(unexpected_token_error("string | whole number",
101+
Some(&tokens[0])));
89102
}
90103
};
91104
indexes.push(index);
92105

93106
if tokens[2] != Token::CloseSquare {
94-
return Error::parser("]", Some(&tokens[1]));
107+
return Err(unexpected_token_error("`]`", Some(&tokens[0])));
95108
}
96109
3
97110
}
@@ -118,7 +131,7 @@ pub fn parse_output(tokens: &[Token]) -> Result<Output> {
118131
let name = match iter.next() {
119132
Some(&Token::Identifier(ref name)) => name,
120133
x => {
121-
return Error::parser("an identifier", x);
134+
return Err(unexpected_token_error("identifier", x));
122135
}
123136
};
124137
let mut args = vec![];
@@ -147,7 +160,7 @@ pub fn parse_output(tokens: &[Token]) -> Result<Output> {
147160
Some(&&Token::Pipe) |
148161
None => break,
149162
_ => {
150-
return Error::parser("a comma or a pipe", Some(iter.next().unwrap()));
163+
return Err(unexpected_token_error("`,` | `|`", Some(iter.next().unwrap())));
151164
}
152165
}
153166
}
@@ -206,7 +219,7 @@ fn parse_tag(iter: &mut Iter<Element>,
206219
options.blocks[x.as_str()].parse(x, &tokens[1..], &children, options)
207220
}
208221

209-
ref x => Err(Error::Parser(format!("parse_tag: {:?} not implemented", x))),
222+
ref x => Err(Error::with_msg("Tag is not supported").context("tag", x)),
210223
}
211224
}
212225

@@ -217,7 +230,7 @@ pub fn expect<'a, T>(tokens: &mut T, expected: &Token) -> Result<&'a Token>
217230
{
218231
match tokens.next() {
219232
Some(x) if x == expected => Ok(x),
220-
x => Error::parser(&expected.to_string(), x),
233+
x => Err(unexpected_token_error(&format!("`{}`", expected), x)),
221234
}
222235
}
223236

@@ -227,7 +240,7 @@ pub fn expect<'a, T>(tokens: &mut T, expected: &Token) -> Result<&'a Token>
227240
pub fn consume_value_token(tokens: &mut Iter<Token>) -> Result<Token> {
228241
match tokens.next() {
229242
Some(t) => value_token(t.clone()),
230-
None => Error::parser("string | number | boolean | identifier", None),
243+
None => Err(unexpected_token_error("string | number | boolean | identifier", NOTHING)),
231244
}
232245
}
233246

@@ -240,7 +253,7 @@ pub fn value_token(t: Token) -> Result<Token> {
240253
v @ Token::FloatLiteral(_) |
241254
v @ Token::BooleanLiteral(_) |
242255
v @ Token::Identifier(_) => Ok(v),
243-
x => Error::parser("string | number | boolean | identifier", Some(&x)),
256+
x => Err(unexpected_token_error("string | number | boolean | identifier", Some(&x))),
244257
}
245258
}
246259

@@ -323,7 +336,7 @@ mod test_parse_output {
323336

324337
let result = parse_output(&tokens);
325338
assert_eq!(result.unwrap_err().to_string(),
326-
"Parsing error: Expected an identifier, found 1");
339+
"liquid: Expected identifier, found `1`\n");
327340
}
328341

329342
#[test]
@@ -332,7 +345,7 @@ mod test_parse_output {
332345

333346
let result = parse_output(&tokens);
334347
assert_eq!(result.unwrap_err().to_string(),
335-
"Parsing error: Expected a comma or a pipe, found blabla");
348+
"liquid: Expected `,` | `|`, found `blabla`\n");
336349
}
337350

338351
#[test]
@@ -341,7 +354,7 @@ mod test_parse_output {
341354

342355
let result = parse_output(&tokens);
343356
assert_eq!(result.unwrap_err().to_string(),
344-
"Parsing error: Expected :, found 1");
357+
"liquid: Expected `:`, found `1`\n");
345358
}
346359
}
347360

src/compiler/tag.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use error::Result;
1+
use super::Result;
22

33
use interpreter::Renderable;
44
use super::LiquidOptions;

src/compiler/token.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use std::fmt;
22

3-
use error::{Error, Result};
3+
use super::Result;
4+
use super::parser::unexpected_token_error;
45
use value::{Index, Value};
56
use interpreter::Argument;
67
use interpreter::Variable;
78

8-
#[derive(Clone, Debug, PartialEq)]
9+
#[derive(Copy, Clone, Debug, PartialEq)]
910
pub enum ComparisonOperator {
1011
Equals,
1112
NotEquals,
@@ -16,6 +17,20 @@ pub enum ComparisonOperator {
1617
Contains,
1718
}
1819

20+
impl fmt::Display for ComparisonOperator {
21+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22+
let out = match *self {
23+
ComparisonOperator::Equals => "==",
24+
ComparisonOperator::NotEquals => "!=",
25+
ComparisonOperator::LessThanEquals => "<=",
26+
ComparisonOperator::GreaterThanEquals => ">=",
27+
ComparisonOperator::LessThan => "<",
28+
ComparisonOperator::GreaterThan => ">",
29+
ComparisonOperator::Contains => "contains",
30+
};
31+
write!(f, "{}", out)
32+
}
33+
}
1934
#[derive(Clone, Debug, PartialEq)]
2035
pub enum Token {
2136
Pipe,
@@ -49,7 +64,7 @@ impl Token {
4964
&Token::IntegerLiteral(x) => Ok(Value::scalar(x)),
5065
&Token::FloatLiteral(x) => Ok(Value::scalar(x)),
5166
&Token::BooleanLiteral(x) => Ok(Value::scalar(x)),
52-
x => Error::parser("Value", Some(x)),
67+
x => Err(unexpected_token_error("string | number | boolean", Some(x))),
5368
}
5469
}
5570

@@ -66,7 +81,7 @@ impl Token {
6681
var.extend(id.split('.').map(Index::with_key));
6782
Ok(Argument::Var(var))
6883
}
69-
ref x => Error::parser("Argument", Some(x)),
84+
ref x => Err(unexpected_token_error("string | number | boolean | identifier", Some(x))),
7085
}
7186
}
7287
}
@@ -88,13 +103,7 @@ impl fmt::Display for Token {
88103
Token::Assignment => "=".to_owned(),
89104
Token::Or => "or".to_owned(),
90105

91-
Token::Comparison(ComparisonOperator::Equals) => "==".to_owned(),
92-
Token::Comparison(ComparisonOperator::NotEquals) => "!=".to_owned(),
93-
Token::Comparison(ComparisonOperator::LessThanEquals) => "<=".to_owned(),
94-
Token::Comparison(ComparisonOperator::GreaterThanEquals) => ">=".to_owned(),
95-
Token::Comparison(ComparisonOperator::LessThan) => "<".to_owned(),
96-
Token::Comparison(ComparisonOperator::GreaterThan) => ">".to_owned(),
97-
Token::Comparison(ComparisonOperator::Contains) => "contains".to_owned(),
106+
Token::Comparison(ref x) => x.to_string(),
98107
Token::Identifier(ref x) |
99108
Token::StringLiteral(ref x) => x.clone(),
100109
Token::IntegerLiteral(ref x) => x.to_string(),

0 commit comments

Comments
 (0)