Add function in expression

This commit is contained in:
Fabrice Reix 2024-11-19 12:53:20 +01:00 committed by hurl-bot
parent aae693d698
commit 6f28ab19f6
No known key found for this signature in database
GPG Key ID: 1283A2B4A0DCAF8D
27 changed files with 297 additions and 137 deletions

View File

@ -1,4 +1,4 @@
error: Invalid variable type
error: Invalid expression type
--> tests_failed/options_template.hurl:8:13
|
| GET http://localhost:8000/unused
@ -7,7 +7,7 @@ error: Invalid variable type
| ^^^^^^^^ expecting boolean, actual value is integer <10>
|
error: Invalid variable type
error: Invalid expression type
--> tests_failed/options_template.hurl:15:15
|
| GET http://localhost:8000/unused
@ -16,7 +16,7 @@ error: Invalid variable type
| ^^^^^^^^^^^^ expecting integer >= -1, actual value is integer <-123>
|
error: Invalid variable type
error: Invalid expression type
--> tests_failed/options_template.hurl:22:15
|
| GET http://localhost:8000/unused
@ -25,7 +25,7 @@ error: Invalid variable type
| ^^^^^^^^^^^^ expecting integer, actual value is string <abc>
|
error: Invalid variable type
error: Invalid expression type
--> tests_failed/options_template.hurl:29:11
|
| GET http://localhost:8000/unused
@ -34,7 +34,7 @@ error: Invalid variable type
| ^^^^^ expecting integer >= -1, actual value is integer <-2>
|
error: Invalid variable type
error: Invalid expression type
--> tests_failed/options_template.hurl:36:11
|
| GET http://localhost:8000/unused
@ -43,7 +43,7 @@ error: Invalid variable type
| ^^^^^ expecting integer, actual value is string <foo>
|
error: Invalid variable type
error: Invalid expression type
--> tests_failed/options_template.hurl:43:10
|
| GET http://localhost:8000/unused
@ -52,7 +52,7 @@ error: Invalid variable type
| ^^^^^ expecting integer >= -1, actual value is integer <-2>
|
error: Invalid variable type
error: Invalid expression type
--> tests_failed/options_template.hurl:50:10
|
| GET http://localhost:8000/unused

View File

@ -215,7 +215,7 @@ error: Invalid XPath expression
| ^^^^ the XPath expression is not valid
|
error: Invalid variable type
error: Invalid expression type
--> tests_failed/runner_errors.hurl:142:12
|
| GET http://localhost:8000/runner_errors

View File

@ -215,7 +215,7 @@
 | ^^^^ the XPath expression is not valid
 |
error: Invalid variable type
error: Invalid expression type
--> tests_failed/runner_errors.hurl:142:12
 |
 | GET http://localhost:8000/runner_errors

View File

@ -71,6 +71,10 @@ pub enum RunnerErrorKind {
AssertVersion {
actual: String,
},
ExpressionInvalidType {
value: String,
expecting: String,
},
/// I/O read error on `path`.
FileReadAccess {
path: PathBuf,
@ -104,11 +108,7 @@ pub enum RunnerErrorKind {
TemplateVariableNotDefined {
name: String,
},
TemplateVariableInvalidType {
name: String,
value: String,
expecting: String,
},
UnrenderableVariable {
name: String,
value: String,
@ -133,7 +133,7 @@ impl DisplaySourceError for RunnerError {
RunnerErrorKind::AssertHeaderValueError { .. } => "Assert header value".to_string(),
RunnerErrorKind::AssertStatus { .. } => "Assert status code".to_string(),
RunnerErrorKind::AssertVersion { .. } => "Assert HTTP version".to_string(),
RunnerErrorKind::ExpressionInvalidType { .. } => "Invalid expression type".to_string(),
RunnerErrorKind::FileReadAccess { .. } => "File read access".to_string(),
RunnerErrorKind::FileWriteAccess { .. } => "File write access".to_string(),
RunnerErrorKind::FilterDecode { .. } => "Filter error".to_string(),
@ -152,9 +152,7 @@ impl DisplaySourceError for RunnerError {
}
RunnerErrorKind::QueryInvalidXml => "Invalid XML".to_string(),
RunnerErrorKind::QueryInvalidXpathEval => "Invalid XPath expression".to_string(),
RunnerErrorKind::TemplateVariableInvalidType { .. } => {
"Invalid variable type".to_string()
}
RunnerErrorKind::TemplateVariableNotDefined { .. } => "Undefined variable".to_string(),
RunnerErrorKind::UnauthorizedFileAccess { .. } => {
"Unauthorized file access".to_string()
@ -208,7 +206,13 @@ impl DisplaySourceError for RunnerError {
let message = error::add_carets(message, self.source_info, content);
color_red_multiline_string(&message)
}
RunnerErrorKind::ExpressionInvalidType {
value, expecting, ..
} => {
let message = &format!("expecting {expecting}, actual value is {value}");
let message = error::add_carets(message, self.source_info, content);
color_red_multiline_string(&message)
}
RunnerErrorKind::FileReadAccess { path } => {
let message = &format!("file {} can not be read", path.to_string_lossy());
let message = error::add_carets(message, self.source_info, content);
@ -289,13 +293,6 @@ impl DisplaySourceError for RunnerError {
let message = error::add_carets(message, self.source_info, content);
color_red_multiline_string(&message)
}
RunnerErrorKind::TemplateVariableInvalidType {
value, expecting, ..
} => {
let message = &format!("expecting {expecting}, actual value is {value}");
let message = error::add_carets(message, self.source_info, content);
color_red_multiline_string(&message)
}
RunnerErrorKind::TemplateVariableNotDefined { name } => {
let message = &format!("you must set the variable {name}");
let message = error::add_carets(message, self.source_info, content);

View File

@ -15,7 +15,7 @@
* limitations under the License.
*
*/
use hurl_core::ast::Expr;
use hurl_core::ast::{Expr, ExprKind};
use crate::runner::error::{RunnerError, RunnerErrorKind};
use crate::runner::value::Value;
@ -23,29 +23,39 @@ use crate::runner::VariableSet;
/// Evaluates the expression `expr` with `variables` map, returns a [`Value`] on success or an [`RunnerError`] .
pub fn eval(expr: &Expr, variables: &VariableSet) -> Result<Value, RunnerError> {
if let Some(value) = variables.get(expr.variable.name.as_str()) {
Ok(value.clone())
} else {
let kind = RunnerErrorKind::TemplateVariableNotDefined {
name: expr.variable.name.clone(),
};
Err(RunnerError::new(expr.variable.source_info, kind, false))
match &expr.kind {
ExprKind::Variable(variable) => {
if let Some(value) = variables.get(variable.name.as_str()) {
Ok(value.clone())
} else {
let kind = RunnerErrorKind::TemplateVariableNotDefined {
name: variable.name.clone(),
};
Err(RunnerError::new(variable.source_info, kind, false))
}
}
ExprKind::Function(_function) => todo!(),
}
}
/// Render the expression `expr` with `variables` map, returns a [`String`] on success or an [`RunnerError`] .
pub fn render(expr: &Expr, variables: &VariableSet) -> Result<String, RunnerError> {
let source_info = expr.variable.source_info;
let name = &expr.variable.name;
let value = eval(expr, variables)?;
if value.is_renderable() {
Ok(value.to_string())
} else {
let kind = RunnerErrorKind::UnrenderableVariable {
name: name.to_string(),
value: value.to_string(),
};
Err(RunnerError::new(source_info, kind, false))
match &expr.kind {
ExprKind::Variable(variable) => {
let source_info = variable.source_info;
let name = &variable.name;
let value = eval(expr, variables)?;
if value.is_renderable() {
Ok(value.to_string())
} else {
let kind = RunnerErrorKind::UnrenderableVariable {
name: name.to_string(),
value: value.to_string(),
};
Err(RunnerError::new(source_info, kind, false))
}
}
ExprKind::Function(_) => todo!(),
}
}
@ -53,7 +63,7 @@ pub fn render(expr: &Expr, variables: &VariableSet) -> Result<String, RunnerErro
mod tests {
use super::*;
use hurl_core::{
ast::{SourceInfo, Variable},
ast::{ExprKind, SourceInfo, Variable},
reader::Pos,
};
@ -62,10 +72,11 @@ mod tests {
let mut variables = VariableSet::new();
variables.insert("status".to_string(), Value::Bool(true));
let expr = Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "status".to_string(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
}),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
};
assert_eq!(eval(&expr, &variables).unwrap(), Value::Bool(true));
assert_eq!(render(&expr, &variables).unwrap(), "true");

View File

@ -83,7 +83,7 @@ pub fn eval_json_value(
return Ok(s);
}
let kind = RunnerErrorKind::InvalidJson { value: s };
Err(RunnerError::new(expr.variable.source_info, kind, false))
Err(RunnerError::new(expr.source_info, kind, false))
}
}
}
@ -198,10 +198,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 15)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 19)),
},
}),
source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 19)),
},
space1: Whitespace {
value: String::new(),
@ -442,10 +443,11 @@ mod tests {
TemplateElement::Placeholder(Placeholder {
space0: whitespace(),
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "quote".to_string(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
}),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
space1: whitespace(),
}),

View File

@ -321,12 +321,11 @@ fn eval_boolean_option(
match expr::eval(expr, variables)? {
Value::Bool(value) => Ok(value),
v => {
let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(),
let kind = RunnerErrorKind::ExpressionInvalidType {
value: v.format(),
expecting: "boolean".to_string(),
};
Err(RunnerError::new(expr.variable.source_info, kind, false))
Err(RunnerError::new(expr.source_info, kind, false))
}
}
}
@ -345,21 +344,19 @@ fn eval_natural_option(
if value > 0 {
Ok(value as u64)
} else {
let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(),
let kind = RunnerErrorKind::ExpressionInvalidType {
value: format!("integer <{value}>"),
expecting: "integer > 0".to_string(),
};
Err(RunnerError::new(expr.variable.source_info, kind, false))
Err(RunnerError::new(expr.source_info, kind, false))
}
}
v => {
let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(),
let kind = RunnerErrorKind::ExpressionInvalidType {
value: v.format(),
expecting: "integer".to_string(),
};
Err(RunnerError::new(expr.variable.source_info, kind, false))
Err(RunnerError::new(expr.source_info, kind, false))
}
}
}
@ -379,21 +376,19 @@ fn eval_count_option(
} else if value >= 0 {
Ok(Count::Finite(value as usize))
} else {
let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(),
let kind = RunnerErrorKind::ExpressionInvalidType {
value: format!("integer <{value}>"),
expecting: "integer >= -1".to_string(),
};
Err(RunnerError::new(expr.variable.source_info, kind, false))
Err(RunnerError::new(expr.source_info, kind, false))
}
}
v => {
let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(),
let kind = RunnerErrorKind::ExpressionInvalidType {
value: v.format(),
expecting: "integer".to_string(),
};
Err(RunnerError::new(expr.variable.source_info, kind, false))
Err(RunnerError::new(expr.source_info, kind, false))
}
},
}
@ -419,12 +414,11 @@ fn eval_duration_option(
{
Value::Number(Number::Integer(value)) => {
if value < 0 {
let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(),
let kind = RunnerErrorKind::ExpressionInvalidType {
value: format!("integer <{value}>"),
expecting: "positive integer".to_string(),
};
return Err(RunnerError::new(expr.variable.source_info, kind, false));
return Err(RunnerError::new(expr.source_info, kind, false));
} else {
match default_unit {
DurationUnit::MilliSecond => value as u64,
@ -434,12 +428,11 @@ fn eval_duration_option(
}
}
v => {
let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(),
let kind = RunnerErrorKind::ExpressionInvalidType {
value: v.format(),
expecting: "positive integer".to_string(),
};
return Err(RunnerError::new(expr.variable.source_info, kind, false));
return Err(RunnerError::new(expr.source_info, kind, false));
}
},
};
@ -471,7 +464,7 @@ fn eval_number(number: &AstNumber) -> Value {
#[cfg(test)]
mod tests {
use hurl_core::ast::{Expr, Placeholder, SourceInfo, Variable, Whitespace};
use hurl_core::ast::{Expr, ExprKind, Placeholder, SourceInfo, Variable, Whitespace};
use hurl_core::reader::Pos;
use hurl_core::typing::{Duration, DurationUnit};
@ -486,10 +479,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "verbose".to_string(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
}),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
space1: Whitespace {
value: String::new(),
@ -506,10 +500,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "retry".to_string(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
}),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
space1: Whitespace {
value: String::new(),
@ -547,8 +542,7 @@ mod tests {
.unwrap();
assert_eq!(
error.kind,
RunnerErrorKind::TemplateVariableInvalidType {
name: "verbose".to_string(),
RunnerErrorKind::ExpressionInvalidType {
value: "integer <10>".to_string(),
expecting: "boolean".to_string()
}

View File

@ -1257,10 +1257,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 11)),
},
expr: Expr {
variable: Variable {
name: String::from("base_url"),
kind: ExprKind::Variable(Variable {
name: "base_url".to_string(),
source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 19)),
},
}),
source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 19)),
},
space1: Whitespace {
value: String::new(),

View File

@ -220,10 +220,11 @@ mod tests {
TemplateElement::Placeholder(Placeholder {
space0: whitespace(),
expr: Expr {
variable: Variable {
name: String::from("base_url"),
kind: ExprKind::Variable(Variable {
name: "base_url".to_string(),
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 15)),
},
}),
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 15)),
},
space1: whitespace(),
}),
@ -301,13 +302,17 @@ mod tests {
elements: vec![TemplateElement::Placeholder(Placeholder {
space0: whitespace(),
expr: Expr {
variable: Variable {
name: String::from("param1"),
kind: ExprKind::Variable(Variable {
name: "param1".to_string(),
source_info: SourceInfo::new(
Pos::new(1, 7),
Pos::new(1, 15),
),
},
}),
source_info: SourceInfo::new(
Pos::new(1, 7),
Pos::new(1, 15),
),
},
space1: whitespace(),
})],

View File

@ -69,14 +69,15 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 3)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 7)),
},
}),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 7)),
},
space1: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 7)),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 7)),
},
})
}

View File

@ -701,7 +701,14 @@ pub struct Placeholder {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Expr {
pub variable: Variable,
pub source_info: SourceInfo,
pub kind: ExprKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExprKind {
Variable(Variable),
Function(Function),
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -710,6 +717,11 @@ pub struct Variable {
pub source_info: SourceInfo,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Function {
NewUuid,
}
/// Check that variable name is not reserved
/// (would conflicts with an existing function)
pub fn is_variable_reserved(name: &str) -> bool {

View File

@ -104,7 +104,16 @@ impl fmt::Display for Placeholder {
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.variable)
write!(f, "{}", self.kind)
}
}
impl fmt::Display for ExprKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ExprKind::Variable(variable) => write!(f, "{}", variable),
ExprKind::Function(function) => write!(f, "{}", function),
}
}
}
@ -114,6 +123,14 @@ impl fmt::Display for Variable {
}
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Function::NewUuid => write!(f, "newUuid"),
}
}
}
impl fmt::Display for CookiePath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut buf = self.name.to_string();
@ -323,10 +340,11 @@ mod tests {
Placeholder {
space0: whitespace(),
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
}),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
space1: whitespace(),
}

View File

@ -216,7 +216,7 @@ impl TemplateElement {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Expr, SourceInfo, TemplateElement, Variable, Whitespace};
use crate::ast::{Expr, ExprKind, SourceInfo, TemplateElement, Variable, Whitespace};
use crate::reader::Pos;
#[test]
@ -229,10 +229,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "x".to_string(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
},
space1: Whitespace {
value: String::new(),
@ -329,10 +330,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
},
space1: Whitespace {
value: String::new(),
@ -351,10 +353,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
},
space1: Whitespace {
value: String::new(),

View File

@ -138,10 +138,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 3)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 7)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 7)),
},
space1: Whitespace {
value: String::new(),

View File

@ -17,15 +17,31 @@
*/
use crate::ast::*;
use crate::parser::error::*;
use crate::parser::function;
use crate::parser::ParseResult;
use crate::reader::Reader;
/// Parse an expression
///
/// Currently, an expression can only be found inside a template.
/// Currently, an expression can only be found inside a placeholder
pub fn parse(reader: &mut Reader) -> ParseResult<Expr> {
let variable = variable_name(reader)?;
Ok(Expr { variable })
let start = reader.cursor().pos;
let save_state = reader.cursor();
let kind = match function::parse(reader) {
Ok(function) => ExprKind::Function(function),
Err(e) => {
if e.recoverable {
reader.seek(save_state);
let variable = variable_name(reader)?;
ExprKind::Variable(variable)
} else {
return Err(e);
}
}
};
let end = reader.cursor().pos;
let source_info = SourceInfo::new(start, end);
Ok(Expr { source_info, kind })
}
fn variable_name(reader: &mut Reader) -> ParseResult<Variable> {

View File

@ -248,10 +248,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 7)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "bar".to_string(),
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 10)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 10)),
},
space1: Whitespace {
value: String::new(),
@ -279,10 +280,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 7)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "bar".to_string(),
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 10)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 10)),
},
space1: Whitespace {
value: String::new(),

View File

@ -0,0 +1,60 @@
/*
* Hurl (https://hurl.dev)
* Copyright (C) 2024 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use crate::ast::*;
use crate::parser::error::*;
use crate::parser::ParseResult;
use crate::reader::Reader;
/// Parse a function
///
pub fn parse(reader: &mut Reader) -> ParseResult<Function> {
let start = reader.cursor();
let function_name = reader.read_while(|c| c.is_alphanumeric() || c == '_' || c == '-');
match function_name.as_str() {
"newUuid" => Ok(Function::NewUuid),
_ => Err(ParseError::new(
start.pos,
true,
ParseErrorKind::Expecting {
value: "function".to_string(),
},
)),
}
}
#[cfg(test)]
mod tests {
use crate::reader::Pos;
use super::*;
#[test]
fn test_exist() {
let mut reader = Reader::new("newUuid");
assert_eq!(parse(&mut reader).unwrap(), Function::NewUuid);
}
#[test]
fn test_not_exist() {
let mut reader = Reader::new("name");
let err = parse(&mut reader).unwrap_err();
assert_eq!(err.pos, Pos::new(1, 1));
assert_eq!(err.recoverable, true);
}
}

View File

@ -486,10 +486,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 15)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 19)),
},
}),
source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 19)),
},
space1: Whitespace {
value: String::new(),
@ -782,10 +783,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 3))
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "n".to_string(),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 4))
}
}),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 4))
},
space1: Whitespace {
value: String::new(),

View File

@ -25,9 +25,9 @@ use super::placeholder;
pub fn parse(reader: &mut Reader) -> ParseResult<Template> {
let start = reader.cursor();
let mut elements = vec![];
loop {
let save_state = reader.cursor();
match placeholder::parse(reader) {
Ok(placeholder) => {
let element = TemplateElement::Placeholder(placeholder);
@ -35,6 +35,7 @@ pub fn parse(reader: &mut Reader) -> ParseResult<Template> {
}
Err(e) => {
if e.recoverable {
reader.seek(save_state);
let value = key_string_content(reader)?;
if value.is_empty() {
break;

View File

@ -40,6 +40,7 @@ mod expr;
mod filename;
mod filename_password;
mod filter;
mod function;
mod json;
mod key_string;
mod multiline;

View File

@ -50,10 +50,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 4)),
},
expr: Expr {
variable: Variable {
name: String::from("name"),
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 8)),
},
}),
source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 8)),
},
space1: Whitespace {
value: String::new(),

View File

@ -526,10 +526,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 6)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "count".to_string(),
source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 11)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 11)),
},
space1: Whitespace {
value: String::new(),

View File

@ -637,10 +637,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 18), Pos::new(1, 18)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 18), Pos::new(1, 22)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 18), Pos::new(1, 22)),
},
space1: Whitespace {
value: String::new(),
@ -691,10 +692,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 3)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "key".to_string(),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 6))
}
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 6)),
}),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 6))
},
space1: Whitespace {
value: String::new(),

View File

@ -329,10 +329,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 14), Pos::new(1, 14)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 14), Pos::new(1, 18)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 14), Pos::new(1, 18)),
},
space1: Whitespace {
value: String::new(),

View File

@ -135,7 +135,7 @@ pub fn templatize(encoded_string: EncodedString) -> ParseResult<Vec<TemplateElem
mod tests {
use super::*;
use crate::ast::{Expr, Placeholder, Variable, Whitespace};
use crate::ast::{Expr, ExprKind, Placeholder, Variable, Whitespace};
#[test]
fn test_templatize_empty_string() {
@ -235,10 +235,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 11)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "name".to_string(),
source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 15)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 15)),
},
space1: Whitespace {
value: String::new(),
@ -274,10 +275,11 @@ mod tests {
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 3)),
},
expr: Expr {
variable: Variable {
kind: ExprKind::Variable(Variable {
name: "x".to_string(),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 4)),
}
}),
source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 4))
},
space1: Whitespace {
value: String::new(),

View File

@ -774,7 +774,30 @@ impl Tokenizable for Placeholder {
impl Tokenizable for Expr {
fn tokenize(&self) -> Vec<Token> {
vec![Token::CodeVariable(self.variable.name.clone())]
self.kind.tokenize()
}
}
impl Tokenizable for ExprKind {
fn tokenize(&self) -> Vec<Token> {
match self {
ExprKind::Variable(variable) => variable.tokenize(),
ExprKind::Function(function) => function.tokenize(),
}
}
}
impl Tokenizable for Variable {
fn tokenize(&self) -> Vec<Token> {
vec![Token::CodeVariable(self.name.clone())]
}
}
impl Tokenizable for Function {
fn tokenize(&self) -> Vec<Token> {
match self {
Function::NewUuid => vec![Token::CodeVariable("newUuid".to_string())],
}
}
}

View File

@ -85,7 +85,10 @@ fn value_string() -> BoxedStrategy<JsonValue> {
value: String::new(),
source_info
},
expr: Expr { variable },
expr: Expr {
kind: ExprKind::Variable(variable),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
},
space1: Whitespace {
value: String::new(),
source_info