mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-11-22 15:42:20 +03:00
Add nth/replace/split filters
This commit is contained in:
parent
5b5afc4630
commit
b46c82ce0f
@ -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
|
||||
|
|
@ -4,3 +4,4 @@ HTTP 200
|
||||
jsonpath "$.id" toInt == 123
|
||||
jsonpath "$.status" toInt == 0
|
||||
jsonpath "$.unknown" toInt == 1
|
||||
jsonpath "$.list" nth 5 == 3
|
||||
|
@ -7,7 +7,8 @@ def error_filter():
|
||||
return Response(
|
||||
"""{
|
||||
"id":"123x",
|
||||
"status": true
|
||||
"status": true,
|
||||
"list": [1,2,3]
|
||||
}
|
||||
""",
|
||||
mimetype="application/json",
|
||||
|
@ -6,6 +6,7 @@
|
||||
<span class="line"><span class="name">text</span><span>:</span> <span class="query-type">jsonpath</span> <span class="string">"$.text"</span></span>
|
||||
<span class="line section-header">[Asserts]</span>
|
||||
<span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.list"</span> <span class="filter-type">count</span> <span class="predicate-type">==</span> <span class="number">3</span></span>
|
||||
<span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.list"</span> <span class="filter-type">nth</span> 1 <span class="predicate-type">==</span> <span class="number">2</span></span>
|
||||
<span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.message"</span> <span class="filter-type">regex</span> <span class="regex">/Hello (.*)!/</span> <span class="predicate-type">==</span> <span class="string">"Bob"</span></span>
|
||||
<span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.url"</span> <span class="predicate-type">==</span> <span class="string">"https://mozilla.org/?x=шеллы"</span></span>
|
||||
<span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.url"</span> <span class="filter-type">urlEncode</span> <span class="predicate-type">==</span> <span class="string">"https%3A//mozilla.org/%3Fx%3D%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"</span></span>
|
||||
@ -19,6 +20,8 @@
|
||||
<span class="line"><span class="query-type">variable</span> <span class="string">"text"</span> <span class="filter-type">htmlEscape</span> <span class="filter-type">htmlUnescape</span> <span class="predicate-type">==</span> <span class="string">"{{text}}"</span></span>
|
||||
<span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.id"</span> <span class="filter-type">toInt</span> <span class="predicate-type">==</span> <span class="number">123</span></span>
|
||||
<span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.score"</span> <span class="filter-type">toInt</span> <span class="predicate-type">==</span> <span class="number">1</span></span>
|
||||
<span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.ips"</span> <span class="filter-type">split</span> ", " <span class="filter-type">count</span> <span class="predicate-type">==</span> <span class="number">3</span></span>
|
||||
<span class="line"><span class="query-type">jsonpath</span> <span class="string">"$.ips"</span> <span class="filter-type">replace</span> <span class="string">", "</span> "|" <span class="predicate-type">==</span> <span class="string">"192.168.2.1|10.0.0.20|10.0.0.10"</span></span>
|
||||
<span class="json"><span class="line">{</span>
|
||||
<span class="line"> "list": [1,2,3],</span>
|
||||
<span class="line"> "message": "Hello Bob!",</span>
|
||||
@ -31,6 +34,7 @@
|
||||
<span class="line"> "&#65 foo"</span>
|
||||
<span class="line"> ],</span>
|
||||
<span class="line"> "id": "123",</span>
|
||||
<span class="line"> "score": 1.6</span>
|
||||
<span class="line"> "score": 1.6,</span>
|
||||
<span class="line"> "ips": "192.168.2.1, 10.0.0.20, 10.0.0.10"</span>
|
||||
<span class="line">}</span></span>
|
||||
</span></span></code></pre>
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"}}}}]}
|
||||
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}"""
|
||||
|
@ -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<String, Value>,
|
||||
) -> Result<Value, Error> {
|
||||
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<String, Value>,
|
||||
source_info: &SourceInfo,
|
||||
) -> Result<Value, Error> {
|
||||
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<Value, Err
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_html_encode(value: &Value, source_info: &SourceInfo) -> Result<Value, Error> {
|
||||
fn eval_nth(value: &Value, source_info: &SourceInfo, n: u64) -> Result<Value, Error> {
|
||||
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<Value, Error> {
|
||||
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<Value, Er
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_html_decode(value: &Value, source_info: &SourceInfo) -> Result<Value, Error> {
|
||||
fn eval_html_unescape(value: &Value, source_info: &SourceInfo) -> Result<Value, Error> {
|
||||
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<Value, Er
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_replace(
|
||||
value: &Value,
|
||||
variables: &HashMap<String, Value>,
|
||||
source_info: &SourceInfo,
|
||||
old_value: &RegexValue,
|
||||
new_value: &Template,
|
||||
) -> Result<Value, Error> {
|
||||
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<String, Value>,
|
||||
source_info: &SourceInfo,
|
||||
sep: &Template,
|
||||
) -> Result<Value, Error> {
|
||||
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<Value, Error> {
|
||||
match value {
|
||||
Value::Integer(v) => Ok(Value::Integer(*v)),
|
||||
@ -206,9 +266,10 @@ fn eval_to_int(value: &Value, source_info: &SourceInfo) -> Result<Value, Error>
|
||||
|
||||
#[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()),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ mod multipart;
|
||||
mod predicate;
|
||||
mod predicate_value;
|
||||
mod query;
|
||||
mod regex;
|
||||
mod request;
|
||||
mod response;
|
||||
mod runner_options;
|
||||
|
46
packages/hurl/src/runner/regex.rs
Normal file
46
packages/hurl/src/runner/regex.rs
Normal file
@ -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<String, Value>,
|
||||
) -> Result<Regex, Error> {
|
||||
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()),
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
|
@ -1079,6 +1079,16 @@ impl Htmlable for FilterValue {
|
||||
fn to_html(&self) -> String {
|
||||
match self {
|
||||
FilterValue::Count => "<span class=\"filter-type\">count</span>".to_string(),
|
||||
FilterValue::HtmlEscape => "<span class=\"filter-type\">htmlEscape</span>".to_string(),
|
||||
FilterValue::HtmlUnescape => {
|
||||
"<span class=\"filter-type\">htmlUnescape</span>".to_string()
|
||||
}
|
||||
FilterValue::Nth { space0, n: value } => {
|
||||
let mut buffer = "<span class=\"filter-type\">nth</span>".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("<span class=\"filter-type\">regex</span>");
|
||||
@ -1086,13 +1096,28 @@ impl Htmlable for FilterValue {
|
||||
buffer.push_str(value.to_html().as_str());
|
||||
buffer
|
||||
}
|
||||
FilterValue::UrlEncode => "<span class=\"filter-type\">urlEncode</span>".to_string(),
|
||||
FilterValue::UrlDecode => "<span class=\"filter-type\">urlDecode</span>".to_string(),
|
||||
FilterValue::HtmlEscape => "<span class=\"filter-type\">htmlEscape</span>".to_string(),
|
||||
FilterValue::HtmlUnescape => {
|
||||
"<span class=\"filter-type\">htmlUnescape</span>".to_string()
|
||||
FilterValue::Replace {
|
||||
space0,
|
||||
old_value,
|
||||
space1,
|
||||
new_value,
|
||||
} => {
|
||||
let mut buffer = "<span class=\"filter-type\">replace</span>".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 = "<span class=\"filter-type\">split</span>".to_string();
|
||||
buffer.push_str(space0.to_html().as_str());
|
||||
buffer.push_str(sep.to_html().as_str());
|
||||
buffer
|
||||
}
|
||||
FilterValue::ToInt => "<span class=\"filter-type\">toInt</span>".to_string(),
|
||||
FilterValue::UrlDecode => "<span class=\"filter-type\">urlDecode</span>".to_string(),
|
||||
FilterValue::UrlEncode => "<span class=\"filter-type\">urlEncode</span>".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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())));
|
||||
}
|
||||
|
@ -1143,19 +1143,43 @@ impl Tokenizable for VeryVerboseOption {
|
||||
impl Tokenizable for Filter {
|
||||
fn tokenize(&self) -> Vec<Token> {
|
||||
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<Token> = 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<Token> = vec![];
|
||||
tokens.push(Token::FilterType(String::from("regex")));
|
||||
let mut tokens: Vec<Token> = 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<Token> = 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<Token> = 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"))],
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user