From 550c5171499022b327c139c5ab4e1c6fee6d6864 Mon Sep 17 00:00:00 2001 From: Fabrice Reix Date: Tue, 31 Jan 2023 20:00:55 +0100 Subject: [PATCH] Add Date value/filters --- docs/spec/hurl.grammar | 6 + integration/tests_failed/filter.err | 10 +- integration/tests_failed/filter.hurl | 1 + integration/tests_ok/assert_header.html | 2 + integration/tests_ok/assert_header.hurl | 2 + integration/tests_ok/assert_header.json | 2 +- .../tests_ok/assert_header.out.pattern | 2 +- integration/tests_ok/assert_header.py | 1 + packages/hurl/src/json/value.rs | 1 + packages/hurl/src/runner/filter.rs | 169 +++++++++++++++++- packages/hurl/src/runner/predicate.rs | 2 + packages/hurl/src/runner/value.rs | 4 + packages/hurl_core/src/ast/core.rs | 8 + packages/hurl_core/src/format/html.rs | 13 ++ packages/hurl_core/src/parser/filter.rs | 16 ++ packages/hurlfmt/src/format/json.rs | 8 + packages/hurlfmt/src/format/token.rs | 12 ++ 17 files changed, 254 insertions(+), 5 deletions(-) diff --git a/docs/spec/hurl.grammar b/docs/spec/hurl.grammar index 4c8aba69378..6d03da6b96e 100644 --- a/docs/spec/hurl.grammar +++ b/docs/spec/hurl.grammar @@ -415,18 +415,22 @@ variable-name: [A-Za-z] [A-Za-z_-0-9]* filter: count-filter + | format-filter | html-escape-filter | html-unescape-filter | nth-filter | regex-filter | replace-filter | split-filter + | to-date-filter | to-int-filter | url-decode-filter | url-encode-filter count-filter: "count" +format-filter: "format" + html-escape-filter: "htmlEscape" html-unescape-filter: "htmlUnescape" @@ -439,6 +443,8 @@ replace-filter: "replace" sp (quoted-string | regex) sp quoted-string split-filter: "split" sp quoted-string +to-date-filter: "toDate" + to-int-filter: "toInt" url-decode-filter: "urlDecode" diff --git a/integration/tests_failed/filter.err b/integration/tests_failed/filter.err index 8c423c23777..54ad860b535 100644 --- a/integration/tests_failed/filter.err +++ b/integration/tests_failed/filter.err @@ -24,4 +24,12 @@ error: Filter Error | 7 | jsonpath "$.list" nth 5 == 3 | ^^^^^ invalid filter input: Out of bound - size is 3 - | \ No newline at end of file + | + +error: Filter Error + --> tests_failed/filter.hurl:8:17 + | + 8 | jsonpath "$.id" toDate "%a, %d %b %Y %H:%M:%S GMT" == "unused" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid filter input: string <123x> + | + diff --git a/integration/tests_failed/filter.hurl b/integration/tests_failed/filter.hurl index fb01cc91a23..82c1c3750b2 100644 --- a/integration/tests_failed/filter.hurl +++ b/integration/tests_failed/filter.hurl @@ -5,3 +5,4 @@ jsonpath "$.id" toInt == 123 jsonpath "$.status" toInt == 0 jsonpath "$.unknown" toInt == 1 jsonpath "$.list" nth 5 == 3 +jsonpath "$.id" toDate "%a, %d %b %Y %H:%M:%S GMT" == "unused" \ No newline at end of file diff --git a/integration/tests_ok/assert_header.html b/integration/tests_ok/assert_header.html index e740c45786f..2c44b1f916d 100644 --- a/integration/tests_ok/assert_header.html +++ b/integration/tests_ok/assert_header.html @@ -9,6 +9,8 @@ header "Content-Type" exists header "Header1" == "value1" header "ETag" == "\"33a64df551425fcc55e4d42a148795d9f25f89d4\"" +header "Expires" == "Wed, 21 Oct 2015 07:28:00 GMT" +header "Expires" toDate "%a, %d %b %Y %H:%M:%S GMT" format "%Y" == "2015" header "Set-Cookie" exists header "Set-Cookie" count == 3 header "Set-Cookie" includes "cookie1=value1; Path=/" diff --git a/integration/tests_ok/assert_header.hurl b/integration/tests_ok/assert_header.hurl index 029ebf8db0e..a8bc8f2226f 100644 --- a/integration/tests_ok/assert_header.hurl +++ b/integration/tests_ok/assert_header.hurl @@ -9,6 +9,8 @@ header "Custom" not exists header "Content-Type" exists header "Header1" == "value1" header "ETag" == "\"33a64df551425fcc55e4d42a148795d9f25f89d4\"" +header "Expires" == "Wed, 21 Oct 2015 07:28:00 GMT" +header "Expires" toDate "%a, %d %b %Y %H:%M:%S GMT" format "%Y" == "2015" header "Set-Cookie" exists header "Set-Cookie" count == 3 header "Set-Cookie" includes "cookie1=value1; Path=/" diff --git a/integration/tests_ok/assert_header.json b/integration/tests_ok/assert_header.json index 24078bd843f..1a439988cd5 100644 --- a/integration/tests_ok/assert_header.json +++ b/integration/tests_ok/assert_header.json @@ -1 +1 @@ -{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/assert-header"},"response":{"status":200,"headers":[{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Set-Cookie","value":"cookie1=value1; Path=/"},{"name":"Set-Cookie","value":"cookie2=value2; Path=/"}],"asserts":[{"query":{"type":"header","name":"Custom"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"header","name":"Content-Type"},"predicate":{"type":"exist"}},{"query":{"type":"header","name":"Header1"},"predicate":{"type":"equal","value":"value1"}},{"query":{"type":"header","name":"ETag"},"predicate":{"type":"equal","value":"\"33a64df551425fcc55e4d42a148795d9f25f89d4\""}},{"query":{"type":"header","name":"Set-Cookie"},"predicate":{"type":"exist"}},{"query":{"type":"header","name":"Set-Cookie"},"filters":[{"type":"count"}],"predicate":{"type":"equal","value":3}},{"query":{"type":"header","name":"Set-Cookie"},"predicate":{"type":"include","value":"cookie1=value1; Path=/"}},{"query":{"type":"header","name":"Set-Cookie"},"predicate":{"not":true,"type":"include","value":"cookie4=value4; Path=/"}}]}}]} +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/assert-header"},"response":{"status":200,"headers":[{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Set-Cookie","value":"cookie1=value1; Path=/"},{"name":"Set-Cookie","value":"cookie2=value2; Path=/"}],"asserts":[{"query":{"type":"header","name":"Custom"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"header","name":"Content-Type"},"predicate":{"type":"exist"}},{"query":{"type":"header","name":"Header1"},"predicate":{"type":"equal","value":"value1"}},{"query":{"type":"header","name":"ETag"},"predicate":{"type":"equal","value":"\"33a64df551425fcc55e4d42a148795d9f25f89d4\""}},{"query":{"type":"header","name":"Expires"},"predicate":{"type":"equal","value":"Wed, 21 Oct 2015 07:28:00 GMT"}},{"query":{"type":"header","name":"Expires"},"filters":[{"type":"toDate","fmt":"%a, %d %b %Y %H:%M:%S GMT"},{"type":"format","fmt":"%Y"}],"predicate":{"type":"equal","value":"2015"}},{"query":{"type":"header","name":"Set-Cookie"},"predicate":{"type":"exist"}},{"query":{"type":"header","name":"Set-Cookie"},"filters":[{"type":"count"}],"predicate":{"type":"equal","value":3}},{"query":{"type":"header","name":"Set-Cookie"},"predicate":{"type":"include","value":"cookie1=value1; Path=/"}},{"query":{"type":"header","name":"Set-Cookie"},"predicate":{"not":true,"type":"include","value":"cookie4=value4; Path=/"}}]}}]} diff --git a/integration/tests_ok/assert_header.out.pattern b/integration/tests_ok/assert_header.out.pattern index 2d571d671f3..34783361dfc 100644 --- a/integration/tests_ok/assert_header.out.pattern +++ b/integration/tests_ok/assert_header.out.pattern @@ -1 +1 @@ -{"cookies":[{"domain":"localhost","expires":"0","https":"FALSE","include_subdomain":"FALSE","name":"cookie1","path":"/","value":"value1"},{"domain":"localhost","expires":"0","https":"FALSE","include_subdomain":"FALSE","name":"cookie2","path":"/","value":"value2"},{"domain":"localhost","expires":"0","https":"FALSE","include_subdomain":"FALSE","name":"cookie3","path":"/","value":"value3"}],"entries":[{"asserts":[{"line":3,"success":true},{"line":3,"success":true},{"line":4,"success":true},{"line":5,"success":true},{"line":6,"success":true},{"line":8,"success":true},{"line":9,"success":true},{"line":10,"success":true},{"line":11,"success":true},{"line":12,"success":true},{"line":13,"success":true},{"line":14,"success":true},{"line":15,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/~~~"}],"method":"GET","queryString":[],"url":"http://localhost:8000/assert-header"},"response":{"cookies":[{"name":"cookie1","path":"/","value":"value1"},{"name":"cookie2","path":"/","value":"value2"},{"name":"cookie3","path":"/","value":"value3"}],"headers":[{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Header1","value":"value1"},{"name":"ETag","value":"\"33a64df551425fcc55e4d42a148795d9f25f89d4\""},{"name":"Set-Cookie","value":"cookie1=value1; Path=/"},{"name":"Set-Cookie","value":"cookie2=value2; Path=/"},{"name":"Set-Cookie","value":"cookie3=value3; Path=/"},{"name":"Server","value":"Flask Server"},{"name":"Content-Length","value":"0"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":1,"time":~~~}],"filename":"tests_ok/assert_header.hurl","success":true,"time":~~~} +{"cookies":[{"domain":"localhost","expires":"0","https":"FALSE","include_subdomain":"FALSE","name":"cookie1","path":"/","value":"value1"},{"domain":"localhost","expires":"0","https":"FALSE","include_subdomain":"FALSE","name":"cookie2","path":"/","value":"value2"},{"domain":"localhost","expires":"0","https":"FALSE","include_subdomain":"FALSE","name":"cookie3","path":"/","value":"value3"}],"entries":[{"asserts":[{"line":3,"success":true},{"line":3,"success":true},{"line":4,"success":true},{"line":5,"success":true},{"line":6,"success":true},{"line":8,"success":true},{"line":9,"success":true},{"line":10,"success":true},{"line":11,"success":true},{"line":12,"success":true},{"line":13,"success":true},{"line":14,"success":true},{"line":15,"success":true},{"line":16,"success":true},{"line":17,"success":true}],"calls":[{"request":{"cookies":[],"headers":[{"name":"Host","value":"localhost:8000"},{"name":"Accept","value":"*/*"},{"name":"User-Agent","value":"hurl/2.1.0-SNAPSHOT"}],"method":"GET","queryString":[],"url":"http://localhost:8000/assert-header"},"response":{"cookies":[{"name":"cookie1","path":"/","value":"value1"},{"name":"cookie2","path":"/","value":"value2"},{"name":"cookie3","path":"/","value":"value3"}],"headers":[{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Header1","value":"value1"},{"name":"ETag","value":"\"33a64df551425fcc55e4d42a148795d9f25f89d4\""},{"name":"Expires","value":"Wed, 21 Oct 2015 07:28:00 GMT"},{"name":"Set-Cookie","value":"cookie1=value1; Path=/"},{"name":"Set-Cookie","value":"cookie2=value2; Path=/"},{"name":"Set-Cookie","value":"cookie3=value3; Path=/"},{"name":"Server","value":"Flask Server"},{"name":"Content-Length","value":"0"},{"name":"Date","value":"~~~"}],"httpVersion":"HTTP/1.0","status":200}}],"captures":[],"index":1,"time":~~~}],"filename":"tests_ok/assert_header.hurl","success":true,"time":~~~} diff --git a/integration/tests_ok/assert_header.py b/integration/tests_ok/assert_header.py index c4e81d2fc8f..c42e633a19b 100644 --- a/integration/tests_ok/assert_header.py +++ b/integration/tests_ok/assert_header.py @@ -7,6 +7,7 @@ def assert_header(): resp = make_response() resp.headers["Header1"] = "value1" resp.headers["ETag"] = '"33a64df551425fcc55e4d42a148795d9f25f89d4"' + resp.headers["Expires"] = "Wed, 21 Oct 2015 07:28:00 GMT" resp.set_cookie("cookie1", "value1") resp.set_cookie("cookie2", "value2") resp.set_cookie("cookie3", "value3") diff --git a/packages/hurl/src/json/value.rs b/packages/hurl/src/json/value.rs index 35128181e24..24a898df2a9 100644 --- a/packages/hurl/src/json/value.rs +++ b/packages/hurl/src/json/value.rs @@ -24,6 +24,7 @@ impl Value { pub fn to_json(&self) -> serde_json::Value { match self { Value::Bool(v) => serde_json::Value::Bool(*v), + Value::Date(v) => serde_json::Value::String(v.to_string()), Value::Integer(v) => serde_json::Value::Number(serde_json::Number::from(*v)), Value::Float(f) => serde_json::Value::Number(serde_json::Number::from_f64(*f).unwrap()), Value::String(s) => serde_json::Value::String(s.clone()), diff --git a/packages/hurl/src/runner/filter.rs b/packages/hurl/src/runner/filter.rs index 12d35d20191..63323bdb368 100644 --- a/packages/hurl/src/runner/filter.rs +++ b/packages/hurl/src/runner/filter.rs @@ -15,6 +15,7 @@ * limitations under the License. * */ +use chrono::NaiveDateTime; use std::collections::HashMap; use percent_encoding::AsciiSet; @@ -49,6 +50,9 @@ fn eval_filter( ) -> Result { match &filter.value { FilterValue::Count => eval_count(value, &filter.source_info, in_assert), + FilterValue::Format { fmt, .. } => { + eval_format(value, fmt, variables, &filter.source_info, in_assert) + } FilterValue::HtmlEscape => eval_html_escape(value, &filter.source_info, in_assert), FilterValue::HtmlUnescape => eval_html_unescape(value, &filter.source_info, in_assert), FilterValue::Regex { @@ -76,6 +80,9 @@ fn eval_filter( FilterValue::Split { sep, .. } => { eval_split(value, variables, &filter.source_info, in_assert, sep) } + FilterValue::ToDate { fmt, .. } => { + eval_to_date(value, fmt, variables, &filter.source_info, in_assert) + } FilterValue::ToInt => eval_to_int(value, &filter.source_info, in_assert), FilterValue::UrlDecode => eval_url_decode(value, &filter.source_info, in_assert), FilterValue::UrlEncode => eval_url_encode(value, &filter.source_info, in_assert), @@ -127,6 +134,28 @@ fn eval_count(value: &Value, source_info: &SourceInfo, assert: bool) -> Result, + source_info: &SourceInfo, + assert: bool, +) -> Result { + let fmt = eval_template(fmt, variables)?; + + match value { + Value::Date(value) => { + let formatted = format!("{}", value.format(fmt.as_str())); + Ok(Value::String(formatted)) + } + v => Err(Error { + source_info: source_info.clone(), + inner: RunnerError::FilterInvalidInput(v._type()), + assert, + }), + } +} + // does not encode "/" // like Jinja template (https://jinja.palletsprojects.com/en/3.1.x/templates/#jinja-filters.urlencode) fn eval_url_encode(value: &Value, source_info: &SourceInfo, assert: bool) -> Result { @@ -269,6 +298,32 @@ fn eval_split( } } +fn eval_to_date( + value: &Value, + fmt: &Template, + variables: &HashMap, + source_info: &SourceInfo, + assert: bool, +) -> Result { + let fmt = eval_template(fmt, variables)?; + + match value { + Value::String(v) => match NaiveDateTime::parse_from_str(v, fmt.as_str()) { + Ok(v) => Ok(Value::Date(v.and_local_timezone(chrono::Utc).unwrap())), + Err(_) => Err(Error { + source_info: source_info.clone(), + inner: RunnerError::FilterInvalidInput(value.display()), + assert, + }), + }, + v => Err(Error { + source_info: source_info.clone(), + inner: RunnerError::FilterInvalidInput(v.display()), + assert, + }), + } +} + fn eval_to_int(value: &Value, source_info: &SourceInfo, assert: bool) -> Result { match value { Value::Integer(v) => Ok(Value::Integer(*v)), @@ -291,9 +346,10 @@ fn eval_to_int(value: &Value, source_info: &SourceInfo, assert: bool) -> Result< #[cfg(test)] pub mod tests { - use hurl_core::ast::{FilterValue, SourceInfo, Template, TemplateElement, Whitespace}; - use super::*; + use chrono::offset::Utc; + use chrono::prelude::*; + use hurl_core::ast::{FilterValue, SourceInfo, Template, TemplateElement, Whitespace}; pub fn filter_count() -> Filter { Filter { @@ -351,6 +407,42 @@ pub mod tests { ); } + #[test] + pub fn eval_filter_format() { + // let naivedatetime_utc = NaiveDate::from_ymd_opt(2000, 1, 12).unwrap().and_hms_opt(2, 0, 0).unwrap(); + //let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + + let variables = HashMap::new(); + let whitespace = Whitespace { + value: String::from(""), + source_info: SourceInfo::new(0, 0, 0, 0), + }; + let filter = Filter { + source_info: SourceInfo::new(1, 1, 1, 20), + value: FilterValue::Format { + space0: whitespace, + fmt: Template { + delimiter: None, + elements: vec![TemplateElement::String { + value: "%d/%m/%Y %H:%M".to_string(), + encoded: "%d/%m/%Y %H:%M".to_string(), + }], + source_info: SourceInfo::new(1, 7, 1, 20), + }, + }, + }; + assert_eq!( + eval_filter( + &filter, + &Value::Date(Utc.with_ymd_and_hms(2017, 04, 02, 12, 50, 32).unwrap()), + &variables, + false, + ) + .unwrap(), + Value::String("02/04/2017 12:50".to_string()) + ); + } + #[test] fn eval_filter_regex() { // regex "Hello (.*)!" @@ -711,4 +803,77 @@ pub mod tests { ]) ); } + + #[test] + pub fn eval_filter_to_date() { + let variables = HashMap::new(); + + let filter = Filter { + source_info: SourceInfo::new(1, 1, 1, 1), + value: FilterValue::ToDate { + fmt: Template { + delimiter: Some('"'), + elements: vec![TemplateElement::String { + value: "%Y %b %d %H:%M:%S%.3f %z".to_string(), + encoded: "%Y %b %d %H:%M:%S%.3f %z".to_string(), + }], + source_info: SourceInfo::new(0, 0, 0, 0), + }, + space0: Whitespace { + value: String::from(""), + source_info: SourceInfo::new(0, 0, 0, 0), + }, + }, + }; + + let naivedatetime_utc = NaiveDate::from_ymd_opt(1983, 4, 13) + .unwrap() + .and_hms_micro_opt(12, 9, 14, 274000) + .unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + assert_eq!( + eval_filter( + &filter, + &Value::String("1983 Apr 13 12:09:14.274 +0000".to_string()), + &variables, + false + ) + .unwrap(), + Value::Date(datetime_utc) + ); + + let filter = Filter { + source_info: SourceInfo::new(1, 1, 1, 1), + value: FilterValue::ToDate { + fmt: Template { + delimiter: Some('"'), + elements: vec![TemplateElement::String { + value: "%a, %d %b %Y %H:%M:%S GMT".to_string(), + encoded: "%a, %d %b %Y %H:%M:%S GMT".to_string(), + }], + source_info: SourceInfo::new(0, 0, 0, 0), + }, + space0: Whitespace { + value: String::from(""), + source_info: SourceInfo::new(0, 0, 0, 0), + }, + }, + }; + + let naivedatetime_utc = NaiveDate::from_ymd_opt(2015, 10, 21) + .unwrap() + .and_hms_opt(7, 28, 0) + .unwrap(); + let datetime_utc = DateTime::::from_utc(naivedatetime_utc, Utc); + assert_eq!( + eval_filter( + &filter, + &Value::String("Wed, 21 Oct 2015 07:28:00 GMT".to_string()), + &variables, + false + ) + .unwrap(), + Value::Date(datetime_utc) + ); + } } diff --git a/packages/hurl/src/runner/predicate.rs b/packages/hurl/src/runner/predicate.rs index 074a4724096..3e21d4a7b6c 100644 --- a/packages/hurl/src/runner/predicate.rs +++ b/packages/hurl/src/runner/predicate.rs @@ -94,6 +94,7 @@ impl Value { pub fn display(&self) -> String { match self { Value::Bool(v) => format!("bool <{v}>"), + Value::Date(v) => format!("date <{v}>"), Value::Integer(v) => format!("int <{v}>"), Value::String(v) => format!("string <{v}>"), Value::Float(f) => format!("float <{}>", format_float(*f)), @@ -139,6 +140,7 @@ impl Value { match self { Value::Bool(value) => format!("bool <{value}>"), Value::Bytes(values) => format!("list of size {}", values.len()), + Value::Date(value) => format!("date <{value}>"), Value::Float(f) => format!("float <{}>", format_float(*f)), Value::Integer(value) => format!("integer <{value}>"), Value::List(value) => format!("list of size {}", value.len()), diff --git a/packages/hurl/src/runner/value.rs b/packages/hurl/src/runner/value.rs index 196747b74a3..6418d56cf78 100644 --- a/packages/hurl/src/runner/value.rs +++ b/packages/hurl/src/runner/value.rs @@ -25,6 +25,7 @@ use std::fmt; pub enum Value { Bool(bool), Bytes(Vec), + Date(chrono::DateTime), Float(f64), Integer(i64), List(Vec), @@ -50,6 +51,7 @@ impl PartialEq for Value { (Value::Object(v1), Value::Object(v2)) => v1 == v2, (Value::String(v1), Value::String(v2)) => v1 == v2, (Value::Unit, Value::Unit) => true, + (Value::Date(v1), Value::Date(v2)) => v1 == v2, _ => false, } } @@ -62,6 +64,7 @@ impl fmt::Display for Value { let value = match self { Value::Integer(x) => x.to_string(), Value::Bool(x) => x.to_string(), + Value::Date(v) => v.to_string(), Value::Float(f) => format_float(*f), Value::String(x) => x.clone(), Value::List(values) => { @@ -95,6 +98,7 @@ impl Value { match self { Value::Integer(_) => "integer".to_string(), Value::Bool(_) => "boolean".to_string(), + Value::Date(_) => "date".to_string(), Value::Float(_) => "float".to_string(), Value::String(_) => "string".to_string(), Value::List(_) => "list".to_string(), diff --git a/packages/hurl_core/src/ast/core.rs b/packages/hurl_core/src/ast/core.rs index d4df89f780a..9c97636a6bb 100644 --- a/packages/hurl_core/src/ast/core.rs +++ b/packages/hurl_core/src/ast/core.rs @@ -873,6 +873,10 @@ pub struct Filter { #[derive(Clone, Debug, PartialEq, Eq)] pub enum FilterValue { Count, + Format { + space0: Whitespace, + fmt: Template, + }, HtmlEscape, HtmlUnescape, Nth { @@ -893,6 +897,10 @@ pub enum FilterValue { space0: Whitespace, sep: Template, }, + ToDate { + space0: Whitespace, + fmt: Template, + }, ToInt, UrlDecode, UrlEncode, diff --git a/packages/hurl_core/src/format/html.rs b/packages/hurl_core/src/format/html.rs index 18baf1f82ee..d1556001e5b 100644 --- a/packages/hurl_core/src/format/html.rs +++ b/packages/hurl_core/src/format/html.rs @@ -1121,6 +1121,12 @@ impl Htmlable for FilterValue { fn to_html(&self) -> String { match self { FilterValue::Count => "count".to_string(), + FilterValue::Format { space0, fmt } => { + let mut buffer = "format".to_string(); + buffer.push_str(space0.to_html().as_str()); + buffer.push_str(fmt.to_html().as_str()); + buffer + } FilterValue::HtmlEscape => "htmlEscape".to_string(), FilterValue::HtmlUnescape => { "htmlUnescape".to_string() @@ -1157,6 +1163,13 @@ impl Htmlable for FilterValue { buffer.push_str(sep.to_html().as_str()); buffer } + FilterValue::ToDate { space0, fmt } => { + let mut buffer = "".to_string(); + buffer.push_str("toDate"); + buffer.push_str(space0.to_html().as_str()); + buffer.push_str(fmt.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 2e875da4e6a..f663e30519b 100644 --- a/packages/hurl_core/src/parser/filter.rs +++ b/packages/hurl_core/src/parser/filter.rs @@ -52,6 +52,7 @@ pub fn filter(reader: &mut Reader) -> ParseResult<'static, Filter> { let value = choice( &[ count_filter, + format_filter, html_decode_filter, html_encode_filter, nth_filter, @@ -59,6 +60,7 @@ pub fn filter(reader: &mut Reader) -> ParseResult<'static, Filter> { replace_filter, split_filter, to_int_filter, + to_date_filter, url_decode_filter, url_encode_filter, ], @@ -87,6 +89,13 @@ fn count_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> { Ok(FilterValue::Count) } +fn format_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> { + try_literal("format", reader)?; + let space0 = one_or_more_spaces(reader)?; + let fmt = quoted_template(reader)?; + Ok(FilterValue::Format { space0, fmt }) +} + fn html_encode_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> { try_literal("htmlEscape", reader)?; Ok(FilterValue::HtmlEscape) @@ -132,6 +141,13 @@ fn split_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> { Ok(FilterValue::Split { space0, sep }) } +fn to_date_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> { + try_literal("toDate", reader)?; + let space0 = one_or_more_spaces(reader)?; + let fmt = quoted_template(reader)?; + Ok(FilterValue::ToDate { space0, fmt }) +} + fn to_int_filter(reader: &mut Reader) -> ParseResult<'static, FilterValue> { try_literal("toInt", reader)?; Ok(FilterValue::ToInt) diff --git a/packages/hurlfmt/src/format/json.rs b/packages/hurlfmt/src/format/json.rs index 6230d55e227..ec3374ae824 100644 --- a/packages/hurlfmt/src/format/json.rs +++ b/packages/hurlfmt/src/format/json.rs @@ -529,6 +529,10 @@ impl ToJson for FilterValue { FilterValue::Count => { attributes.push(("type".to_string(), JValue::String("count".to_string()))); } + FilterValue::Format { fmt, .. } => { + attributes.push(("type".to_string(), JValue::String("format".to_string()))); + attributes.push(("fmt".to_string(), JValue::String(fmt.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()))); @@ -568,6 +572,10 @@ impl ToJson for FilterValue { attributes.push(("type".to_string(), JValue::String("split".to_string()))); attributes.push(("sep".to_string(), JValue::String(sep.to_string()))); } + FilterValue::ToDate { fmt, .. } => { + attributes.push(("type".to_string(), JValue::String("toDate".to_string()))); + attributes.push(("fmt".to_string(), JValue::String(fmt.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 7ddb428fdda..151c83fede7 100644 --- a/packages/hurlfmt/src/format/token.rs +++ b/packages/hurlfmt/src/format/token.rs @@ -1144,6 +1144,12 @@ impl Tokenizable for Filter { fn tokenize(&self) -> Vec { match self.value.clone() { FilterValue::Count => vec![Token::FilterType(String::from("count"))], + FilterValue::Format { space0, fmt } => { + let mut tokens: Vec = vec![Token::FilterType(String::from("format"))]; + tokens.append(&mut space0.tokenize()); + tokens.append(&mut fmt.tokenize()); + tokens + } FilterValue::HtmlEscape => vec![Token::FilterType(String::from("htmlEscape"))], FilterValue::HtmlUnescape => { vec![Token::FilterType(String::from("htmlUnescape"))] @@ -1181,6 +1187,12 @@ impl Tokenizable for Filter { tokens.append(&mut sep.tokenize()); tokens } + FilterValue::ToDate { space0, fmt } => { + let mut tokens: Vec = vec![Token::FilterType(String::from("toDate"))]; + tokens.append(&mut space0.tokenize()); + tokens.append(&mut fmt.tokenize()); + tokens + } FilterValue::ToInt => vec![Token::FilterType(String::from("toInt"))], } }