From 633500ebbd7b6c9ac66a0fb5182d896c723ed169 Mon Sep 17 00:00:00 2001 From: jcamiel Date: Fri, 12 Aug 2022 20:07:14 +0200 Subject: [PATCH] Add verbose entry option. --- docs/spec/hurl.grammar | 7 +- .../{options.curl => option_insecure.curl} | 0 .../{options.html => option_insecure.html} | 0 .../{options.hurl => option_insecure.hurl} | 0 .../{options.json => option_insecure.json} | 0 .../{options.out => option_insecure.out} | 0 .../tests_ok/option_verbose.err.pattern | 28 ++++++++ integration/tests_ok/option_verbose.html | 10 +++ integration/tests_ok/option_verbose.hurl | 10 +++ integration/tests_ok/option_verbose.json | 1 + integration/tests_ok/option_verbose.out | 1 + integration/tests_ok/very_verbose.err.pattern | 14 ++-- packages/hurl/src/runner/entry.rs | 66 ++++++++++++++----- packages/hurl/src/runner/hurl_file.rs | 25 +++++-- packages/hurl/src/util/logger.rs | 20 ++++-- packages/hurl_core/src/ast/core.rs | 11 ++++ packages/hurl_core/src/format/html.rs | 18 +++++ packages/hurl_core/src/parser/sections.rs | 24 ++++++- packages/hurlfmt/src/format/token.rs | 22 +++++++ 19 files changed, 218 insertions(+), 39 deletions(-) rename integration/tests_ok/{options.curl => option_insecure.curl} (100%) rename integration/tests_ok/{options.html => option_insecure.html} (100%) rename integration/tests_ok/{options.hurl => option_insecure.hurl} (100%) rename integration/tests_ok/{options.json => option_insecure.json} (100%) rename integration/tests_ok/{options.out => option_insecure.out} (100%) create mode 100644 integration/tests_ok/option_verbose.err.pattern create mode 100644 integration/tests_ok/option_verbose.html create mode 100644 integration/tests_ok/option_verbose.hurl create mode 100644 integration/tests_ok/option_verbose.json create mode 100644 integration/tests_ok/option_verbose.out diff --git a/docs/spec/hurl.grammar b/docs/spec/hurl.grammar index 8b1d68713..db6252599 100644 --- a/docs/spec/hurl.grammar +++ b/docs/spec/hurl.grammar @@ -119,12 +119,15 @@ assert: option: lt* - (insecure-option | ca-certificate-option) + (insecure-option | ca-certificate-option | verbose-option) -insecure-option: "insecure" ":" (boolean | template) lt +insecure-option: "insecure" ":" boolean lt ca-certificate-option: "cacert" ":" filename lt +verbose-option: "verbose" ":" boolean lt + + # Query query: main-query (sp subquery)? diff --git a/integration/tests_ok/options.curl b/integration/tests_ok/option_insecure.curl similarity index 100% rename from integration/tests_ok/options.curl rename to integration/tests_ok/option_insecure.curl diff --git a/integration/tests_ok/options.html b/integration/tests_ok/option_insecure.html similarity index 100% rename from integration/tests_ok/options.html rename to integration/tests_ok/option_insecure.html diff --git a/integration/tests_ok/options.hurl b/integration/tests_ok/option_insecure.hurl similarity index 100% rename from integration/tests_ok/options.hurl rename to integration/tests_ok/option_insecure.hurl diff --git a/integration/tests_ok/options.json b/integration/tests_ok/option_insecure.json similarity index 100% rename from integration/tests_ok/options.json rename to integration/tests_ok/option_insecure.json diff --git a/integration/tests_ok/options.out b/integration/tests_ok/option_insecure.out similarity index 100% rename from integration/tests_ok/options.out rename to integration/tests_ok/option_insecure.out diff --git a/integration/tests_ok/option_verbose.err.pattern b/integration/tests_ok/option_verbose.err.pattern new file mode 100644 index 000000000..6b5f16817 --- /dev/null +++ b/integration/tests_ok/option_verbose.err.pattern @@ -0,0 +1,28 @@ +* ------------------------------------------------------------------------------ +* Executing entry 2 +* +* Entry options: +* verbose: true +* +* Cookie store: +* +* Request: +* GET http://localhost:8000/hello +* +* Request can be run with the following curl command: +* curl 'http://localhost:8000/hello' +* +> GET /hello HTTP/1.1 +> Host: localhost:8000 +> Accept: */* +> User-Agent: hurl/~~~ +> +* Response: +* +< HTTP/1.0 200 OK +< Content-Type: text/html; charset=utf-8 +< Content-Length: 12 +< Server: Flask Server +< Date: ~~~, ~~ ~~~ ~~~~ ~~:~~:~~ GMT +< +* diff --git a/integration/tests_ok/option_verbose.html b/integration/tests_ok/option_verbose.html new file mode 100644 index 000000000..7d4dfb5a8 --- /dev/null +++ b/integration/tests_ok/option_verbose.html @@ -0,0 +1,10 @@ +
GET http://localhost:8000/hello
+
+GET http://localhost:8000/hello
+[Options]
+verbose: true
+
+HTTP/* 200
+```Hello World!```
+
+GET http://localhost:8000/hello
\ No newline at end of file diff --git a/integration/tests_ok/option_verbose.hurl b/integration/tests_ok/option_verbose.hurl new file mode 100644 index 000000000..c614726c3 --- /dev/null +++ b/integration/tests_ok/option_verbose.hurl @@ -0,0 +1,10 @@ +GET http://localhost:8000/hello + +GET http://localhost:8000/hello +[Options] +verbose: true + +HTTP/* 200 +```Hello World!``` + +GET http://localhost:8000/hello \ No newline at end of file diff --git a/integration/tests_ok/option_verbose.json b/integration/tests_ok/option_verbose.json new file mode 100644 index 000000000..3fb9ed69c --- /dev/null +++ b/integration/tests_ok/option_verbose.json @@ -0,0 +1 @@ +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/hello"}},{"request":{"method":"GET","url":"http://localhost:8000/hello"},"response":{"status":200,"body":{"type":"raw-string","value":"Hello World!"}}},{"request":{"method":"GET","url":"http://localhost:8000/hello"}}]} \ No newline at end of file diff --git a/integration/tests_ok/option_verbose.out b/integration/tests_ok/option_verbose.out new file mode 100644 index 000000000..c57eff55e --- /dev/null +++ b/integration/tests_ok/option_verbose.out @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/integration/tests_ok/very_verbose.err.pattern b/integration/tests_ok/very_verbose.err.pattern index 4b988e706..5ac99eec0 100644 --- a/integration/tests_ok/very_verbose.err.pattern +++ b/integration/tests_ok/very_verbose.err.pattern @@ -17,7 +17,7 @@ > GET /very-verbose/redirect HTTP/1.1 > Host: localhost:8000 > Accept: */* -> User-Agent: hurl/~~~-snapshot +> User-Agent: hurl/~~~ > * Request body: * @@ -45,7 +45,7 @@ > GET /very-verbose/redirected HTTP/1.1 > Host: localhost:8000 > Accept: */* -> User-Agent: hurl/~~~-snapshot +> User-Agent: hurl/~~~ > * Request body: * @@ -74,7 +74,7 @@ > GET /very-verbose/encoding/latin1 HTTP/1.1 > Host: localhost:8000 > Accept: */* -> User-Agent: hurl/~~~-snapshot +> User-Agent: hurl/~~~ > * Request body: * @@ -108,7 +108,7 @@ > Accept: */* > Accept-Encoding: brotli > Content-Type: application/json -> User-Agent: hurl/~~~-snapshot +> User-Agent: hurl/~~~ > Content-Length: 37 > * Request body: @@ -142,7 +142,7 @@ > GET /very-verbose/cat HTTP/1.1 > Host: localhost:8000 > Accept: */* -> User-Agent: hurl/~~~-snapshot +> User-Agent: hurl/~~~ > * Request body: * @@ -175,7 +175,7 @@ > POST /very-verbose/update-cat HTTP/1.1 > Host: localhost:8000 > Accept: */* -> User-Agent: hurl/~~~-snapshot +> User-Agent: hurl/~~~ > Content-Length: 26572 > Content-Type: multipart/form-data; boundary=~~~~~ > @@ -208,7 +208,7 @@ > Host: localhost:8000 > Accept: */* > x-foo: bar -> User-Agent: hurl/~~~-snapshot +> User-Agent: hurl/~~~ > * Request body: * diff --git a/packages/hurl/src/runner/entry.rs b/packages/hurl/src/runner/entry.rs index c2221e26d..5d974e5fb 100644 --- a/packages/hurl/src/runner/entry.rs +++ b/packages/hurl/src/runner/entry.rs @@ -19,7 +19,7 @@ use std::collections::HashMap; use crate::cli::Logger; use crate::http; -use crate::http::ClientOptions; +use crate::http::{ClientOptions, Verbosity}; use hurl_core::ast::*; use super::core::*; @@ -56,21 +56,18 @@ pub fn run( } }; - // We computes overridden options for this entry. - let client_options = get_entry_options(entry, client_options, logger); - // Experimental features // with cookie storage use std::str::FromStr; if let Some(s) = cookie_storage_set(&entry.request) { if let Ok(cookie) = http::Cookie::from_str(s.as_str()) { - http_client.add_cookie(&cookie, &client_options); + http_client.add_cookie(&cookie, client_options); } else { logger.warning(format!("Cookie string can not be parsed: '{}'", s).as_str()); } } if cookie_storage_clear(&entry.request) { - http_client.clear_cookie_storage(&client_options); + http_client.clear_cookie_storage(client_options); } logger.debug(""); @@ -83,12 +80,12 @@ pub fn run( logger.debug("Request can be run with the following curl command:"); logger.debug( http_client - .curl_command_line(&http_request, &runner_options.context_dir, &client_options) + .curl_command_line(&http_request, &runner_options.context_dir, client_options) .as_str(), ); logger.debug(""); - let calls = match http_client.execute_with_redirect(&http_request, &client_options, logger) { + let calls = match http_client.execute_with_redirect(&http_request, client_options, logger) { Ok(calls) => calls, Err(http_error) => { let runner_error = RunnerError::from(http_error); @@ -229,23 +226,19 @@ fn log_request_spec(request: &http::RequestSpec, logger: &Logger) { /// Returns a new [`ClientOptions`] based on the `entry` optional Options section /// and a default `client_options`. -fn get_entry_options( +pub fn get_entry_options( entry: &Entry, client_options: &ClientOptions, logger: &Logger, ) -> ClientOptions { let mut client_options = client_options.clone(); - - let has_options = entry - .request - .sections - .iter() - .any(|s| matches!(s.value, SectionValue::Options(_))); - if has_options { - logger.debug(""); - logger.debug_important("Entry options:"); + if !has_options(entry) { + return client_options; } + logger.debug(""); + logger.debug_important("Entry options:"); + for section in &entry.request.sections { if let SectionValue::Options(options) = §ion.value { for option in options { @@ -258,9 +251,46 @@ fn get_entry_options( client_options.cacert_file = Some(option.filename.value.clone()); logger.debug(format!("cacert: {}", option.filename.value).as_str()); } + EntryOption::Verbose(option) => { + client_options.verbosity = if option.value { + Some(Verbosity::Verbose) + } else { + None + }; + logger.debug(format!("verbose: {}", option.value).as_str()); + } } } } } client_options } + +/// Returns [`true`] if this `entry` has an Option section, [`false`] otherwise. +fn has_options(entry: &Entry) -> bool { + entry + .request + .sections + .iter() + .any(|s| matches!(s.value, SectionValue::Options(_))) +} + +/// Returns the overridden `entry` verbosity, or the default `verbosity` file. +pub fn get_entry_verbosity(entry: &Entry, verbosity: &Option) -> Option { + let mut verbosity = verbosity.clone(); + + for section in &entry.request.sections { + if let SectionValue::Options(options) = §ion.value { + for option in options { + if let EntryOption::Verbose(option) = option { + verbosity = if option.value { + Some(Verbosity::Verbose) + } else { + None + } + } + } + } + } + verbosity +} diff --git a/packages/hurl/src/runner/hurl_file.rs b/packages/hurl/src/runner/hurl_file.rs index 16338b780..659d321fb 100644 --- a/packages/hurl/src/runner/hurl_file.rs +++ b/packages/hurl/src/runner/hurl_file.rs @@ -21,6 +21,7 @@ use std::time::Instant; use crate::cli::Logger; use crate::http; use crate::http::ClientOptions; +use crate::runner::entry::get_entry_verbosity; use hurl_core::ast::*; use super::core::*; @@ -90,8 +91,8 @@ pub fn run( let mut entries = vec![]; let mut variables = HashMap::default(); - for (key, value) in runner_options.variables.clone() { - variables.insert(key.to_string(), value); + for (key, value) in &runner_options.variables { + variables.insert(key.to_string(), value.clone()); } let n = if let Some(to_entry) = runner_options.to_entry { @@ -105,9 +106,8 @@ pub fn run( .entries .iter() .take(n) - .cloned() .enumerate() - .collect::>() + .collect::>() { if let Some(pre_entry) = runner_options.pre_entry { let exit = pre_entry(entry.clone()); @@ -116,17 +116,30 @@ pub fn run( } } + // We compute these new overridden options for this entry, before entering into the `run` + // function because entry options can modify the logger and we want the preamble + // "Executing entry..." to be displayed based on the entry level verbosity. + let entry_verbosity = get_entry_verbosity(entry, &client_options.verbosity); + let logger = &Logger::new( + logger.color, + entry_verbosity.is_some(), + logger.filename, + logger.content, + ); + logger.debug_important( "------------------------------------------------------------------------------", ); logger.debug_important(format!("Executing entry {}", entry_index + 1).as_str()); + let client_options = entry::get_entry_options(entry, client_options, logger); + let entry_results = entry::run( - &entry, + entry, http_client, &mut variables, runner_options, - client_options, + &client_options, logger, ); diff --git a/packages/hurl/src/util/logger.rs b/packages/hurl/src/util/logger.rs index 33f3a038c..dc63e1521 100644 --- a/packages/hurl/src/util/logger.rs +++ b/packages/hurl/src/util/logger.rs @@ -91,8 +91,10 @@ pub struct Logger<'a> { pub header_out: fn(&str, &str), pub test_running: fn(&str, usize, usize), pub test_completed: fn(&str, bool), - pub content: &'a str, + pub color: bool, + pub verbose: bool, pub filename: &'a str, + pub content: &'a str, } impl<'a> Logger<'a> { @@ -112,8 +114,10 @@ impl<'a> Logger<'a> { header_out: log_header_out, test_running: log_test_running, test_completed: log_test_completed, - content, + color, + verbose, filename, + content, }, (false, true) => Logger { info: log_info, @@ -128,8 +132,10 @@ impl<'a> Logger<'a> { header_out: log_header_out_no_color, test_running: log_test_running_no_color, test_completed: log_test_completed_no_color, - content, + color, + verbose, filename, + content, }, (true, false) => Logger { info: log_info, @@ -144,8 +150,10 @@ impl<'a> Logger<'a> { header_out: nop2, test_running: log_test_running, test_completed: log_test_completed, - content, + color, + verbose, filename, + content, }, (false, false) => Logger { info: log_info, @@ -160,8 +168,10 @@ impl<'a> Logger<'a> { header_out: nop2, test_running: log_test_running_no_color, test_completed: log_test_completed_no_color, - content, + color, + verbose, filename, + content, }, } } diff --git a/packages/hurl_core/src/ast/core.rs b/packages/hurl_core/src/ast/core.rs index 501acabee..c99f8133a 100644 --- a/packages/hurl_core/src/ast/core.rs +++ b/packages/hurl_core/src/ast/core.rs @@ -669,6 +669,7 @@ pub struct Variable { pub enum EntryOption { Insecure(InsecureOption), CaCertificate(CaCertificateOption), + Verbose(VerboseOption), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -690,3 +691,13 @@ pub struct CaCertificateOption { pub filename: Filename, pub line_terminator0: LineTerminator, } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct VerboseOption { + pub line_terminators: Vec, + pub space0: Whitespace, + pub space1: Whitespace, + pub space2: Whitespace, + pub value: bool, + pub line_terminator0: LineTerminator, +} diff --git a/packages/hurl_core/src/format/html.rs b/packages/hurl_core/src/format/html.rs index 0f9a502d5..e41ab8767 100644 --- a/packages/hurl_core/src/format/html.rs +++ b/packages/hurl_core/src/format/html.rs @@ -234,6 +234,7 @@ impl Htmlable for EntryOption { match self { EntryOption::Insecure(option) => option.to_html(), EntryOption::CaCertificate(option) => option.to_html(), + EntryOption::Verbose(option) => option.to_html(), } } } @@ -272,6 +273,23 @@ impl Htmlable for CaCertificateOption { } } +impl Htmlable for VerboseOption { + fn to_html(&self) -> String { + let mut buffer = String::from(""); + add_line_terminators(&mut buffer, self.line_terminators.clone()); + buffer.push_str(""); + buffer.push_str(self.space0.to_html().as_str()); + buffer.push_str("verbose"); + buffer.push_str(self.space1.to_html().as_str()); + buffer.push_str(":"); + buffer.push_str(self.space2.to_html().as_str()); + buffer.push_str(format!("{}", self.value).as_str()); + buffer.push_str(""); + buffer.push_str(self.line_terminator0.to_html().as_str()); + buffer + } +} + impl Htmlable for MultipartParam { fn to_html(&self) -> String { match self { diff --git a/packages/hurl_core/src/parser/sections.rs b/packages/hurl_core/src/parser/sections.rs index 5b45bd388..c855ffbf4 100644 --- a/packages/hurl_core/src/parser/sections.rs +++ b/packages/hurl_core/src/parser/sections.rs @@ -342,7 +342,7 @@ fn assert(reader: &mut Reader) -> ParseResult<'static, Assert> { } fn option(reader: &mut Reader) -> ParseResult<'static, EntryOption> { - choice(vec![option_insecure, option_cacert], reader) + choice(vec![option_insecure, option_cacert, option_verbose], reader) } fn option_insecure(reader: &mut Reader) -> ParseResult<'static, EntryOption> { @@ -389,6 +389,28 @@ fn option_cacert(reader: &mut Reader) -> ParseResult<'static, EntryOption> { Ok(EntryOption::CaCertificate(option)) } +fn option_verbose(reader: &mut Reader) -> ParseResult<'static, EntryOption> { + let line_terminators = optional_line_terminators(reader)?; + let space0 = zero_or_more_spaces(reader)?; + try_literal("verbose", reader)?; + let space1 = zero_or_more_spaces(reader)?; + try_literal(":", reader)?; + let space2 = zero_or_more_spaces(reader)?; + let value = nonrecover(boolean, reader)?; + let line_terminator0 = line_terminator(reader)?; + + let option = VerboseOption { + line_terminators, + space0, + space1, + space2, + value, + line_terminator0, + }; + + Ok(EntryOption::Verbose(option)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/hurlfmt/src/format/token.rs b/packages/hurlfmt/src/format/token.rs index 14ac3d481..6e46a26d5 100644 --- a/packages/hurlfmt/src/format/token.rs +++ b/packages/hurlfmt/src/format/token.rs @@ -820,6 +820,7 @@ impl Tokenizable for EntryOption { match self { EntryOption::Insecure(option) => option.tokenize(), EntryOption::CaCertificate(option) => option.tokenize(), + EntryOption::Verbose(option) => option.tokenize(), } } } @@ -865,3 +866,24 @@ impl Tokenizable for CaCertificateOption { tokens } } + +impl Tokenizable for VerboseOption { + fn tokenize(&self) -> Vec { + let mut tokens: Vec = vec![]; + tokens.append( + &mut self + .line_terminators + .iter() + .flat_map(|e| e.tokenize()) + .collect(), + ); + tokens.append(&mut self.space0.tokenize()); + tokens.push(Token::String("verbose".to_string())); + tokens.append(&mut self.space1.tokenize()); + tokens.push(Token::Colon(String::from(":"))); + tokens.append(&mut self.space2.tokenize()); + tokens.push(Token::Boolean(self.value.to_string())); + tokens.append(&mut self.line_terminator0.tokenize()); + tokens + } +}