Skip to content

Commit 18eedb7

Browse files
authored
export errors and reduce box usage (#67)
* Export Error to library, at the cost that the internal Pairs is now stringified * get rid of Json parsing for boolean and put the PestError in a Box (especially to keep the results small in Ok(_) case) * make `json_path_instance` work without PathInstance, which is a Box<dyn> type and thus needs an additinal vtable for each search
1 parent 3eb0149 commit 18eedb7

File tree

7 files changed

+207
-88
lines changed

7 files changed

+207
-88
lines changed

src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
use crate::parser::model::JsonPath;
104104
use crate::parser::parser::parse_json_path;
105105
use crate::path::json_path_instance;
106+
use parser::errors::JsonPathParserError;
106107
use serde_json::Value;
107108
use std::convert::TryInto;
108109
use std::fmt::Debug;
@@ -150,7 +151,7 @@ extern crate pest;
150151
/// #Note:
151152
/// the result is going to be cloned and therefore it can be significant for the huge queries
152153
pub trait JsonPathQuery {
153-
fn path(self, query: &str) -> Result<Value, String>;
154+
fn path(self, query: &str) -> Result<Value, JsonPathParserError>;
154155
}
155156

156157
#[derive(Clone, Debug)]
@@ -159,7 +160,7 @@ pub struct JsonPathInst {
159160
}
160161

161162
impl FromStr for JsonPathInst {
162-
type Err = String;
163+
type Err = JsonPathParserError;
163164

164165
fn from_str(s: &str) -> Result<Self, Self::Err> {
165166
Ok(JsonPathInst {
@@ -170,6 +171,7 @@ impl FromStr for JsonPathInst {
170171

171172
impl JsonPathInst {
172173
pub fn find_slice<'a>(&'a self, value: &'a Value) -> Vec<JsonPtr<'a, Value>> {
174+
use crate::path::Path;
173175
json_path_instance(&self.inner, value)
174176
.find(JsonPathValue::from_root(value))
175177
.into_iter()
@@ -205,7 +207,7 @@ impl<'a> Deref for JsonPtr<'a, Value> {
205207
}
206208

207209
impl JsonPathQuery for Value {
208-
fn path(self, query: &str) -> Result<Value, String> {
210+
fn path(self, query: &str) -> Result<Value, JsonPathParserError> {
209211
let p = JsonPathInst::from_str(query)?;
210212
Ok(find(&p, &self))
211213
}
@@ -419,6 +421,7 @@ impl<'a, Data> JsonPathValue<'a, Data> {
419421
/// );
420422
/// ```
421423
pub fn find_slice<'a>(path: &'a JsonPathInst, json: &'a Value) -> Vec<JsonPathValue<'a, Value>> {
424+
use crate::path::Path;
422425
let instance = json_path_instance(&path.inner, json);
423426
let res = instance.find(JsonPathValue::from_root(json));
424427
let has_v: Vec<JsonPathValue<'_, Value>> = res.into_iter().filter(|v| v.has_value()).collect();

src/parser/errors.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1-
use pest::iterators::Pairs;
21
use thiserror::Error;
32

43
use super::parser::Rule;
54

65
#[derive(Error, Debug)]
7-
#[allow(clippy::large_enum_variant)]
8-
pub enum JsonPathParserError<'a> {
6+
pub enum JsonPathParserError {
97
#[error("Failed to parse rule: {0}")]
10-
PestError(#[from] pest::error::Error<Rule>),
11-
#[error("Failed to parse JSON: {0}")]
12-
JsonParsingError(#[from] serde_json::Error),
13-
#[error("{0}")]
14-
ParserError(String),
15-
#[error("Unexpected rule {0:?} when trying to parse logic atom: {1:?}")]
16-
UnexpectedRuleLogicError(Rule, Pairs<'a, Rule>),
17-
#[error("Unexpected `none` when trying to parse logic atom: {0:?}")]
18-
UnexpectedNoneLogicError(Pairs<'a, Rule>),
19-
}
20-
21-
pub fn parser_err(cause: &str) -> JsonPathParserError<'_> {
22-
JsonPathParserError::ParserError(format!("Failed to parse JSONPath: {cause}"))
8+
PestError(#[from] Box<pest::error::Error<Rule>>),
9+
#[error("Unexpected rule {0:?} when trying to parse logic atom: {1} within {2}")]
10+
UnexpectedRuleLogicError(Rule, String, String),
11+
#[error("Unexpected `none` when trying to parse logic atom: {0} within {1}")]
12+
UnexpectedNoneLogicError(String, String),
13+
#[error("Pest returned successful parsing but did not produce any output, that should be unreachable due to .pest definition file: SOI ~ chain ~ EOI")]
14+
UnexpectedPestOutput,
15+
#[error("expected a `Rule::path` but found nothing")]
16+
NoRulePath,
17+
#[error("expected a `JsonPath::Descent` but found nothing")]
18+
NoJsonPathDescent,
19+
#[error("expected a `JsonPath::Field` but found nothing")]
20+
NoJsonPathField,
21+
#[error("expected a `f64` or `i64`, but got {0}")]
22+
InvalidNumber(String),
23+
#[error("Invalid toplevel rule for JsonPath: {0:?}")]
24+
InvalidTopLevelRule(Rule),
25+
#[error("Failed to get inner pairs for {0}")]
26+
EmptyInner(String),
2327
}

src/parser/model.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use crate::parse_json_path;
22
use serde_json::Value;
33
use std::convert::TryFrom;
44

5+
use super::errors::JsonPathParserError;
6+
57
/// The basic structures for parsing json paths.
68
/// The common logic of the structures pursues to correspond the internal parsing structure.
79
#[derive(Debug, Clone)]
@@ -35,10 +37,15 @@ impl JsonPath {
3537
}
3638

3739
impl TryFrom<&str> for JsonPath {
38-
type Error = String;
40+
type Error = JsonPathParserError;
3941

42+
/// Parses a string into a [JsonPath].
43+
///
44+
/// # Errors
45+
///
46+
/// Returns a variant of [JsonPathParserError] if the parsing operation failed.
4047
fn try_from(value: &str) -> Result<Self, Self::Error> {
41-
parse_json_path(value).map_err(|e| e.to_string())
48+
parse_json_path(value)
4249
}
4350
}
4451

src/parser/parser.rs

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#![allow(clippy::empty_docs)]
22

3-
use crate::parser::errors::JsonPathParserError::ParserError;
4-
use crate::parser::errors::{parser_err, JsonPathParserError};
3+
use crate::parser::errors::JsonPathParserError;
54
use crate::parser::model::FilterExpression::{And, Not, Or};
65
use crate::parser::model::{
76
FilterExpression, FilterSign, Function, JsonPath, JsonPathIndex, Operand,
@@ -20,9 +19,10 @@ struct JsonPathParser;
2019
///
2120
/// Returns a variant of [JsonPathParserError] if the parsing operation failed.
2221
pub fn parse_json_path(jp_str: &str) -> Result<JsonPath, JsonPathParserError> {
23-
JsonPathParser::parse(Rule::path, jp_str)?
22+
JsonPathParser::parse(Rule::path, jp_str)
23+
.map_err(Box::new)?
2424
.next()
25-
.ok_or(parser_err(jp_str))
25+
.ok_or(JsonPathParserError::UnexpectedPestOutput)
2626
.and_then(parse_internal)
2727
}
2828

@@ -36,7 +36,7 @@ fn parse_internal(rule: Pair<Rule>) -> Result<JsonPath, JsonPathParserError> {
3636
Rule::path => rule
3737
.into_inner()
3838
.next()
39-
.ok_or(parser_err("expected a Rule::path but found nothing"))
39+
.ok_or(JsonPathParserError::NoRulePath)
4040
.and_then(parse_internal),
4141
Rule::current => rule
4242
.into_inner()
@@ -53,14 +53,14 @@ fn parse_internal(rule: Pair<Rule>) -> Result<JsonPath, JsonPathParserError> {
5353
Rule::wildcard => Ok(JsonPath::Wildcard),
5454
Rule::descent => parse_key(down(rule)?)?
5555
.map(JsonPath::Descent)
56-
.ok_or(parser_err("expected a JsonPath::Descent but found nothing")),
56+
.ok_or(JsonPathParserError::NoJsonPathDescent),
5757
Rule::descent_w => Ok(JsonPath::DescentW),
5858
Rule::function => Ok(JsonPath::Fn(Function::Length)),
5959
Rule::field => parse_key(down(rule)?)?
6060
.map(JsonPath::Field)
61-
.ok_or(parser_err("expected a JsonPath::Field but found nothing")),
61+
.ok_or(JsonPathParserError::NoJsonPathField),
6262
Rule::index => parse_index(rule).map(JsonPath::Index),
63-
_ => Err(ParserError(format!("{rule} did not match any 'Rule' "))),
63+
rule => Err(JsonPathParserError::InvalidTopLevelRule(rule)),
6464
}
6565
}
6666

@@ -106,12 +106,17 @@ fn number_to_value(number: &str) -> Result<Value, JsonPathParserError> {
106106
.or_else(|| number.parse::<f64>().ok().map(Value::from))
107107
{
108108
Some(value) => Ok(value),
109-
None => Err(JsonPathParserError::ParserError(format!(
110-
"Failed to parse {number} as either f64 or i64"
111-
))),
109+
None => Err(JsonPathParserError::InvalidNumber(number.to_string())),
112110
}
113111
}
114112

113+
fn bool_to_value(boolean: &str) -> Value {
114+
boolean
115+
.parse::<bool>()
116+
.map(Value::from)
117+
.expect("unreachable: according to .pest this is either `true` or `false`")
118+
}
119+
115120
fn parse_unit_indexes(pairs: Pairs<Rule>) -> Result<JsonPathIndex, JsonPathParserError> {
116121
let mut keys = vec![];
117122

@@ -152,34 +157,40 @@ fn parse_filter_index(pair: Pair<Rule>) -> Result<JsonPathIndex, JsonPathParserE
152157

153158
fn parse_logic_or(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
154159
let mut expr: Option<FilterExpression> = None;
155-
let error_message = format!("Failed to parse logical expression: {:?}", pairs);
156-
for pair in pairs {
160+
// only possible for the loop not to produce any value (except Errors)
161+
if pairs.len() == 0 {
162+
return Err(JsonPathParserError::UnexpectedNoneLogicError(
163+
pairs.get_input().to_string(),
164+
pairs.as_str().to_string(),
165+
));
166+
}
167+
for pair in pairs.into_iter() {
157168
let next_expr = parse_logic_and(pair.into_inner())?;
158169
match expr {
159170
None => expr = Some(next_expr),
160171
Some(e) => expr = Some(Or(Box::new(e), Box::new(next_expr))),
161172
}
162173
}
163-
match expr {
164-
Some(expr) => Ok(expr),
165-
None => Err(JsonPathParserError::ParserError(error_message)),
166-
}
174+
Ok(expr.expect("unreachable: above len() == 0 check should have catched this"))
167175
}
168176

169177
fn parse_logic_and(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
170178
let mut expr: Option<FilterExpression> = None;
171-
let error_message = format!("Failed to parse logical `and` expression: {:?}", pairs,);
179+
// only possible for the loop not to produce any value (except Errors)
180+
if pairs.len() == 0 {
181+
return Err(JsonPathParserError::UnexpectedNoneLogicError(
182+
pairs.get_input().to_string(),
183+
pairs.as_str().to_string(),
184+
));
185+
}
172186
for pair in pairs {
173187
let next_expr = parse_logic_not(pair.into_inner())?;
174188
match expr {
175189
None => expr = Some(next_expr),
176190
Some(e) => expr = Some(And(Box::new(e), Box::new(next_expr))),
177191
}
178192
}
179-
match expr {
180-
Some(expr) => Ok(expr),
181-
None => Err(JsonPathParserError::ParserError(error_message)),
182-
}
193+
Ok(expr.expect("unreachable: above len() == 0 check should have catched this"))
183194
}
184195

185196
fn parse_logic_not(mut pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
@@ -191,10 +202,13 @@ fn parse_logic_not(mut pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathP
191202
.map(|expr|Not(Box::new(expr)))
192203
},
193204
Rule::logic_atom => parse_logic_atom(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()),
194-
x => Err(JsonPathParserError::UnexpectedRuleLogicError(x, pairs)),
205+
rule => Err(JsonPathParserError::UnexpectedRuleLogicError(rule, pairs.get_input().to_string(), pairs.as_str().to_string())),
195206
}
196207
} else {
197-
Err(JsonPathParserError::UnexpectedNoneLogicError(pairs))
208+
Err(JsonPathParserError::UnexpectedNoneLogicError(
209+
pairs.get_input().to_string(),
210+
pairs.as_str().to_string(),
211+
))
198212
}
199213
}
200214

@@ -213,10 +227,13 @@ fn parse_logic_atom(mut pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPath
213227
Ok(FilterExpression::Atom(left, sign, right))
214228
}
215229
}
216-
x => Err(JsonPathParserError::UnexpectedRuleLogicError(x, pairs)),
230+
rule => Err(JsonPathParserError::UnexpectedRuleLogicError(rule, pairs.get_input().to_string(), pairs.as_str().to_string())),
217231
}
218232
} else {
219-
Err(JsonPathParserError::UnexpectedNoneLogicError(pairs))
233+
Err(JsonPathParserError::UnexpectedNoneLogicError(
234+
pairs.get_input().to_string(),
235+
pairs.as_str().to_string(),
236+
))
220237
}
221238
}
222239

@@ -226,7 +243,7 @@ fn parse_atom(rule: Pair<Rule>) -> Result<Operand, JsonPathParserError> {
226243
Rule::number => Operand::Static(number_to_value(rule.as_str())?),
227244
Rule::string_qt => Operand::Static(Value::from(down(atom)?.as_str())),
228245
Rule::chain => parse_chain_in_operand(down(rule)?)?,
229-
Rule::boolean => Operand::Static(rule.as_str().parse::<Value>()?),
246+
Rule::boolean => Operand::Static(bool_to_value(rule.as_str())),
230247
_ => Operand::Static(Value::Null),
231248
};
232249
Ok(parsed_atom)
@@ -246,10 +263,10 @@ fn parse_index(rule: Pair<Rule>) -> Result<JsonPathIndex, JsonPathParserError> {
246263
}
247264

248265
fn down(rule: Pair<Rule>) -> Result<Pair<Rule>, JsonPathParserError> {
249-
let error_message = format!("Failed to get inner pairs for {:?}", rule);
266+
let error_message = rule.to_string();
250267
match rule.into_inner().next() {
251-
Some(rule) => Ok(rule.to_owned()),
252-
None => Err(ParserError(error_message)),
268+
Some(rule) => Ok(rule),
269+
None => Err(JsonPathParserError::EmptyInner(error_message)),
253270
}
254271
}
255272

src/path/index.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crate::JsonPathValue::{NoValue, Slice};
77
use serde_json::value::Value::Array;
88
use serde_json::Value;
99

10+
use super::TopPaths;
11+
1012
/// process the slice like [start:end:step]
1113
#[derive(Debug)]
1214
pub(crate) struct ArraySlice {
@@ -127,7 +129,7 @@ impl<'a> Current<'a> {
127129
pub(crate) fn from(jp: &'a JsonPath, root: &'a Value) -> Self {
128130
match jp {
129131
JsonPath::Empty => Current::none(),
130-
tail => Current::new(json_path_instance(tail, root)),
132+
tail => Current::new(Box::new(json_path_instance(tail, root))),
131133
}
132134
}
133135
pub(crate) fn new(tail: PathInstance<'a>) -> Self {
@@ -151,30 +153,32 @@ impl<'a> Path<'a> for Current<'a> {
151153

152154
/// the list of indexes like [1,2,3]
153155
pub(crate) struct UnionIndex<'a> {
154-
indexes: Vec<PathInstance<'a>>,
156+
indexes: Vec<TopPaths<'a>>,
155157
}
156158

157159
impl<'a> UnionIndex<'a> {
158160
pub fn from_indexes(elems: &'a [Value]) -> Self {
159-
let mut indexes: Vec<PathInstance<'a>> = vec![];
161+
let mut indexes: Vec<TopPaths<'a>> = vec![];
160162

161163
for idx in elems.iter() {
162-
indexes.push(Box::new(ArrayIndex::new(idx.as_u64().unwrap() as usize)))
164+
indexes.push(TopPaths::ArrayIndex(ArrayIndex::new(
165+
idx.as_u64().unwrap() as usize
166+
)))
163167
}
164168

165169
UnionIndex::new(indexes)
166170
}
167171
pub fn from_keys(elems: &'a [String]) -> Self {
168-
let mut indexes: Vec<PathInstance<'a>> = vec![];
172+
let mut indexes: Vec<TopPaths<'a>> = vec![];
169173

170174
for key in elems.iter() {
171-
indexes.push(Box::new(ObjectField::new(key)))
175+
indexes.push(TopPaths::ObjectField(ObjectField::new(key)))
172176
}
173177

174178
UnionIndex::new(indexes)
175179
}
176180

177-
pub fn new(indexes: Vec<PathInstance<'a>>) -> Self {
181+
pub fn new(indexes: Vec<TopPaths<'a>>) -> Self {
178182
UnionIndex { indexes }
179183
}
180184
}
@@ -582,8 +586,8 @@ mod tests {
582586
let chain = chain!(path!($), path!("key"), index);
583587
let path_inst = json_path_instance(&chain, &json);
584588
let expected_res = jp_v![
585-
&exp4;"$.['key'][0]",
586-
&exp3;"$.['key'][2]",
589+
&exp4;"$.['key'][0]",
590+
&exp3;"$.['key'][2]",
587591
&exp4;"$.['key'][4]"];
588592
assert_eq!(
589593
path_inst.find(JsonPathValue::from_root(&json)),

0 commit comments

Comments
 (0)