mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-11-22 07:18:06 +03:00
Add function in expression
This commit is contained in:
parent
aae693d698
commit
6f28ab19f6
@ -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
|
||||
|
@ -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
|
||||
|
@ -215,7 +215,7 @@
|
||||
[1;34m |[0m[1;31m ^^^^ the XPath expression is not valid[0m
|
||||
[1;34m |[0m
|
||||
|
||||
[1;31merror[0m: [1mInvalid variable type[0m
|
||||
[1;31merror[0m: [1mInvalid expression type[0m
|
||||
[1;34m-->[0m tests_failed/runner_errors.hurl:142:12
|
||||
[1;34m |[0m
|
||||
[1;34m |[0m [90mGET http://localhost:8000/runner_errors[0m
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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(),
|
||||
}),
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
})],
|
||||
|
@ -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)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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> {
|
||||
|
@ -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(),
|
||||
|
60
packages/hurl_core/src/parser/function.rs
Normal file
60
packages/hurl_core/src/parser/function.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
|
@ -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;
|
||||
|
@ -40,6 +40,7 @@ mod expr;
|
||||
mod filename;
|
||||
mod filename_password;
|
||||
mod filter;
|
||||
mod function;
|
||||
mod json;
|
||||
mod key_string;
|
||||
mod multiline;
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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())],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user