From 93c2fede66bf396d311ae211d6277fb151fbb115 Mon Sep 17 00:00:00 2001 From: Fabrice Reix Date: Wed, 16 Jun 2021 21:13:17 +0200 Subject: [PATCH] Add bytes query and equals predicate for bytearray --- Cargo.lock | 1 + integration/tests/bytes.html | 2 +- integration/tests/bytes.hurl | 6 +- integration/tests/bytes.json | 2 +- packages/hurl/src/runner/predicate.rs | 17 ++++ packages/hurl/src/runner/query.rs | 19 +++++ packages/hurl_core/src/ast/core.rs | 10 +++ packages/hurl_core/src/ast/display.rs | 10 +++ packages/hurl_core/src/format/html.rs | 9 +++ packages/hurl_core/src/parser/predicate.rs | 9 +++ packages/hurl_core/src/parser/primitives.rs | 87 +++++++++++++++++++-- packages/hurl_core/src/parser/query.rs | 6 ++ packages/hurlfmt/Cargo.toml | 1 + packages/hurlfmt/src/format/json.rs | 14 ++++ packages/hurlfmt/src/format/token.rs | 6 ++ packages/hurlfmt/src/linter/rules.rs | 19 +++++ 16 files changed, 205 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4151692df..bf550b45c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,6 +365,7 @@ name = "hurlfmt" version = "1.3.0" dependencies = [ "atty", + "base64", "clap", "colored", "hurl_core", diff --git a/integration/tests/bytes.html b/integration/tests/bytes.html index a1f2c9b61..436ba6f24 100644 --- a/integration/tests/bytes.html +++ b/integration/tests/bytes.html @@ -1 +1 @@ -
GET http://localhost:8000/bytes
HTTP/1.0 200Content-Type: application/octet-stream[Asserts]
#TODO create a bytes query to get e byte array#body countEquals 1
+
GET http://localhost:8000/bytes
HTTP/1.0 200Content-Type: application/octet-stream[Asserts]bytes equals hex,ff;bytes equals 1
\ No newline at end of file diff --git a/integration/tests/bytes.hurl b/integration/tests/bytes.hurl index ada9e4637..bc271b1a3 100644 --- a/integration/tests/bytes.hurl +++ b/integration/tests/bytes.hurl @@ -2,8 +2,6 @@ GET http://localhost:8000/bytes HTTP/1.0 200 Content-Type: application/octet-stream [Asserts] -#TODO create a bytes query to get e byte array -#body countEquals 1 - - +bytes equals hex,ff; +bytes countEquals 1 diff --git a/integration/tests/bytes.json b/integration/tests/bytes.json index 5286d5afd..ee9991581 100644 --- a/integration/tests/bytes.json +++ b/integration/tests/bytes.json @@ -1 +1 @@ -{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/bytes"},"response":{"version":"HTTP/1.0","status":200,"headers":[{"name":"Content-Type","value":"application/octet-stream"}]}}]} +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/bytes"},"response":{"version":"HTTP/1.0","status":200,"headers":[{"name":"Content-Type","value":"application/octet-stream"}],"asserts":[{"query":{"type":"bytes"},"predicate":{"type":"equal","value":{"value":"/w==","encoding":"base64"}}},{"query":{"type":"bytes"},"predicate":{"type":"count","value":1}}]}}]} \ No newline at end of file diff --git a/packages/hurl/src/runner/predicate.rs b/packages/hurl/src/runner/predicate.rs index b5c019b82..9d47658f0 100644 --- a/packages/hurl/src/runner/predicate.rs +++ b/packages/hurl/src/runner/predicate.rs @@ -164,6 +164,9 @@ fn expected( let expected = eval_template(expected, variables)?; Ok(format!("string <{}>", expected)) } + PredicateFuncValue::EqualHex { + value: expected, .. + } => Ok(format!("bytearray <{}>", expected.to_string())), PredicateFuncValue::EqualExpression { value: expected, .. } => { @@ -326,6 +329,14 @@ fn eval_something( Ok(assert_values_equal(value, Value::String(expected))) } + // equals hex + PredicateFuncValue::EqualHex { + value: Hex { + value: expected, .. + }, + .. + } => Ok(assert_values_equal(value, Value::Bytes(expected))), + // equals expression PredicateFuncValue::EqualExpression { value: expected, .. @@ -392,6 +403,12 @@ fn eval_something( expected: expected_value.to_string(), type_mismatch: false, }), + Value::Bytes(data) => Ok(AssertResult { + success: data.len() as u64 == expected_value, + actual: data.len().to_string(), + expected: expected_value.to_string(), + type_mismatch: false, + }), _ => Ok(AssertResult { success: false, actual: value.clone().display(), diff --git a/packages/hurl/src/runner/query.rs b/packages/hurl/src/runner/query.rs index c637ec103..d44001877 100644 --- a/packages/hurl/src/runner/query.rs +++ b/packages/hurl/src/runner/query.rs @@ -203,6 +203,7 @@ pub fn eval_query( QueryValue::Duration {} => Ok(Some(Value::Integer( http_response.duration.as_millis() as i64 ))), + QueryValue::Bytes {} => Ok(Some(Value::Bytes(http_response.body))), } } @@ -1006,4 +1007,22 @@ pub mod tests { assert_eq!(error.source_info, SourceInfo::init(1, 7, 1, 10)); assert_eq!(error.inner, RunnerError::InvalidRegex()); } + + #[test] + fn test_query_bytes() { + let variables = HashMap::new(); + assert_eq!( + eval_query( + Query { + source_info: SourceInfo::init(0, 0, 0, 0), + value: QueryValue::Bytes {}, + }, + &variables, + http::hello_http_response(), + ) + .unwrap() + .unwrap(), + Value::Bytes(String::into_bytes(String::from("Hello World!"))) + ); + } } diff --git a/packages/hurl_core/src/ast/core.rs b/packages/hurl_core/src/ast/core.rs index 091782021..afac8a199 100644 --- a/packages/hurl_core/src/ast/core.rs +++ b/packages/hurl_core/src/ast/core.rs @@ -323,6 +323,7 @@ pub enum QueryValue { name: Template, }, Duration {}, + Bytes {}, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -403,6 +404,7 @@ pub enum PredicateFuncValue { EqualFloat { space0: Whitespace, value: Float }, EqualBool { space0: Whitespace, value: bool }, EqualNull { space0: Whitespace }, + EqualHex { space0: Whitespace, value: Hex }, EqualExpression { space0: Whitespace, value: Expr }, GreaterThanInt { space0: Whitespace, value: i64 }, GreaterThanFloat { space0: Whitespace, value: Float }, @@ -528,6 +530,14 @@ pub enum Bytes { }, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Hex { + pub space0: Whitespace, + pub value: Vec, + pub encoded: String, + pub space1: Whitespace, +} + #[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 3cc6cd25d..92b2b7f33 100644 --- a/packages/hurl_core/src/ast/display.rs +++ b/packages/hurl_core/src/ast/display.rs @@ -131,6 +131,16 @@ impl fmt::Display for CookieAttribute { } } +impl fmt::Display for Hex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "hex,{}{}{};", + self.space0.value, self.encoded, self.space1.value + ) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/hurl_core/src/format/html.rs b/packages/hurl_core/src/format/html.rs index 48846fcb1..3dccad847 100644 --- a/packages/hurl_core/src/format/html.rs +++ b/packages/hurl_core/src/format/html.rs @@ -375,6 +375,9 @@ impl Htmlable for QueryValue { QueryValue::Duration {} => { buffer.push_str("duration"); } + QueryValue::Bytes {} => { + buffer.push_str("bytes"); + } } buffer } @@ -463,6 +466,12 @@ impl Htmlable for PredicateFuncValue { format!("{}", value.to_string()).as_str(), ); } + PredicateFuncValue::EqualHex { space0, value } => { + buffer.push_str("equals"); + buffer.push_str(space0.to_html().as_str()); + buffer + .push_str(format!("{}", value.to_string()).as_str()); + } PredicateFuncValue::GreaterThanInt { space0, value } => { buffer.push_str("greaterThan"); buffer.push_str(space0.to_html().as_str()); diff --git a/packages/hurl_core/src/parser/predicate.rs b/packages/hurl_core/src/parser/predicate.rs index f4f948d9e..0b09a13a2 100644 --- a/packages/hurl_core/src/parser/predicate.rs +++ b/packages/hurl_core/src/parser/predicate.rs @@ -103,6 +103,7 @@ fn equal_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncVal Ok(PredicateValue::Bool { value }) => Ok(PredicateFuncValue::EqualBool { space0, value }), Ok(PredicateValue::Int { value }) => Ok(PredicateFuncValue::EqualInt { space0, value }), Ok(PredicateValue::Float { value }) => Ok(PredicateFuncValue::EqualFloat { space0, value }), + Ok(PredicateValue::Hex { value }) => Ok(PredicateFuncValue::EqualHex { space0, value }), Ok(PredicateValue::Expression { value }) => { Ok(PredicateFuncValue::EqualExpression { space0, value }) } @@ -273,6 +274,9 @@ fn include_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncV Ok(PredicateValue::Template { value }) => { Ok(PredicateFuncValue::IncludeString { space0, value }) } + Ok(PredicateValue::Hex { value: _ }) => { + todo!() + } Ok(PredicateValue::Expression { value }) => { Ok(PredicateFuncValue::IncludeExpression { space0, value }) } @@ -332,6 +336,7 @@ enum PredicateValue { Float { value: Float }, Bool { value: bool }, Template { value: Template }, + Hex { value: Hex }, Expression { value: Expr }, } @@ -354,6 +359,10 @@ fn predicate_value(reader: &mut Reader) -> ParseResult<'static, PredicateValue> Ok(value) => Ok(PredicateValue::Int { value }), Err(e) => Err(e), }, + |p1| match hex(p1) { + Ok(value) => Ok(PredicateValue::Hex { value }), + Err(e) => Err(e), + }, |p1| match expr::parse(p1) { Ok(value) => Ok(PredicateValue::Expression { value }), Err(e) => Err(e), diff --git a/packages/hurl_core/src/parser/primitives.rs b/packages/hurl_core/src/parser/primitives.rs index 14f2cdf43..898e277f2 100644 --- a/packages/hurl_core/src/parser/primitives.rs +++ b/packages/hurl_core/src/parser/primitives.rs @@ -263,6 +263,42 @@ pub fn key_value(reader: &mut Reader) -> ParseResult<'static, KeyValue> { }) } +pub fn hex(reader: &mut Reader) -> ParseResult<'static, Hex> { + try_literal("hex", reader)?; + literal(",", reader)?; + let space0 = zero_or_more_spaces(reader)?; + let mut value: Vec = vec![]; + let start = reader.state.cursor; + let mut current: i32 = -1; + loop { + let s = reader.state.clone(); + match hex_digit(reader) { + Ok(d) => { + if current != -1 { + value.push((current * 16 + d as i32) as u8); + current = -1; + } else { + current = d as i32; + } + } + Err(_) => { + reader.state = s; + break; + } + }; + } + let encoded = reader.from(start); + let space1 = zero_or_more_spaces(reader)?; + literal(";", reader)?; + + Ok(Hex { + space0, + value, + encoded, + space1, + }) +} + pub fn filename(reader: &mut Reader) -> ParseResult<'static, Filename> { // this is an absolure file // that you have to write with a relative name @@ -882,7 +918,7 @@ mod tests { Float { int: 1, decimal: 0, - decimal_digits: 1 + decimal_digits: 1, } ); assert_eq!(reader.state.cursor, 3); @@ -893,7 +929,7 @@ mod tests { Float { int: -1, decimal: 0, - decimal_digits: 1 + decimal_digits: 1, } ); assert_eq!(reader.state.cursor, 4); @@ -904,7 +940,7 @@ mod tests { Float { int: 1, decimal: 100_000_000_000_000_000, - decimal_digits: 1 + decimal_digits: 1, } ); assert_eq!(reader.state.cursor, 3); @@ -915,7 +951,7 @@ mod tests { Float { int: 1, decimal: 100_000_000_000_000_000, - decimal_digits: 3 + decimal_digits: 3, } ); assert_eq!(reader.state.cursor, 5); @@ -926,7 +962,7 @@ mod tests { Float { int: 1, decimal: 10_000_000_000_000_000, - decimal_digits: 2 + decimal_digits: 2, } ); assert_eq!(reader.state.cursor, 4); @@ -937,7 +973,7 @@ mod tests { Float { int: 1, decimal: 10_000_000_000_000_000, - decimal_digits: 3 + decimal_digits: 3, } ); assert_eq!(reader.state.cursor, 5); @@ -948,7 +984,7 @@ mod tests { Float { int: 0, decimal: 333_333_333_333_333_333, - decimal_digits: 18 + decimal_digits: 18, } ); assert_eq!(reader.state.cursor, 21); @@ -1254,4 +1290,41 @@ mod tests { assert_eq!(error.inner, ParseError::HexDigit {}); assert_eq!(error.recoverable, true); } + + #[test] + fn test_hex() { + let mut reader = Reader::init("hex, ff;"); + assert_eq!( + hex(&mut reader).unwrap(), + Hex { + space0: Whitespace { + value: " ".to_string(), + source_info: SourceInfo::init(1, 5, 1, 6) + }, + value: vec![255], + encoded: "ff".to_string(), + space1: Whitespace { + value: "".to_string(), + source_info: SourceInfo::init(1, 8, 1, 8) + }, + } + ); + + let mut reader = Reader::init("hex,010203 ;"); + assert_eq!( + hex(&mut reader).unwrap(), + Hex { + space0: Whitespace { + value: "".to_string(), + source_info: SourceInfo::init(1, 5, 1, 5) + }, + value: vec![1, 2, 3], + encoded: "010203".to_string(), + space1: Whitespace { + value: " ".to_string(), + source_info: SourceInfo::init(1, 11, 1, 12) + }, + } + ); + } } diff --git a/packages/hurl_core/src/parser/query.rs b/packages/hurl_core/src/parser/query.rs index bfd41cd52..02d65e66a 100644 --- a/packages/hurl_core/src/parser/query.rs +++ b/packages/hurl_core/src/parser/query.rs @@ -56,6 +56,7 @@ fn query_value(reader: &mut Reader) -> ParseResult<'static, QueryValue> { regex_query, variable_query, duration_query, + bytes_query, ], reader, ) @@ -154,6 +155,11 @@ fn duration_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> { Ok(QueryValue::Duration {}) } +fn bytes_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> { + try_literal("bytes", reader)?; + Ok(QueryValue::Bytes {}) +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/hurlfmt/Cargo.toml b/packages/hurlfmt/Cargo.toml index aa01e926c..c4f16f226 100644 --- a/packages/hurlfmt/Cargo.toml +++ b/packages/hurlfmt/Cargo.toml @@ -15,6 +15,7 @@ strict = [] [dependencies] atty = "0.2.13" +base64 = "0.11.0" clap = "2.33.0" colored = "2" hurl_core = { version = "1.1.0", path = "../hurl_core" } diff --git a/packages/hurlfmt/src/format/json.rs b/packages/hurlfmt/src/format/json.rs index dbcd55acc..5948eef29 100644 --- a/packages/hurlfmt/src/format/json.rs +++ b/packages/hurlfmt/src/format/json.rs @@ -293,6 +293,9 @@ impl ToJson for QueryValue { QueryValue::Duration {} => { attributes.push(("type".to_string(), JValue::String("duration".to_string()))); } + QueryValue::Bytes {} => { + attributes.push(("type".to_string(), JValue::String("bytes".to_string()))); + } }; JValue::Object(attributes) } @@ -344,6 +347,17 @@ impl ToJson for Predicate { attributes.push(("type".to_string(), JValue::String("equal".to_string()))); attributes.push(("value".to_string(), JValue::Null)); } + PredicateFuncValue::EqualHex { value, .. } => { + attributes.push(("type".to_string(), JValue::String("equal".to_string()))); + let value = JValue::Object(vec![ + ( + "value".to_string(), + JValue::String(base64::encode(&value.value)), + ), + ("encoding".to_string(), JValue::String("base64".to_string())), + ]); + attributes.push(("value".to_string(), value)); + } PredicateFuncValue::EqualExpression { value, .. } => { attributes.push(("type".to_string(), JValue::String("equal".to_string()))); attributes.push(("value".to_string(), JValue::String(value.to_string()))); diff --git a/packages/hurlfmt/src/format/token.rs b/packages/hurlfmt/src/format/token.rs index dec9de423..8ee8ac157 100644 --- a/packages/hurlfmt/src/format/token.rs +++ b/packages/hurlfmt/src/format/token.rs @@ -463,6 +463,7 @@ impl Tokenizable for Query { add_tokens(&mut tokens, name.tokenize()); } QueryValue::Duration {} => tokens.push(Token::QueryType(String::from("duration"))), + QueryValue::Bytes {} => tokens.push(Token::QueryType(String::from("bytes"))), } tokens } @@ -551,6 +552,11 @@ impl Tokenizable for PredicateFuncValue { add_tokens(&mut tokens, space0.tokenize()); tokens.push(Token::Number(value.to_string())); } + PredicateFuncValue::EqualHex { space0, value } => { + tokens.push(Token::PredicateType(String::from("equals"))); + add_tokens(&mut tokens, space0.tokenize()); + tokens.push(Token::String(value.to_string())); + } PredicateFuncValue::EqualExpression { space0, value } => { tokens.push(Token::PredicateType(String::from("equals"))); add_tokens(&mut tokens, space0.tokenize()); diff --git a/packages/hurlfmt/src/linter/rules.rs b/packages/hurlfmt/src/linter/rules.rs index 58dd82757..5eccf20c6 100644 --- a/packages/hurlfmt/src/linter/rules.rs +++ b/packages/hurlfmt/src/linter/rules.rs @@ -296,6 +296,7 @@ impl Lintable for QueryValue { space0: one_whitespace(), }, QueryValue::Duration {} => QueryValue::Duration {}, + QueryValue::Bytes {} => QueryValue::Bytes {}, } } } @@ -403,6 +404,10 @@ impl Lintable for PredicateFuncValue { space0: one_whitespace(), value: value.clone(), }, + PredicateFuncValue::EqualHex { value, .. } => PredicateFuncValue::EqualHex { + space0: one_whitespace(), + value: value.lint(), + }, PredicateFuncValue::EqualExpression { value, .. } => { PredicateFuncValue::EqualExpression { space0: one_whitespace(), @@ -664,6 +669,20 @@ fn one_whitespace() -> Whitespace { source_info: SourceInfo::init(0, 0, 0, 0), } } +impl Lintable for Hex { + fn errors(&self) -> Vec { + unimplemented!() + } + + fn lint(&self) -> Hex { + Hex { + space0: one_whitespace(), + value: self.value.clone(), + encoded: self.encoded.clone(), + space1: empty_whitespace(), + } + } +} impl Lintable for LineTerminator { fn errors(&self) -> Vec {