Skip to content

Commit a06d7f0

Browse files
authored
Merge pull request #618 from zaneduffield/better-parse-errors
Improve path parse error messages
2 parents 8c10215 + 7cf53ec commit a06d7f0

File tree

2 files changed

+103
-9
lines changed

2 files changed

+103
-9
lines changed

src/path/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl FromStr for Expression {
2727
struct ParseError(String);
2828

2929
impl ParseError {
30-
fn new(inner: winnow::error::ContextError) -> Self {
30+
fn new(inner: winnow::error::ParseError<&str, winnow::error::ContextError>) -> Self {
3131
Self(inner.to_string())
3232
}
3333
}

src/path/parser.rs

+102-8
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@ use std::str::FromStr;
22

33
use winnow::ascii::digit1;
44
use winnow::ascii::space0;
5+
use winnow::combinator::cut_err;
56
use winnow::combinator::dispatch;
6-
use winnow::combinator::eof;
77
use winnow::combinator::fail;
88
use winnow::combinator::opt;
99
use winnow::combinator::repeat;
1010
use winnow::combinator::seq;
1111
use winnow::error::ContextError;
12+
use winnow::error::ParseError;
13+
use winnow::error::StrContext;
14+
use winnow::error::StrContextValue;
1215
use winnow::prelude::*;
1316
use winnow::token::any;
1417
use winnow::token::take_while;
1518

1619
use crate::path::Expression;
1720

18-
pub(crate) fn from_str(mut input: &str) -> Result<Expression, ContextError> {
19-
let input = &mut input;
20-
path(input).map_err(|e| e.into_inner().unwrap())
21+
pub(crate) fn from_str(input: &str) -> Result<Expression, ParseError<&str, ContextError>> {
22+
path.parse(input)
2123
}
2224

2325
fn path(i: &mut &str) -> PResult<Expression> {
@@ -31,7 +33,6 @@ fn path(i: &mut &str) -> PResult<Expression> {
3133
},
3234
)
3335
.parse_next(i)?;
34-
eof.parse_next(i)?;
3536
Ok(expr)
3637
}
3738

@@ -41,9 +42,21 @@ fn ident(i: &mut &str) -> PResult<Expression> {
4142

4243
fn postfix(i: &mut &str) -> PResult<Child> {
4344
dispatch! {any;
44-
'[' => seq!(integer.map(Child::Index), _: ']').map(|(i,)| i),
45-
'.' => raw_ident.map(Child::Key),
46-
_ => fail,
45+
'[' => cut_err(
46+
seq!(
47+
integer.map(Child::Index),
48+
_: ']'.context(StrContext::Expected(StrContextValue::CharLiteral(']'))),
49+
)
50+
.map(|(i,)| i)
51+
.context(StrContext::Label("subscript"))
52+
),
53+
'.' => cut_err(raw_ident.map(Child::Key)),
54+
_ => cut_err(
55+
fail
56+
.context(StrContext::Label("postfix"))
57+
.context(StrContext::Expected(StrContextValue::CharLiteral('[')))
58+
.context(StrContext::Expected(StrContextValue::CharLiteral('.')))
59+
),
4760
}
4861
.parse_next(i)
4962
}
@@ -56,6 +69,12 @@ enum Child {
5669
fn raw_ident(i: &mut &str) -> PResult<String> {
5770
take_while(1.., ('a'..='z', 'A'..='Z', '0'..='9', '_', '-'))
5871
.map(ToString::to_string)
72+
.context(StrContext::Label("identifier"))
73+
.context(StrContext::Expected(StrContextValue::Description(
74+
"ASCII alphanumeric",
75+
)))
76+
.context(StrContext::Expected(StrContextValue::CharLiteral('_')))
77+
.context(StrContext::Expected(StrContextValue::CharLiteral('-')))
5978
.parse_next(i)
6079
}
6180

@@ -65,12 +84,17 @@ fn integer(i: &mut &str) -> PResult<isize> {
6584
(opt('-'), digit1).take().try_map(FromStr::from_str),
6685
_: space0
6786
)
87+
.context(StrContext::Expected(StrContextValue::Description(
88+
"integer",
89+
)))
6890
.map(|(i,)| i)
6991
.parse_next(i)
7092
}
7193

7294
#[cfg(test)]
7395
mod test {
96+
use snapbox::{assert_data_eq, str};
97+
7498
use super::Expression::*;
7599
use super::*;
76100

@@ -117,4 +141,74 @@ mod test {
117141

118142
assert_eq!(parsed, expected);
119143
}
144+
145+
#[test]
146+
fn test_invalid_identifier() {
147+
let err = from_str("!").unwrap_err();
148+
assert_data_eq!(
149+
err.to_string(),
150+
str![[r#"
151+
!
152+
^
153+
invalid identifier
154+
expected ASCII alphanumeric, `_`, `-`
155+
"#]]
156+
);
157+
}
158+
159+
#[test]
160+
fn test_invalid_child() {
161+
let err = from_str("a..").unwrap_err();
162+
assert_data_eq!(
163+
err.to_string(),
164+
str![[r#"
165+
a..
166+
^
167+
invalid identifier
168+
expected ASCII alphanumeric, `_`, `-`
169+
"#]]
170+
);
171+
}
172+
173+
#[test]
174+
fn test_invalid_subscript() {
175+
let err = from_str("a[b]").unwrap_err();
176+
assert_data_eq!(
177+
err.to_string(),
178+
str![[r#"
179+
a[b]
180+
^
181+
invalid subscript
182+
expected integer
183+
"#]]
184+
);
185+
}
186+
187+
#[test]
188+
fn test_incomplete_subscript() {
189+
let err = from_str("a[0").unwrap_err();
190+
assert_data_eq!(
191+
err.to_string(),
192+
str![[r#"
193+
a[0
194+
^
195+
invalid subscript
196+
expected `]`
197+
"#]]
198+
);
199+
}
200+
201+
#[test]
202+
fn test_invalid_postfix() {
203+
let err = from_str("a!b").unwrap_err();
204+
assert_data_eq!(
205+
err.to_string(),
206+
str![[r#"
207+
a!b
208+
^
209+
invalid postfix
210+
expected `[`, `.`
211+
"#]]
212+
);
213+
}
120214
}

0 commit comments

Comments
 (0)