Add verbose entry option.

This commit is contained in:
jcamiel 2022-08-12 20:07:14 +02:00
parent 02a1e49ddb
commit 633500ebbd
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
19 changed files with 218 additions and 39 deletions

View File

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

View File

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

View File

@ -0,0 +1,10 @@
<pre><code class="language-hurl"><span class="hurl-entry"><span class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/hello</span></span>
</span></span><span class="hurl-entry"><span class="request"><span class="line"></span>
<span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/hello</span></span>
<span class="line section-header">[Options]</span>
<span class="line"><span class="string">verbose</span><span>:</span> <span class="boolean">true</span></span>
</span><span class="response"><span class="line"></span>
<span class="line"><span class="version">HTTP/*</span> <span class="number">200</span></span>
<span class="raw"><span class="line">```Hello World!```</span></span>
</span></span><span class="hurl-entry"><span class="request"><span class="line"></span>
<span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/hello</span></span></span></span></code></pre>

View File

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

View File

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

View File

@ -0,0 +1 @@
Hello World!

View File

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

View File

@ -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) = &section.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<Verbosity>) -> Option<Verbosity> {
let mut verbosity = verbosity.clone();
for section in &entry.request.sections {
if let SectionValue::Options(options) = &section.value {
for option in options {
if let EntryOption::Verbose(option) = option {
verbosity = if option.value {
Some(Verbosity::Verbose)
} else {
None
}
}
}
}
}
verbosity
}

View File

@ -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::<Vec<(usize, Entry)>>()
.collect::<Vec<(usize, &Entry)>>()
{
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,
);

View File

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

View File

@ -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<LineTerminator>,
pub space0: Whitespace,
pub space1: Whitespace,
pub space2: Whitespace,
pub value: bool,
pub line_terminator0: LineTerminator,
}

View File

@ -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("<span class=\"line\">");
buffer.push_str(self.space0.to_html().as_str());
buffer.push_str("<span class=\"string\">verbose</span>");
buffer.push_str(self.space1.to_html().as_str());
buffer.push_str("<span>:</span>");
buffer.push_str(self.space2.to_html().as_str());
buffer.push_str(format!("<span class=\"boolean\">{}</span>", self.value).as_str());
buffer.push_str("</span>");
buffer.push_str(self.line_terminator0.to_html().as_str());
buffer
}
}
impl Htmlable for MultipartParam {
fn to_html(&self) -> String {
match self {

View File

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

View File

@ -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<Token> {
let mut tokens: Vec<Token> = 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
}
}