From b83bbf67eda7d355bf15cf56d2ea374f24f9fa37 Mon Sep 17 00:00:00 2001 From: Fabrice Reix Date: Wed, 26 Jan 2022 22:10:48 +0100 Subject: [PATCH] Add Regex predicate value --- integration/tests/assert_match.html | 10 +- integration/tests/assert_match.hurl | 9 +- integration/tests/assert_match.json | 2 +- integration/tests/assert_match.out | 4 +- integration/tests/assert_match.py | 4 +- .../tests_error_parser/invalid_regex.err | 7 + .../tests_error_parser/invalid_regex.exit | 1 + .../tests_error_parser/invalid_regex.hurl | 4 + packages/hurl/src/json/value.rs | 1 + packages/hurl/src/runner/predicate.rs | 65 ++++++--- packages/hurl/src/runner/predicate_value.rs | 1 + packages/hurl/src/runner/value.rs | 7 + packages/hurl_core/src/ast/core.rs | 14 ++ packages/hurl_core/src/ast/display.rs | 6 + packages/hurl_core/src/error/mod.rs | 2 +- packages/hurl_core/src/format/html.rs | 7 + packages/hurl_core/src/parser/error.rs | 2 +- packages/hurl_core/src/parser/predicate.rs | 2 +- .../hurl_core/src/parser/predicate_value.rs | 4 + packages/hurl_core/src/parser/primitives.rs | 125 ++++++++++++++++++ packages/hurlfmt/src/format/json.rs | 3 + packages/hurlfmt/src/format/token.rs | 8 ++ packages/hurlfmt/src/linter/rules.rs | 1 + 23 files changed, 259 insertions(+), 30 deletions(-) create mode 100644 integration/tests_error_parser/invalid_regex.err create mode 100644 integration/tests_error_parser/invalid_regex.exit create mode 100644 integration/tests_error_parser/invalid_regex.hurl diff --git a/integration/tests/assert_match.html b/integration/tests/assert_match.html index e469050e9..4e0104a3c 100644 --- a/integration/tests/assert_match.html +++ b/integration/tests/assert_match.html @@ -2,7 +2,9 @@ HTTP/1.0 200 [Asserts] jsonpath "$.date1" matches "\\d{4}-\\d{2}-\\d{2}" -jsonpath "$.date2" matches "\\d{4}-\\d{2}-\\d{2}" -jsonpath "$.date1" matches "^\\d{4}-\\d{2}-\\d{2}$" -jsonpath "$.date2" not matches "^\\d{4}-\\d{2}-\\d{2}$" - \ No newline at end of file +jsonpath "$.date1" matches /\d{4}-\d{2}-\d{2}/ +jsonpath "$.date2" matches /\d{4}-\d{2}-\d{2}/ +jsonpath "$.date1" matches /^\d{4}-\d{2}-\d{2}$/ +jsonpath "$.date2" not matches /^\d{4}-\d{2}-\d{2}$/ +jsonpath "$.path1" matches /aa\/bb/ +jsonpath "$.path2" matches /aa\\bb/ \ No newline at end of file diff --git a/integration/tests/assert_match.hurl b/integration/tests/assert_match.hurl index 628ceab3b..7383d1bed 100644 --- a/integration/tests/assert_match.hurl +++ b/integration/tests/assert_match.hurl @@ -2,6 +2,9 @@ GET http://localhost:8000/assert-match HTTP/1.0 200 [Asserts] jsonpath "$.date1" matches "\\d{4}-\\d{2}-\\d{2}" -jsonpath "$.date2" matches "\\d{4}-\\d{2}-\\d{2}" -jsonpath "$.date1" matches "^\\d{4}-\\d{2}-\\d{2}$" -jsonpath "$.date2" not matches "^\\d{4}-\\d{2}-\\d{2}$" +jsonpath "$.date1" matches /\d{4}-\d{2}-\d{2}/ +jsonpath "$.date2" matches /\d{4}-\d{2}-\d{2}/ +jsonpath "$.date1" matches /^\d{4}-\d{2}-\d{2}$/ +jsonpath "$.date2" not matches /^\d{4}-\d{2}-\d{2}$/ +jsonpath "$.path1" matches /aa\/bb/ +jsonpath "$.path2" matches /aa\\bb/ \ No newline at end of file diff --git a/integration/tests/assert_match.json b/integration/tests/assert_match.json index 3010c621d..c69baf4a1 100644 --- a/integration/tests/assert_match.json +++ b/integration/tests/assert_match.json @@ -1 +1 @@ -{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/assert-match"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.date1"},"predicate":{"type":"match","value":"\\d{4}-\\d{2}-\\d{2}"}},{"query":{"type":"jsonpath","expr":"$.date2"},"predicate":{"type":"match","value":"\\d{4}-\\d{2}-\\d{2}"}},{"query":{"type":"jsonpath","expr":"$.date1"},"predicate":{"type":"match","value":"^\\d{4}-\\d{2}-\\d{2}$"}},{"query":{"type":"jsonpath","expr":"$.date2"},"predicate":{"not":true,"type":"match","value":"^\\d{4}-\\d{2}-\\d{2}$"}}]}}]} +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/assert-match"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"jsonpath","expr":"$.date1"},"predicate":{"type":"match","value":"\\d{4}-\\d{2}-\\d{2}"}},{"query":{"type":"jsonpath","expr":"$.date1"},"predicate":{"type":"match","value":"\\d{4}-\\d{2}-\\d{2}","encoding":"regex"}},{"query":{"type":"jsonpath","expr":"$.date2"},"predicate":{"type":"match","value":"\\d{4}-\\d{2}-\\d{2}","encoding":"regex"}},{"query":{"type":"jsonpath","expr":"$.date1"},"predicate":{"type":"match","value":"^\\d{4}-\\d{2}-\\d{2}$","encoding":"regex"}},{"query":{"type":"jsonpath","expr":"$.date2"},"predicate":{"not":true,"type":"match","value":"^\\d{4}-\\d{2}-\\d{2}$","encoding":"regex"}},{"query":{"type":"jsonpath","expr":"$.path1"},"predicate":{"type":"match","value":"aa/bb","encoding":"regex"}},{"query":{"type":"jsonpath","expr":"$.path2"},"predicate":{"type":"match","value":"aa\\\\bb","encoding":"regex"}}]}}]} \ No newline at end of file diff --git a/integration/tests/assert_match.out b/integration/tests/assert_match.out index d1c437099..42f62fd73 100644 --- a/integration/tests/assert_match.out +++ b/integration/tests/assert_match.out @@ -1,4 +1,6 @@ { "date1": "2014-01-01", - "date2": "x2014-01-01" + "date2": "x2014-01-01", + "path1": "aa/bb", + "path2": "aa\\bb" } \ No newline at end of file diff --git a/integration/tests/assert_match.py b/integration/tests/assert_match.py index 6f9372c08..2c8148a6a 100644 --- a/integration/tests/assert_match.py +++ b/integration/tests/assert_match.py @@ -5,7 +5,9 @@ from flask import Response def assert_match(): return Response('''{ "date1": "2014-01-01", - "date2": "x2014-01-01" + "date2": "x2014-01-01", + "path1": "aa/bb", + "path2": "aa\\\\bb" }''', mimetype='application/json') diff --git a/integration/tests_error_parser/invalid_regex.err b/integration/tests_error_parser/invalid_regex.err new file mode 100644 index 000000000..6054fabe3 --- /dev/null +++ b/integration/tests_error_parser/invalid_regex.err @@ -0,0 +1,7 @@ +error: Parsing regex + --> tests_error_parser/invalid_regex.hurl:4:23 + | + 4 | jsonpath "$.data" == /aa{a}/ + | ^ Invalid Regex expression: repetition quantifier expects a valid decimal + | + diff --git a/integration/tests_error_parser/invalid_regex.exit b/integration/tests_error_parser/invalid_regex.exit new file mode 100644 index 000000000..d8263ee98 --- /dev/null +++ b/integration/tests_error_parser/invalid_regex.exit @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/integration/tests_error_parser/invalid_regex.hurl b/integration/tests_error_parser/invalid_regex.hurl new file mode 100644 index 000000000..fe0c2fea7 --- /dev/null +++ b/integration/tests_error_parser/invalid_regex.hurl @@ -0,0 +1,4 @@ +GET http://localhost:8000/unused +HTTP/1.1 200 +[Asserts] +jsonpath "$.data" == /aa{a}/ diff --git a/packages/hurl/src/json/value.rs b/packages/hurl/src/json/value.rs index e58bf9a55..c39bd1fa6 100644 --- a/packages/hurl/src/json/value.rs +++ b/packages/hurl/src/json/value.rs @@ -51,6 +51,7 @@ impl Value { serde_json::Value::String(encoded) } Value::Null => serde_json::Value::Null, + Value::Regex(value) => serde_json::Value::String(value.to_string()), Value::Unit => todo!("how to serialize that in json?"), } } diff --git a/packages/hurl/src/runner/predicate.rs b/packages/hurl/src/runner/predicate.rs index e19e14723..358d19e82 100644 --- a/packages/hurl/src/runner/predicate.rs +++ b/packages/hurl/src/runner/predicate.rs @@ -17,7 +17,7 @@ */ use std::collections::HashMap; -use regex::Regex; +use regex; use hurl_core::ast::*; @@ -109,6 +109,7 @@ impl Value { Value::Bytes(value) => format!("byte array <{}>", hex::encode(value)), Value::Null => "null".to_string(), Value::Unit => "unit".to_string(), + Value::Regex(value) => format!("regex <{}>", value.as_str()), } } } @@ -145,6 +146,7 @@ impl Value { Value::Object(values) => format!("list of size {}", values.len()), Value::String(value) => format!("string <{}>", value), Value::Unit => "something".to_string(), + Value::Regex(value) => format!("regex <{}>", value), } } } @@ -409,33 +411,34 @@ fn eval_something( PredicateFuncValue::Match { value: expected, .. } => { - let template = if let PredicateValue::String(template) = expected { - template - } else { - panic!("expect a string predicate value") - }; - let expected = eval_template(&template, variables)?; - let regex = match Regex::new(expected.as_str()) { - Ok(re) => re, - Err(_) => { - return Err(Error { - source_info: predicate_func.source_info.clone(), - inner: RunnerError::InvalidRegex(), - assert: false, - }); + let regex = match expected { + PredicateValue::String(template) => { + let expected = eval_template(&template, variables)?; + match regex::Regex::new(expected.as_str()) { + Ok(re) => re, + Err(_) => { + return Err(Error { + source_info: predicate_func.source_info.clone(), + inner: RunnerError::InvalidRegex(), + assert: false, + }); + } + } } + PredicateValue::Regex(regex) => regex.inner, + _ => panic!("expect a string predicate value"), // should have failed in parsing }; match value.clone() { Value::String(actual) => Ok(AssertResult { success: regex.is_match(actual.as_str()), actual: value.display(), - expected: format!("matches regex <{}>", expected), + expected: format!("matches regex <{}>", regex), type_mismatch: false, }), _ => Ok(AssertResult { success: false, actual: value.display(), - expected: format!("matches regex <{}>", expected), + expected: format!("matches regex <{}>", regex), type_mismatch: true, }), } @@ -1556,4 +1559,32 @@ mod tests { let variables = HashMap::new(); assert!(eval_predicate(predicate, &variables, None).is_ok()); } + + #[test] + fn test_predicate_match() { + let variables = HashMap::new(); + let whitespace = Whitespace { + value: String::from(" "), + source_info: SourceInfo::init(0, 0, 0, 0), + }; + // // a float can be equals to an int (but the reverse) + let assert_result = eval_something( + PredicateFunc { + value: PredicateFuncValue::Match { + space0: whitespace, + value: PredicateValue::Regex(Regex { + inner: regex::Regex::new(r#"a{3}"#).unwrap(), + }), + }, + source_info: SourceInfo::init(0, 0, 0, 0), + }, + &variables, + Value::String("aa".to_string()), + ) + .unwrap(); + assert!(!assert_result.success); + assert!(!assert_result.type_mismatch); + assert_eq!(assert_result.actual.as_str(), "string "); + assert_eq!(assert_result.expected.as_str(), "matches regex "); + } } diff --git a/packages/hurl/src/runner/predicate_value.rs b/packages/hurl/src/runner/predicate_value.rs index c7cd48929..4a4546650 100644 --- a/packages/hurl/src/runner/predicate_value.rs +++ b/packages/hurl/src/runner/predicate_value.rs @@ -47,5 +47,6 @@ pub fn eval_predicate_value( let value = eval_expr(expr, variables)?; Ok(value) } + PredicateValue::Regex(regex) => Ok(Value::Regex(regex.inner)), } } diff --git a/packages/hurl/src/runner/value.rs b/packages/hurl/src/runner/value.rs index b536a74d9..67d7fc49d 100644 --- a/packages/hurl/src/runner/value.rs +++ b/packages/hurl/src/runner/value.rs @@ -33,6 +33,7 @@ pub enum Value { Object(Vec<(String, Value)>), String(String), Unit, + Regex(regex::Regex), } // You must implement it yourself because of the Float @@ -53,6 +54,7 @@ impl PartialEq for Value { } } } + impl Eq for Value {} impl fmt::Display for Value { @@ -71,6 +73,10 @@ impl fmt::Display for Value { Value::Bytes(v) => format!("hex, {};", hex::encode(v)), Value::Null => "null".to_string(), Value::Unit => "Unit".to_string(), + Value::Regex(x) => { + let s = str::replace(x.as_str(), "/", "\\/"); + format!("/{}/", s) + } }; write!(f, "{}", value) } @@ -97,6 +103,7 @@ impl Value { Value::Bytes(_) => "bytes".to_string(), Value::Null => "null".to_string(), Value::Unit => "unit".to_string(), + Value::Regex(_) => "regex".to_string(), } } diff --git a/packages/hurl_core/src/ast/core.rs b/packages/hurl_core/src/ast/core.rs index 719ca7252..91a8f6c1c 100644 --- a/packages/hurl_core/src/ast/core.rs +++ b/packages/hurl_core/src/ast/core.rs @@ -422,6 +422,7 @@ pub enum PredicateValue { Hex(Hex), Base64(Base64), Expression(Expr), + Regex(Regex), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -600,6 +601,19 @@ pub struct Hex { pub space1: Whitespace, } +// Literal Regex +#[derive(Clone, Debug)] +pub struct Regex { + pub inner: regex::Regex, +} + +impl PartialEq for Regex { + fn eq(&self, other: &Self) -> bool { + self.inner.to_string() == other.inner.to_string() + } +} +impl Eq for Regex {} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct Pos { pub line: usize, diff --git a/packages/hurl_core/src/ast/display.rs b/packages/hurl_core/src/ast/display.rs index 512c0b647..e9ad28793 100644 --- a/packages/hurl_core/src/ast/display.rs +++ b/packages/hurl_core/src/ast/display.rs @@ -137,6 +137,12 @@ impl fmt::Display for Hex { } } +impl fmt::Display for Regex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.inner) + } +} + impl PredicateFuncValue { pub fn name(&self) -> String { match self { diff --git a/packages/hurl_core/src/error/mod.rs b/packages/hurl_core/src/error/mod.rs index 90fb01574..0bdf5b37c 100644 --- a/packages/hurl_core/src/error/mod.rs +++ b/packages/hurl_core/src/error/mod.rs @@ -93,7 +93,7 @@ impl Error for parser::Error { ParseError::Json { .. } => "json error".to_string(), ParseError::Predicate { .. } => "expecting a predicate".to_string(), ParseError::PredicateValue { .. } => "invalid predicate value".to_string(), - ParseError::RegexExpr { .. } => "Invalid Regex expression".to_string(), + ParseError::RegexExpr { message } => format!("Invalid Regex expression: {}", message), ParseError::DuplicateSection { .. } => "The section is already defined".to_string(), ParseError::RequestSection { .. } => { "This is not a valid section for a request".to_string() diff --git a/packages/hurl_core/src/format/html.rs b/packages/hurl_core/src/format/html.rs index f2f3e6ff4..a4e0358fb 100644 --- a/packages/hurl_core/src/format/html.rs +++ b/packages/hurl_core/src/format/html.rs @@ -636,6 +636,7 @@ impl Htmlable for PredicateValue { PredicateValue::Base64(value) => value.to_html(), PredicateValue::Expression(value) => value.to_html(), PredicateValue::Null {} => "null".to_string(), + PredicateValue::Regex(value) => value.to_html(), } } } @@ -797,6 +798,12 @@ impl Htmlable for Hex { } } +impl Htmlable for Regex { + fn to_html(&self) -> String { + let s = str::replace(self.inner.as_str(), "/", "\\/"); + format!("/{}/", s) + } +} impl Htmlable for EncodedString { fn to_html(&self) -> String { format!("{}", self.encoded) diff --git a/packages/hurl_core/src/parser/error.rs b/packages/hurl_core/src/parser/error.rs index 9b13096f4..16a44db95 100644 --- a/packages/hurl_core/src/parser/error.rs +++ b/packages/hurl_core/src/parser/error.rs @@ -43,7 +43,7 @@ pub enum ParseError { Xml {}, Predicate, PredicateValue, - RegexExpr, + RegexExpr { message: String }, Unexpected { character: String }, Eof {}, diff --git a/packages/hurl_core/src/parser/predicate.rs b/packages/hurl_core/src/parser/predicate.rs index a5ddb794c..be09c9b21 100644 --- a/packages/hurl_core/src/parser/predicate.rs +++ b/packages/hurl_core/src/parser/predicate.rs @@ -311,7 +311,7 @@ fn match_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncVal let space0 = one_or_more_spaces(reader)?; let save = reader.state.clone(); let value = predicate_value(reader)?; - if !value.is_string() { + if !matches!(value, PredicateValue::String(_)) && !matches!(value, PredicateValue::Regex(_)) { return Err(Error { pos: save.pos, recoverable: false, diff --git a/packages/hurl_core/src/parser/predicate_value.rs b/packages/hurl_core/src/parser/predicate_value.rs index 9c4b58c9c..8ba30e734 100644 --- a/packages/hurl_core/src/parser/predicate_value.rs +++ b/packages/hurl_core/src/parser/predicate_value.rs @@ -65,6 +65,10 @@ pub fn predicate_value(reader: &mut Reader) -> ParseResult<'static, PredicateVal Ok(value) => Ok(PredicateValue::Raw(value)), Err(e) => Err(e), }, + |p1| match regex(p1) { + Ok(value) => Ok(PredicateValue::Regex(value)), + Err(e) => Err(e), + }, ], reader, ) diff --git a/packages/hurl_core/src/parser/primitives.rs b/packages/hurl_core/src/parser/primitives.rs index 81917c315..979a916f4 100644 --- a/packages/hurl_core/src/parser/primitives.rs +++ b/packages/hurl_core/src/parser/primitives.rs @@ -332,6 +332,71 @@ pub fn hex(reader: &mut Reader) -> ParseResult<'static, Hex> { }) } +pub fn regex(reader: &mut Reader) -> ParseResult<'static, Regex> { + try_literal("/", reader)?; + let start = reader.state.pos.clone(); + let mut s = String::from(""); + + // Hurl escaping / + // in order to avoid terminating the regex + // eg. \a\b/ + // + // Other escaped sequences such as \* are part of the regex expression + // They are not part of the syntax of Hurl itself. + loop { + match reader.read() { + None => { + return Err(Error { + pos: reader.state.pos.clone(), + recoverable: false, + inner: ParseError::Eof {}, + }) + } + Some('/') => break, + Some('\\') => { + if let Some('/') = reader.peek() { + reader.read(); + s.push('/'); + } else { + s.push('\\'); + } + } + Some(c) => s.push(c), + } + } + match regex::Regex::new(s.as_str()) { + Ok(inner) => Ok(Regex { inner }), + Err(e) => { + let message = match e { + regex::Error::Syntax(s) => { + // The regex syntax error from the crate returns a multiline String + // For example + // regex parse error: + // x{a} + // ^ + // error: repetition quantifier expects a valid decimal + // + // To fit nicely in Hurl Error reporting, you need an error message string that does not spread on multiple lines + // You will assume that the error most relevant description is on the last line + let lines = s.split('\n').clone().collect::>(); + let last_line = lines.last().expect("at least one line"); + last_line + .strip_prefix("error: ") + .unwrap_or(last_line) + .to_string() + } + regex::Error::CompiledTooBig(_) => "Size limit exceeded".to_string(), + _ => "unknown".to_string(), + }; + Err(Error { + pos: start, + recoverable: false, + inner: ParseError::RegexExpr { message }, + }) + } + } +} + pub fn null(reader: &mut Reader) -> ParseResult<'static, ()> { try_literal("null", reader) } @@ -1332,6 +1397,66 @@ mod tests { assert_eq!(error.inner, ParseError::OddNumberOfHexDigits {}); } + #[test] + fn test_regex() { + let mut reader = Reader::init(r#"/a{3}/"#); + assert_eq!( + regex(&mut reader).unwrap(), + Regex { + inner: regex::Regex::new(r#"a{3}"#).unwrap() + } + ); + + let mut reader = Reader::init(r#"/a\/b/"#); + assert_eq!( + regex(&mut reader).unwrap(), + Regex { + inner: regex::Regex::new(r#"a/b"#).unwrap() + } + ); + + let mut reader = Reader::init(r#"/a\.b/"#); + assert_eq!( + regex(&mut reader).unwrap(), + Regex { + inner: regex::Regex::new(r#"a\.b"#).unwrap() + } + ); + + let mut reader = Reader::init(r#"/\d{4}-\d{2}-\d{2}/"#); + assert_eq!( + regex(&mut reader).unwrap(), + Regex { + inner: regex::Regex::new(r#"\d{4}-\d{2}-\d{2}"#).unwrap() + } + ); + } + + #[test] + fn test_regex_error() { + let mut reader = Reader::init("xxx"); + let error = regex(&mut reader).err().unwrap(); + assert_eq!(error.pos, Pos { line: 1, column: 1 }); + assert!(error.recoverable); + + let mut reader = Reader::init("/xxx"); + let error = regex(&mut reader).err().unwrap(); + assert_eq!(error.pos, Pos { line: 1, column: 5 }); + assert!(!error.recoverable); + assert_eq!(error.inner, ParseError::Eof {}); + + let mut reader = Reader::init("/x{a}/"); + let error = regex(&mut reader).err().unwrap(); + assert_eq!(error.pos, Pos { line: 1, column: 2 }); + assert!(!error.recoverable); + assert_eq!( + error.inner, + ParseError::RegexExpr { + message: "repetition quantifier expects a valid decimal".to_string() + } + ); + } + #[test] fn test_file() { let mut reader = Reader::init("file,data.xml;"); diff --git a/packages/hurlfmt/src/format/json.rs b/packages/hurlfmt/src/format/json.rs index 3f65543a3..a4fe18430 100644 --- a/packages/hurlfmt/src/format/json.rs +++ b/packages/hurlfmt/src/format/json.rs @@ -465,6 +465,9 @@ fn json_predicate_value(predicate_value: PredicateValue) -> (JValue, Option (JValue::String(value.to_string()), None), + PredicateValue::Regex(value) => { + (JValue::String(value.to_string()), Some("regex".to_string())) + } } } diff --git a/packages/hurlfmt/src/format/token.rs b/packages/hurlfmt/src/format/token.rs index 8dfaf0dbc..2e5d22c38 100644 --- a/packages/hurlfmt/src/format/token.rs +++ b/packages/hurlfmt/src/format/token.rs @@ -653,6 +653,7 @@ impl Tokenizable for PredicateValue { PredicateValue::Hex(value) => vec![Token::String(value.to_string())], PredicateValue::Base64(value) => value.tokenize(), PredicateValue::Expression(value) => value.tokenize(), + PredicateValue::Regex(value) => value.tokenize(), } } } @@ -735,6 +736,13 @@ impl Tokenizable for Expr { } } +impl Tokenizable for Regex { + fn tokenize(&self) -> Vec { + let s = str::replace(self.inner.as_str(), "/", "\\/"); + vec![Token::String(format!("/{}/", s))] + } +} + impl Tokenizable for LineTerminator { fn tokenize(&self) -> Vec { let mut tokens: Vec = vec![]; diff --git a/packages/hurlfmt/src/linter/rules.rs b/packages/hurlfmt/src/linter/rules.rs index caa02694d..9ced232df 100644 --- a/packages/hurlfmt/src/linter/rules.rs +++ b/packages/hurlfmt/src/linter/rules.rs @@ -508,6 +508,7 @@ impl Lintable for PredicateValue { PredicateValue::Hex(value) => PredicateValue::Hex(value.lint()), PredicateValue::Base64(value) => PredicateValue::Base64(value.lint()), PredicateValue::Expression(value) => PredicateValue::Expression(value.clone()), + PredicateValue::Regex(value) => PredicateValue::Regex(value.clone()), } } }