diff --git a/integration/tests_failed/filter.err b/integration/tests_failed/filter.err
index cfad5146d..8c423c237 100644
--- a/integration/tests_failed/filter.err
+++ b/integration/tests_failed/filter.err
@@ -19,3 +19,9 @@ error: Filter Error
| ^^^^^ missing value to apply filter
|
+error: Filter Error
+ --> tests_failed/filter.hurl:7:19
+ |
+ 7 | jsonpath "$.list" nth 5 == 3
+ | ^^^^^ invalid filter input: Out of bound - size is 3
+ |
\ No newline at end of file
diff --git a/integration/tests_failed/filter.hurl b/integration/tests_failed/filter.hurl
index 97fe969c5..fb01cc91a 100644
--- a/integration/tests_failed/filter.hurl
+++ b/integration/tests_failed/filter.hurl
@@ -4,3 +4,4 @@ HTTP 200
jsonpath "$.id" toInt == 123
jsonpath "$.status" toInt == 0
jsonpath "$.unknown" toInt == 1
+jsonpath "$.list" nth 5 == 3
diff --git a/integration/tests_failed/filter.py b/integration/tests_failed/filter.py
index c64b5eae8..2c0a0fc75 100644
--- a/integration/tests_failed/filter.py
+++ b/integration/tests_failed/filter.py
@@ -7,7 +7,8 @@ def error_filter():
return Response(
"""{
"id":"123x",
- "status": true
+ "status": true,
+ "list": [1,2,3]
}
""",
mimetype="application/json",
diff --git a/integration/tests_ok/filter.html b/integration/tests_ok/filter.html
index 34062bc44..bf464a53c 100644
--- a/integration/tests_ok/filter.html
+++ b/integration/tests_ok/filter.html
@@ -6,6 +6,7 @@
text: jsonpath "$.text"
jsonpath "$.list" count == 3
+jsonpath "$.list" nth 1 == 2
jsonpath "$.message" regex /Hello (.*)!/ == "Bob"
jsonpath "$.url" == "https://mozilla.org/?x=шеллы"
jsonpath "$.url" urlEncode == "https%3A//mozilla.org/%3Fx%3D%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"
@@ -19,6 +20,8 @@
variable "text" htmlEscape htmlUnescape == "{{text}}"
jsonpath "$.id" toInt == 123
jsonpath "$.score" toInt == 1
+jsonpath "$.ips" split ", " count == 3
+jsonpath "$.ips" replace ", " "|" == "192.168.2.1|10.0.0.20|10.0.0.10"
{
"list": [1,2,3],
"message": "Hello Bob!",
@@ -31,6 +34,7 @@
"A foo"
],
"id": "123",
- "score": 1.6
+ "score": 1.6,
+ "ips": "192.168.2.1, 10.0.0.20, 10.0.0.10"
}
diff --git a/integration/tests_ok/filter.hurl b/integration/tests_ok/filter.hurl
index eeaa69d61..26c7872e3 100644
--- a/integration/tests_ok/filter.hurl
+++ b/integration/tests_ok/filter.hurl
@@ -6,6 +6,7 @@ url: jsonpath "$.url"
text: jsonpath "$.text"
[Asserts]
jsonpath "$.list" count == 3
+jsonpath "$.list" nth 1 == 2
jsonpath "$.message" regex /Hello (.*)!/ == "Bob"
jsonpath "$.url" == "https://mozilla.org/?x=шеллы"
jsonpath "$.url" urlEncode == "https%3A//mozilla.org/%3Fx%3D%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"
@@ -19,6 +20,8 @@ jsonpath "$.escaped_html[2]" htmlUnescape == "A foo"
variable "text" htmlEscape htmlUnescape == "{{text}}"
jsonpath "$.id" toInt == 123
jsonpath "$.score" toInt == 1
+jsonpath "$.ips" split ", " count == 3
+jsonpath "$.ips" replace ", " "|" == "192.168.2.1|10.0.0.20|10.0.0.10"
{
"list": [1,2,3],
"message": "Hello Bob!",
@@ -31,5 +34,6 @@ jsonpath "$.score" toInt == 1
"A foo"
],
"id": "123",
- "score": 1.6
+ "score": 1.6,
+ "ips": "192.168.2.1, 10.0.0.20, 10.0.0.10"
}
diff --git a/integration/tests_ok/filter.json b/integration/tests_ok/filter.json
index 5076c33c2..68ba22df4 100644
--- a/integration/tests_ok/filter.json
+++ b/integration/tests_ok/filter.json
@@ -1 +1 @@
-{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/filter"},"response":{"status":200,"captures":[{"name":"url","query":{"type":"jsonpath","expr":"$.url"}},{"name":"text","query":{"type":"jsonpath","expr":"$.text"}}],"asserts":[{"query":{"type":"jsonpath","expr":"$.list"},"filters":[{"type":"count"}],"predicate":{"type":"equal","value":3}},{"query":{"type":"jsonpath","expr":"$.message"},"filters":[{"type":"regex","expr":{"type":"regex","value":"Hello (.*)!"}}],"predicate":{"type":"equal","value":"Bob"}},{"query":{"type":"jsonpath","expr":"$.url"},"predicate":{"type":"equal","value":"https://mozilla.org/?x=шеллы"}},{"query":{"type":"jsonpath","expr":"$.url"},"filters":[{"type":"urlEncode"}],"predicate":{"type":"equal","value":"https%3A//mozilla.org/%3Fx%3D%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"}},{"query":{"type":"jsonpath","expr":"$.encoded_url"},"filters":[{"type":"urlDecode"}],"predicate":{"type":"equal","value":"https://mozilla.org/?x=шеллы"}},{"query":{"type":"variable","name":"url"},"filters":[{"type":"urlEncode"},{"type":"urlDecode"}],"predicate":{"type":"equal","value":"{{url}}"}},{"query":{"type":"jsonpath","expr":"$.text"},"predicate":{"type":"equal","value":"a > b && a < c"}},{"query":{"type":"jsonpath","expr":"$.text"},"filters":[{"type":"htmlEscape"}],"predicate":{"type":"equal","value":"a > b && a < c"}},{"query":{"type":"jsonpath","expr":"$.escaped_html[0]"},"filters":[{"type":"htmlUnescape"}],"predicate":{"type":"equal","value":"a > b && a < c"}},{"query":{"type":"jsonpath","expr":"$.escaped_html[1]"},"filters":[{"type":"htmlUnescape"}],"predicate":{"type":"equal","value":"Foo © bar 𝌆 baz ☃ qux"}},{"query":{"type":"jsonpath","expr":"$.escaped_html[2]"},"filters":[{"type":"htmlUnescape"}],"predicate":{"type":"equal","value":"A foo"}},{"query":{"type":"variable","name":"text"},"filters":[{"type":"htmlEscape"},{"type":"htmlUnescape"}],"predicate":{"type":"equal","value":"{{text}}"}},{"query":{"type":"jsonpath","expr":"$.id"},"filters":[{"type":"toInt"}],"predicate":{"type":"equal","value":123}},{"query":{"type":"jsonpath","expr":"$.score"},"filters":[{"type":"toInt"}],"predicate":{"type":"equal","value":1}}],"body":{"type":"json","value":{"list":[1,2,3],"message":"Hello Bob!","url":"https://mozilla.org/?x=шеллы","encoded_url":"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B","text":"a > b && a < c","escaped_html":["a > b && a < c","Foo © bar 𝌆 baz ☃ qux","A foo"],"id":"123","score":1.6}}}}]}
+{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/filter"},"response":{"status":200,"captures":[{"name":"url","query":{"type":"jsonpath","expr":"$.url"}},{"name":"text","query":{"type":"jsonpath","expr":"$.text"}}],"asserts":[{"query":{"type":"jsonpath","expr":"$.list"},"filters":[{"type":"count"}],"predicate":{"type":"equal","value":3}},{"query":{"type":"jsonpath","expr":"$.list"},"filters":[{"type":"nth","n":1}],"predicate":{"type":"equal","value":2}},{"query":{"type":"jsonpath","expr":"$.message"},"filters":[{"type":"regex","expr":{"type":"regex","value":"Hello (.*)!"}}],"predicate":{"type":"equal","value":"Bob"}},{"query":{"type":"jsonpath","expr":"$.url"},"predicate":{"type":"equal","value":"https://mozilla.org/?x=шеллы"}},{"query":{"type":"jsonpath","expr":"$.url"},"filters":[{"type":"urlEncode"}],"predicate":{"type":"equal","value":"https%3A//mozilla.org/%3Fx%3D%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"}},{"query":{"type":"jsonpath","expr":"$.encoded_url"},"filters":[{"type":"urlDecode"}],"predicate":{"type":"equal","value":"https://mozilla.org/?x=шеллы"}},{"query":{"type":"variable","name":"url"},"filters":[{"type":"urlEncode"},{"type":"urlDecode"}],"predicate":{"type":"equal","value":"{{url}}"}},{"query":{"type":"jsonpath","expr":"$.text"},"predicate":{"type":"equal","value":"a > b && a < c"}},{"query":{"type":"jsonpath","expr":"$.text"},"filters":[{"type":"htmlEscape"}],"predicate":{"type":"equal","value":"a > b && a < c"}},{"query":{"type":"jsonpath","expr":"$.escaped_html[0]"},"filters":[{"type":"htmlUnescape"}],"predicate":{"type":"equal","value":"a > b && a < c"}},{"query":{"type":"jsonpath","expr":"$.escaped_html[1]"},"filters":[{"type":"htmlUnescape"}],"predicate":{"type":"equal","value":"Foo © bar 𝌆 baz ☃ qux"}},{"query":{"type":"jsonpath","expr":"$.escaped_html[2]"},"filters":[{"type":"htmlUnescape"}],"predicate":{"type":"equal","value":"A foo"}},{"query":{"type":"variable","name":"text"},"filters":[{"type":"htmlEscape"},{"type":"htmlUnescape"}],"predicate":{"type":"equal","value":"{{text}}"}},{"query":{"type":"jsonpath","expr":"$.id"},"filters":[{"type":"toInt"}],"predicate":{"type":"equal","value":123}},{"query":{"type":"jsonpath","expr":"$.score"},"filters":[{"type":"toInt"}],"predicate":{"type":"equal","value":1}},{"query":{"type":"jsonpath","expr":"$.ips"},"filters":[{"type":"split","sep":", "},{"type":"count"}],"predicate":{"type":"equal","value":3}},{"query":{"type":"jsonpath","expr":"$.ips"},"filters":[{"type":"replace","old_value":", ","new_value":"|"}],"predicate":{"type":"equal","value":"192.168.2.1|10.0.0.20|10.0.0.10"}}],"body":{"type":"json","value":{"list":[1,2,3],"message":"Hello Bob!","url":"https://mozilla.org/?x=шеллы","encoded_url":"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B","text":"a > b && a < c","escaped_html":["a > b && a < c","Foo © bar 𝌆 baz ☃ qux","A foo"],"id":"123","score":1.6,"ips":"192.168.2.1, 10.0.0.20, 10.0.0.10"}}}}]}
diff --git a/integration/tests_ok/filter.out b/integration/tests_ok/filter.out
index dd0488d10..a522cd911 100644
--- a/integration/tests_ok/filter.out
+++ b/integration/tests_ok/filter.out
@@ -10,5 +10,6 @@
"A foo"
],
"id": "123",
- "score": 1.6
+ "score": 1.6,
+ "ips": "192.168.2.1, 10.0.0.20, 10.0.0.10"
}
\ No newline at end of file
diff --git a/integration/tests_ok/filter.py b/integration/tests_ok/filter.py
index 7678c4734..9d3cff1b4 100644
--- a/integration/tests_ok/filter.py
+++ b/integration/tests_ok/filter.py
@@ -15,5 +15,6 @@ def filter():
"A foo"
],
"id": "123",
- "score": 1.6
+ "score": 1.6,
+ "ips": "192.168.2.1, 10.0.0.20, 10.0.0.10"
}"""
diff --git a/packages/hurl/src/runner/filter.rs b/packages/hurl/src/runner/filter.rs
index bfb849e01..d0b5312ae 100644
--- a/packages/hurl/src/runner/filter.rs
+++ b/packages/hurl/src/runner/filter.rs
@@ -15,13 +15,16 @@
* limitations under the License.
*
*/
+use std::collections::HashMap;
+
+use percent_encoding::AsciiSet;
+
+use hurl_core::ast::{Filter, FilterValue, RegexValue, SourceInfo, Template};
+
use crate::html;
+use crate::runner::regex::eval_regex_value;
use crate::runner::template::eval_template;
use crate::runner::{Error, RunnerError, Value};
-use hurl_core::ast::{Filter, FilterValue, RegexValue, SourceInfo};
-use percent_encoding::AsciiSet;
-use regex::Regex;
-use std::collections::HashMap;
// TODO: indicated whether you running the filter in an assert / this produce an "assert" error
pub fn eval_filters(
@@ -42,15 +45,22 @@ fn eval_filter(
variables: &HashMap,
) -> Result {
match &filter.value {
+ FilterValue::Count => eval_count(value, &filter.source_info),
+ FilterValue::HtmlEscape => eval_html_escape(value, &filter.source_info),
+ FilterValue::HtmlUnescape => eval_html_unescape(value, &filter.source_info),
FilterValue::Regex {
value: regex_value, ..
} => eval_regex(value, regex_value, variables, &filter.source_info),
- FilterValue::Count => eval_count(value, &filter.source_info),
- FilterValue::UrlEncode => eval_url_encode(value, &filter.source_info),
- FilterValue::UrlDecode => eval_url_decode(value, &filter.source_info),
- FilterValue::HtmlEscape => eval_html_encode(value, &filter.source_info),
- FilterValue::HtmlUnescape => eval_html_decode(value, &filter.source_info),
+ FilterValue::Nth { n, .. } => eval_nth(value, &filter.source_info, *n),
+ FilterValue::Replace {
+ old_value,
+ new_value,
+ ..
+ } => eval_replace(value, variables, &filter.source_info, old_value, new_value),
+ FilterValue::Split { sep, .. } => eval_split(value, variables, &filter.source_info, sep),
FilterValue::ToInt => eval_to_int(value, &filter.source_info),
+ FilterValue::UrlDecode => eval_url_decode(value, &filter.source_info),
+ FilterValue::UrlEncode => eval_url_encode(value, &filter.source_info),
}
}
@@ -60,23 +70,7 @@ fn eval_regex(
variables: &HashMap,
source_info: &SourceInfo,
) -> Result {
- let re = match regex_value {
- RegexValue::Template(t) => {
- let value = eval_template(t, variables)?;
- match Regex::new(value.as_str()) {
- Ok(re) => re,
- Err(_) => {
- return Err(Error {
- source_info: t.source_info.clone(),
- inner: RunnerError::InvalidRegex(),
- assert: false,
- });
- }
- }
- }
- RegexValue::Regex(re) => re.inner.clone(),
- };
-
+ let re = eval_regex_value(regex_value, variables)?;
match value {
Value::String(s) => match re.captures(s.as_str()) {
Some(captures) => match captures.get(1) {
@@ -156,7 +150,28 @@ fn eval_url_decode(value: &Value, source_info: &SourceInfo) -> Result Result {
+fn eval_nth(value: &Value, source_info: &SourceInfo, n: u64) -> Result {
+ match value {
+ Value::List(values) => match values.get(n as usize) {
+ None => Err(Error {
+ source_info: source_info.clone(),
+ inner: RunnerError::FilterInvalidInput(format!(
+ "Out of bound - size is {}",
+ values.len()
+ )),
+ assert: false,
+ }),
+ Some(value) => Ok(value.clone()),
+ },
+ v => Err(Error {
+ source_info: source_info.clone(),
+ inner: RunnerError::FilterInvalidInput(v.display()),
+ assert: false,
+ }),
+ }
+}
+
+fn eval_html_escape(value: &Value, source_info: &SourceInfo) -> Result {
match value {
Value::String(value) => {
let encoded = html::html_escape(value);
@@ -170,7 +185,7 @@ fn eval_html_encode(value: &Value, source_info: &SourceInfo) -> Result Result {
+fn eval_html_unescape(value: &Value, source_info: &SourceInfo) -> Result {
match value {
Value::String(value) => {
let decoded = html::html_unescape(value);
@@ -184,6 +199,51 @@ fn eval_html_decode(value: &Value, source_info: &SourceInfo) -> Result,
+ source_info: &SourceInfo,
+ old_value: &RegexValue,
+ new_value: &Template,
+) -> Result {
+ match value {
+ Value::String(v) => {
+ let re = eval_regex_value(old_value, variables)?;
+ let new_value = eval_template(new_value, variables)?;
+ let s = re.replace_all(v, new_value).to_string();
+ Ok(Value::String(s))
+ }
+ v => Err(Error {
+ source_info: source_info.clone(),
+ inner: RunnerError::FilterInvalidInput(v.display()),
+ assert: false,
+ }),
+ }
+}
+
+fn eval_split(
+ value: &Value,
+ variables: &HashMap,
+ source_info: &SourceInfo,
+ sep: &Template,
+) -> Result {
+ match value {
+ Value::String(s) => {
+ let sep = eval_template(sep, variables)?;
+ let values = s
+ .split(&sep)
+ .map(|v| Value::String(v.to_string()))
+ .collect();
+ Ok(Value::List(values))
+ }
+ v => Err(Error {
+ source_info: source_info.clone(),
+ inner: RunnerError::FilterInvalidInput(v.display()),
+ assert: false,
+ }),
+ }
+}
+
fn eval_to_int(value: &Value, source_info: &SourceInfo) -> Result {
match value {
Value::Integer(v) => Ok(Value::Integer(*v)),
@@ -206,9 +266,10 @@ fn eval_to_int(value: &Value, source_info: &SourceInfo) -> Result
#[cfg(test)]
pub mod tests {
- use super::*;
use hurl_core::ast::{FilterValue, SourceInfo, Template, TemplateElement, Whitespace};
+ use super::*;
+
pub fn filter_count() -> Filter {
Filter {
source_info: SourceInfo::new(1, 1, 1, 6),
@@ -468,4 +529,118 @@ pub mod tests {
);
}
}
+
+ #[test]
+ pub fn eval_filter_nth() {
+ let variables = HashMap::new();
+ let filter = Filter {
+ source_info: SourceInfo::new(1, 1, 1, 1),
+ value: FilterValue::Nth {
+ n: 2,
+ space0: Whitespace {
+ value: String::from(""),
+ source_info: SourceInfo::new(0, 0, 0, 0),
+ },
+ },
+ };
+
+ assert_eq!(
+ eval_filter(
+ &filter,
+ &Value::List(vec![
+ Value::Integer(0),
+ Value::Integer(1),
+ Value::Integer(2),
+ Value::Integer(3)
+ ]),
+ &variables
+ )
+ .unwrap(),
+ Value::Integer(2)
+ );
+ assert_eq!(
+ eval_filter(
+ &filter,
+ &Value::List(vec![Value::Integer(0), Value::Integer(1)]),
+ &variables
+ )
+ .err()
+ .unwrap(),
+ Error {
+ source_info: SourceInfo::new(1, 1, 1, 1),
+ inner: RunnerError::FilterInvalidInput("Out of bound - size is 2".to_string()),
+ assert: false
+ }
+ );
+ }
+
+ #[test]
+ pub fn eval_filter_replace() {
+ let variables = HashMap::new();
+ let filter = Filter {
+ source_info: SourceInfo::new(1, 1, 1, 1),
+ value: FilterValue::Replace {
+ old_value: RegexValue::Template(Template {
+ delimiter: None,
+ elements: vec![TemplateElement::String {
+ value: "\\s+".to_string(),
+ encoded: ",".to_string(),
+ }],
+ source_info: SourceInfo::new(1, 7, 1, 20),
+ }),
+ new_value: Template {
+ delimiter: Some('"'),
+ elements: vec![TemplateElement::String {
+ value: ",".to_string(),
+ encoded: ",".to_string(),
+ }],
+ source_info: SourceInfo::new(0, 0, 0, 0),
+ },
+ space0: Whitespace {
+ value: String::from(""),
+ source_info: SourceInfo::new(0, 0, 0, 0),
+ },
+ space1: Whitespace {
+ value: String::from(""),
+ source_info: SourceInfo::new(0, 0, 0, 0),
+ },
+ },
+ };
+
+ assert_eq!(
+ eval_filter(&filter, &Value::String("1 2\t3 4".to_string()), &variables).unwrap(),
+ Value::String("1,2,3,4".to_string())
+ );
+ }
+
+ #[test]
+ pub fn eval_filter_split() {
+ let variables = HashMap::new();
+ let filter = Filter {
+ source_info: SourceInfo::new(1, 1, 1, 1),
+ value: FilterValue::Split {
+ sep: Template {
+ delimiter: Some('"'),
+ elements: vec![TemplateElement::String {
+ value: ",".to_string(),
+ encoded: ",".to_string(),
+ }],
+ source_info: SourceInfo::new(0, 0, 0, 0),
+ },
+ space0: Whitespace {
+ value: String::from(""),
+ source_info: SourceInfo::new(0, 0, 0, 0),
+ },
+ },
+ };
+
+ assert_eq!(
+ eval_filter(&filter, &Value::String("1,2,3".to_string()), &variables).unwrap(),
+ Value::List(vec![
+ Value::String("1".to_string()),
+ Value::String("2".to_string()),
+ Value::String("3".to_string()),
+ ])
+ );
+ }
}
diff --git a/packages/hurl/src/runner/mod.rs b/packages/hurl/src/runner/mod.rs
index 1ac20fb25..a5c06f446 100644
--- a/packages/hurl/src/runner/mod.rs
+++ b/packages/hurl/src/runner/mod.rs
@@ -45,6 +45,7 @@ mod multipart;
mod predicate;
mod predicate_value;
mod query;
+mod regex;
mod request;
mod response;
mod runner_options;
diff --git a/packages/hurl/src/runner/regex.rs b/packages/hurl/src/runner/regex.rs
new file mode 100644
index 000000000..babcd8507
--- /dev/null
+++ b/packages/hurl/src/runner/regex.rs
@@ -0,0 +1,46 @@
+/*
+ * Hurl (https://hurl.dev)
+ * Copyright (C) 2023 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 std::collections::HashMap;
+
+use regex::Regex;
+
+use hurl_core::ast::RegexValue;
+
+use crate::runner::template::eval_template;
+use crate::runner::{Error, RunnerError, Value};
+
+pub fn eval_regex_value(
+ regex_value: &RegexValue,
+ variables: &HashMap,
+) -> Result {
+ match regex_value {
+ RegexValue::Template(t) => {
+ let value = eval_template(t, variables)?;
+ match Regex::new(value.as_str()) {
+ Ok(re) => Ok(re),
+ Err(_) => Err(Error {
+ source_info: t.source_info.clone(),
+ inner: RunnerError::InvalidRegex(),
+ assert: false,
+ }),
+ }
+ }
+ RegexValue::Regex(re) => Ok(re.inner.clone()),
+ }
+}
diff --git a/packages/hurl_core/src/ast/core.rs b/packages/hurl_core/src/ast/core.rs
index 4fb608c75..86bfa2bb9 100644
--- a/packages/hurl_core/src/ast/core.rs
+++ b/packages/hurl_core/src/ast/core.rs
@@ -872,13 +872,27 @@ pub struct Filter {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FilterValue {
Count,
+ HtmlEscape,
+ HtmlUnescape,
+ Nth {
+ space0: Whitespace,
+ n: u64,
+ },
Regex {
space0: Whitespace,
value: RegexValue,
},
- UrlEncode,
- UrlDecode,
- HtmlEscape,
- HtmlUnescape,
+ Replace {
+ space0: Whitespace,
+ old_value: RegexValue,
+ space1: Whitespace,
+ new_value: Template,
+ },
+ Split {
+ space0: Whitespace,
+ sep: Template,
+ },
ToInt,
+ UrlDecode,
+ UrlEncode,
}
diff --git a/packages/hurl_core/src/format/html.rs b/packages/hurl_core/src/format/html.rs
index 480fbe972..86a3c7674 100644
--- a/packages/hurl_core/src/format/html.rs
+++ b/packages/hurl_core/src/format/html.rs
@@ -1079,6 +1079,16 @@ impl Htmlable for FilterValue {
fn to_html(&self) -> String {
match self {
FilterValue::Count => "count".to_string(),
+ FilterValue::HtmlEscape => "htmlEscape".to_string(),
+ FilterValue::HtmlUnescape => {
+ "htmlUnescape".to_string()
+ }
+ FilterValue::Nth { space0, n: value } => {
+ let mut buffer = "nth".to_string();
+ buffer.push_str(space0.to_html().as_str());
+ buffer.push_str(&value.to_string());
+ buffer
+ }
FilterValue::Regex { space0, value } => {
let mut buffer = "".to_string();
buffer.push_str("regex");
@@ -1086,13 +1096,28 @@ impl Htmlable for FilterValue {
buffer.push_str(value.to_html().as_str());
buffer
}
- FilterValue::UrlEncode => "urlEncode".to_string(),
- FilterValue::UrlDecode => "urlDecode".to_string(),
- FilterValue::HtmlEscape => "htmlEscape".to_string(),
- FilterValue::HtmlUnescape => {
- "htmlUnescape".to_string()
+ FilterValue::Replace {
+ space0,
+ old_value,
+ space1,
+ new_value,
+ } => {
+ let mut buffer = "replace".to_string();
+ buffer.push_str(space0.to_html().as_str());
+ buffer.push_str(old_value.to_html().as_str());
+ buffer.push_str(space1.to_html().as_str());
+ buffer.push_str(new_value.to_html().as_str());
+ buffer
+ }
+ FilterValue::Split { space0, sep } => {
+ let mut buffer = "split".to_string();
+ buffer.push_str(space0.to_html().as_str());
+ buffer.push_str(sep.to_html().as_str());
+ buffer
}
FilterValue::ToInt => "toInt".to_string(),
+ FilterValue::UrlDecode => "urlDecode".to_string(),
+ FilterValue::UrlEncode => "urlEncode".to_string(),
}
}
}
diff --git a/packages/hurl_core/src/parser/filter.rs b/packages/hurl_core/src/parser/filter.rs
index 047637551..2e875da4e 100644
--- a/packages/hurl_core/src/parser/filter.rs
+++ b/packages/hurl_core/src/parser/filter.rs
@@ -17,8 +17,9 @@
*/
use crate::ast::{Filter, FilterValue, SourceInfo, Whitespace};
use crate::parser::combinators::choice;
-use crate::parser::primitives::{one_or_more_spaces, try_literal, zero_or_more_spaces};
+use crate::parser::primitives::{natural, one_or_more_spaces, try_literal, zero_or_more_spaces};
use crate::parser::query::regex_value;
+use crate::parser::string::quoted_template;
use crate::parser::{Error, ParseError, ParseResult, Reader};
pub fn filters(reader: &mut Reader) -> ParseResult<'static, Vec<(Whitespace, Filter)>> {
@@ -51,12 +52,15 @@ pub fn filter(reader: &mut Reader) -> ParseResult<'static, Filter> {
let value = choice(
&[
count_filter,
- regex_filter,
- url_encode_filter,
- url_decode_filter,
- html_encode_filter,
html_decode_filter,
+ html_encode_filter,
+ nth_filter,
+ regex_filter,
+ replace_filter,
+ split_filter,
to_int_filter,
+ url_decode_filter,
+ url_encode_filter,
],
reader,
)
@@ -83,23 +87,6 @@ fn count_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
Ok(FilterValue::Count)
}
-fn regex_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
- try_literal("regex", reader)?;
- let space0 = one_or_more_spaces(reader)?;
- let value = regex_value(reader)?;
- Ok(FilterValue::Regex { space0, value })
-}
-
-fn url_encode_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
- try_literal("urlEncode", reader)?;
- Ok(FilterValue::UrlEncode)
-}
-
-fn url_decode_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
- try_literal("urlDecode", reader)?;
- Ok(FilterValue::UrlDecode)
-}
-
fn html_encode_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
try_literal("htmlEscape", reader)?;
Ok(FilterValue::HtmlEscape)
@@ -110,11 +97,56 @@ fn html_decode_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue>
Ok(FilterValue::HtmlUnescape)
}
+fn nth_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
+ try_literal("nth", reader)?;
+ let space0 = one_or_more_spaces(reader)?;
+ let n = natural(reader)?;
+ Ok(FilterValue::Nth { space0, n })
+}
+
+fn regex_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
+ try_literal("regex", reader)?;
+ let space0 = one_or_more_spaces(reader)?;
+ let value = regex_value(reader)?;
+ Ok(FilterValue::Regex { space0, value })
+}
+
+fn replace_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
+ try_literal("replace", reader)?;
+ let space0 = one_or_more_spaces(reader)?;
+ let old_value = regex_value(reader)?;
+ let space1 = one_or_more_spaces(reader)?;
+ let new_value = quoted_template(reader).map_err(|e| e.non_recoverable())?;
+ Ok(FilterValue::Replace {
+ space0,
+ old_value,
+ space1,
+ new_value,
+ })
+}
+
+fn split_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
+ try_literal("split", reader)?;
+ let space0 = one_or_more_spaces(reader)?;
+ let sep = quoted_template(reader).map_err(|e| e.non_recoverable())?;
+ Ok(FilterValue::Split { space0, sep })
+}
+
fn to_int_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
try_literal("toInt", reader)?;
Ok(FilterValue::ToInt)
}
+fn url_encode_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
+ try_literal("urlEncode", reader)?;
+ Ok(FilterValue::UrlEncode)
+}
+
+fn url_decode_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> {
+ try_literal("urlDecode", reader)?;
+ Ok(FilterValue::UrlDecode)
+}
+
#[cfg(test)]
mod tests {
use crate::ast::Pos;
diff --git a/packages/hurlfmt/src/format/json.rs b/packages/hurlfmt/src/format/json.rs
index 0370c88bb..b38a80553 100644
--- a/packages/hurlfmt/src/format/json.rs
+++ b/packages/hurlfmt/src/format/json.rs
@@ -16,9 +16,10 @@
*
*/
-use super::serialize_json::*;
use hurl_core::ast::*;
+use super::serialize_json::*;
+
pub fn format(hurl_file: HurlFile) -> String {
hurl_file.to_json().format()
}
@@ -526,18 +527,12 @@ impl ToJson for FilterValue {
fn to_json(&self) -> JValue {
let mut attributes = vec![];
match self {
- FilterValue::Regex { value, .. } => {
- attributes.push(("type".to_string(), JValue::String("regex".to_string())));
- attributes.push(("expr".to_string(), value.to_json()));
- }
FilterValue::Count => {
attributes.push(("type".to_string(), JValue::String("count".to_string())));
}
- FilterValue::UrlEncode => {
- attributes.push(("type".to_string(), JValue::String("urlEncode".to_string())));
- }
- FilterValue::UrlDecode => {
- attributes.push(("type".to_string(), JValue::String("urlDecode".to_string())));
+ FilterValue::Nth { n, .. } => {
+ attributes.push(("type".to_string(), JValue::String("nth".to_string())));
+ attributes.push(("n".to_string(), JValue::Number(n.to_string())));
}
FilterValue::HtmlEscape => {
attributes.push(("type".to_string(), JValue::String("htmlEscape".to_string())));
@@ -548,6 +543,32 @@ impl ToJson for FilterValue {
JValue::String("htmlUnescape".to_string()),
));
}
+ FilterValue::Regex { value, .. } => {
+ attributes.push(("type".to_string(), JValue::String("regex".to_string())));
+ attributes.push(("expr".to_string(), value.to_json()));
+ }
+ FilterValue::Replace {
+ old_value,
+ new_value,
+ ..
+ } => {
+ attributes.push(("type".to_string(), JValue::String("replace".to_string())));
+ attributes.push(("old_value".to_string(), old_value.to_json()));
+ attributes.push((
+ "new_value".to_string(),
+ JValue::String(new_value.to_string()),
+ ));
+ }
+ FilterValue::UrlEncode => {
+ attributes.push(("type".to_string(), JValue::String("urlEncode".to_string())));
+ }
+ FilterValue::UrlDecode => {
+ attributes.push(("type".to_string(), JValue::String("urlDecode".to_string())));
+ }
+ FilterValue::Split { sep, .. } => {
+ attributes.push(("type".to_string(), JValue::String("split".to_string())));
+ attributes.push(("sep".to_string(), JValue::String(sep.to_string())));
+ }
FilterValue::ToInt => {
attributes.push(("type".to_string(), JValue::String("toInt".to_string())));
}
diff --git a/packages/hurlfmt/src/format/token.rs b/packages/hurlfmt/src/format/token.rs
index fdda3620e..c5977e6b3 100644
--- a/packages/hurlfmt/src/format/token.rs
+++ b/packages/hurlfmt/src/format/token.rs
@@ -1143,19 +1143,43 @@ impl Tokenizable for VeryVerboseOption {
impl Tokenizable for Filter {
fn tokenize(&self) -> Vec {
match self.value.clone() {
+ FilterValue::Count => vec![Token::FilterType(String::from("count"))],
+ FilterValue::HtmlEscape => vec![Token::FilterType(String::from("htmlEscape"))],
+ FilterValue::HtmlUnescape => {
+ vec![Token::FilterType(String::from("htmlUnescape"))]
+ }
+ FilterValue::Nth { space0, n } => {
+ let mut tokens: Vec = vec![Token::FilterType(String::from("nth"))];
+ tokens.append(&mut space0.tokenize());
+ tokens.push(Token::Number(n.to_string()));
+ tokens
+ }
FilterValue::Regex { space0, value } => {
- let mut tokens: Vec = vec![];
- tokens.push(Token::FilterType(String::from("regex")));
+ let mut tokens: Vec = vec![Token::FilterType(String::from("regex"))];
tokens.append(&mut space0.tokenize());
tokens.append(&mut value.tokenize());
tokens
}
- FilterValue::Count => vec![Token::FilterType(String::from("count"))],
+ FilterValue::Replace {
+ space0,
+ old_value,
+ space1,
+ new_value,
+ } => {
+ let mut tokens: Vec = vec![Token::FilterType(String::from("replace"))];
+ tokens.append(&mut space0.tokenize());
+ tokens.append(&mut old_value.tokenize());
+ tokens.append(&mut space1.tokenize());
+ tokens.append(&mut new_value.tokenize());
+ tokens
+ }
FilterValue::UrlEncode => vec![Token::FilterType(String::from("urlEncode"))],
FilterValue::UrlDecode => vec![Token::FilterType(String::from("urlDecode"))],
- FilterValue::HtmlEscape => vec![Token::FilterType(String::from("htmlEscape"))],
- FilterValue::HtmlUnescape => {
- vec![Token::FilterType(String::from("htmlUnescape"))]
+ FilterValue::Split { space0, sep } => {
+ let mut tokens: Vec = vec![Token::FilterType(String::from("split"))];
+ tokens.append(&mut space0.tokenize());
+ tokens.append(&mut sep.tokenize());
+ tokens
}
FilterValue::ToInt => vec![Token::FilterType(String::from("toInt"))],
}