Add nth/replace/split filters

This commit is contained in:
Fabrice Reix 2023-01-06 21:20:10 +01:00 committed by jcamiel
parent 5b5afc4630
commit b46c82ce0f
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
16 changed files with 438 additions and 82 deletions

View File

@ -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
|

View File

@ -4,3 +4,4 @@ HTTP 200
jsonpath "$.id" toInt == 123
jsonpath "$.status" toInt == 0
jsonpath "$.unknown" toInt == 1
jsonpath "$.list" nth 5 == 3

View File

@ -7,7 +7,8 @@ def error_filter():
return Response(
"""{
"id":"123x",
"status": true
"status": true,
"list": [1,2,3]
}
""",
mimetype="application/json",

View File

@ -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"> "&amp;#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>

View File

@ -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
"&#65 foo"
],
"id": "123",
"score": 1.6
"score": 1.6,
"ips": "192.168.2.1, 10.0.0.20, 10.0.0.10"
}

View File

@ -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 &gt; b &amp;&amp; a &lt; 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 &gt; b &amp;&amp; a &lt; c","Foo &#xA9; bar &#x1D306; baz &#x2603; qux","&#65 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 &gt; b &amp;&amp; a &lt; 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 &gt; b &amp;&amp; a &lt; c","Foo &#xA9; bar &#x1D306; baz &#x2603; qux","&#65 foo"],"id":"123","score":1.6,"ips":"192.168.2.1, 10.0.0.20, 10.0.0.10"}}}}]}

View File

@ -10,5 +10,6 @@
"&#65 foo"
],
"id": "123",
"score": 1.6
"score": 1.6,
"ips": "192.168.2.1, 10.0.0.20, 10.0.0.10"
}

View File

@ -15,5 +15,6 @@ def filter():
"&#65 foo"
],
"id": "123",
"score": 1.6
"score": 1.6,
"ips": "192.168.2.1, 10.0.0.20, 10.0.0.10"
}"""

View File

@ -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()),
])
);
}
}

View File

@ -45,6 +45,7 @@ mod multipart;
mod predicate;
mod predicate_value;
mod query;
mod regex;
mod request;
mod response;
mod runner_options;

View 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()),
}
}

View File

@ -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,
}

View File

@ -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(),
}
}
}

View File

@ -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;

View File

@ -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())));
}

View File

@ -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"))],
}