diff --git a/packages/hurl_core/src/error/mod.rs b/packages/hurl_core/src/error/mod.rs index fb5bdf1c11d..cf25e8b3c6a 100644 --- a/packages/hurl_core/src/error/mod.rs +++ b/packages/hurl_core/src/error/mod.rs @@ -18,8 +18,8 @@ use core::cmp; use crate::ast::SourceInfo; -use crate::parser; use crate::parser::ParseError; +use crate::parser::{self, JsonErrorVariant}; pub trait Error { fn source_info(&self) -> SourceInfo; @@ -48,10 +48,7 @@ impl Error for parser::Error { ParseError::JsonPathExpr => "Parsing JSONPath expression".to_string(), ParseError::XPathExpr => "Parsing XPath expression".to_string(), ParseError::TemplateVariable => "Parsing template variable".to_string(), - ParseError::Json => "Parsing JSON".to_string(), - ParseError::UnexpectedInJson { .. } => "Parsing JSON".to_string(), - ParseError::ExpectedAnElementInJson => "Parsing JSON".to_string(), - ParseError::UnclosedBraceInJson => "Parsing JSON".to_string(), + ParseError::Json(_) => "Parsing JSON".to_string(), ParseError::Predicate => "Parsing predicate".to_string(), ParseError::PredicateValue => "Parsing predicate value".to_string(), ParseError::RegexExpr { .. } => "Parsing regex".to_string(), @@ -97,7 +94,16 @@ impl Error for parser::Error { ParseError::JsonPathExpr => "expecting a JSONPath expression".to_string(), ParseError::XPathExpr => "expecting a XPath expression".to_string(), ParseError::TemplateVariable => "expecting a variable".to_string(), - ParseError::Json => "JSON error".to_string(), + ParseError::Json(variant) => { + match variant { + JsonErrorVariant::UnexpectedCharcter { character } => format!("unexpected character: '{character}'"), + JsonErrorVariant::EmptyElement => "expecting an element; found empty element instead".to_string(), + JsonErrorVariant::UnclosedBrace => "this brace is not closed later".to_string(), + JsonErrorVariant::CannotResolve { name } => { + format!("failed to resolve '{name}'. If it's a variable, try enclosing it with double braces, e.g. {{{{{name}}}}}") + } + } + } ParseError::Predicate => "expecting a predicate".to_string(), ParseError::PredicateValue => "invalid predicate value".to_string(), ParseError::RegexExpr { message } => format!("invalid Regex expression: {message}"), @@ -119,9 +125,6 @@ impl Error for parser::Error { ParseError::UrlInvalidStart => "expecting http://, https:// or {{".to_string(), ParseError::Multiline => "the multiline is not valid".to_string(), ParseError::GraphQlVariables => "GraphQL variables is not a valid JSON object".to_string(), - ParseError::UnexpectedInJson { character } => format!("unexpected character: '{character}'"), - ParseError::ExpectedAnElementInJson => "expecting an element; found empty element instead".to_string(), - ParseError::UnclosedBraceInJson => "this brace is not closed later".to_string(), _ => format!("{self:?}"), } diff --git a/packages/hurl_core/src/parser/bytes.rs b/packages/hurl_core/src/parser/bytes.rs index 4be14c5eb92..98a4f720bf8 100644 --- a/packages/hurl_core/src/parser/bytes.rs +++ b/packages/hurl_core/src/parser/bytes.rs @@ -149,7 +149,10 @@ mod tests { let mut reader = Reader::new("{ x "); let error = bytes(&mut reader).err().unwrap(); assert_eq!(error.pos, Pos { line: 1, column: 1 }); - assert_eq!(error.inner, ParseError::UnclosedBraceInJson); + assert_eq!( + error.inner, + ParseError::Json(JsonErrorVariant::UnclosedBrace) + ); } #[test] diff --git a/packages/hurl_core/src/parser/error.rs b/packages/hurl_core/src/parser/error.rs index 7ae1736bf80..67176c0282a 100644 --- a/packages/hurl_core/src/parser/error.rs +++ b/packages/hurl_core/src/parser/error.rs @@ -39,15 +39,12 @@ pub enum ParseError { JsonPathExpr, XPathExpr, TemplateVariable, - Json, + Json(JsonErrorVariant), Xml, Predicate, PredicateValue, RegexExpr { message: String }, - ExpectedAnElementInJson, - UnexpectedInJson { character: String }, - UnclosedBraceInJson, Eof, Url, @@ -68,6 +65,14 @@ pub enum ParseError { GraphQlVariables, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum JsonErrorVariant { + UnexpectedCharcter { character: String }, + CannotResolve { name: String }, + EmptyElement, + UnclosedBrace, +} + impl Error { pub fn recoverable(&self) -> Error { Error { diff --git a/packages/hurl_core/src/parser/json.rs b/packages/hurl_core/src/parser/json.rs index 5d9c011b175..8ab9b1d2126 100644 --- a/packages/hurl_core/src/parser/json.rs +++ b/packages/hurl_core/src/parser/json.rs @@ -38,6 +38,46 @@ pub fn parse(reader: &mut Reader) -> ParseResult { ) } +/// Helper for parse, but already knowing that we are inside a JSON body. +fn parse_in_json(reader: &mut Reader) -> ParseResult { + if let Some(c) = reader.peek() { + if c == ',' { + return Err(Error { + pos: reader.state.pos.clone(), + recoverable: false, + inner: error::ParseError::Json(JsonErrorVariant::EmptyElement), + }); + } + } + match parse(reader) { + Ok(r) => Ok(r), + // The only error that is recoverable is caused by reaching object_value try_literal('{'), + // but this is not recoverable in this case, because we already know that we are in a JSON + // body. So, we change the error to CannotResolve for the object found. + Err(e) => match e { + Error { + recoverable: true, .. + } => { + return Err(error::Error { + pos: e.pos, + recoverable: false, + inner: error::ParseError::Json(JsonErrorVariant::CannotResolve { + name: reader + .read_while(|c| !c.is_whitespace() && !c.is_ascii_punctuation()), + }), + }) + } + _ => { + return Err(Error { + recoverable: false, + pos: e.pos, + inner: e.inner, + }) + } + }, + } +} + pub fn null_value(reader: &mut Reader) -> ParseResult { try_literal("null", reader)?; Ok(JsonValue::Null) @@ -264,9 +304,9 @@ fn list_value(reader: &mut Reader) -> ParseResult { return Err(Error { pos: save, recoverable: false, - inner: ParseError::UnexpectedInJson { + inner: ParseError::Json(JsonErrorVariant::UnexpectedCharcter { character: ','.to_string(), - }, + }), }); } let element = list_element(reader)?; @@ -280,19 +320,7 @@ fn list_value(reader: &mut Reader) -> ParseResult { fn list_element(reader: &mut Reader) -> ParseResult { let space0 = whitespace(reader); - let value = match parse(reader) { - Ok(r) => r, - Err(e) => { - return Err(Error { - // Recoverable must be set to false, else the Body parser may think this is not a - // JSON body, because the JSON parser can fail in object_value try_literal('{'), - // and try_literal is marked as recoverable. - recoverable: false, - pos: e.pos, - inner: e.inner, - }); - } - }; + let value = parse_in_json(reader)?; let space1 = whitespace(reader); Ok(JsonListElement { space0, @@ -330,9 +358,9 @@ pub fn object_value(reader: &mut Reader) -> ParseResult { return Err(Error { pos: save, recoverable: false, - inner: ParseError::UnexpectedInJson { + inner: ParseError::Json(JsonErrorVariant::UnexpectedCharcter { character: ','.to_string(), - }, + }), }); } let element = object_element(reader)?; @@ -354,7 +382,7 @@ fn key(reader: &mut Reader) -> ParseResult