Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --error-format option to output HTTP context on errors #1562

Merged
merged 3 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions integration/tests_failed/error_format_long.err.pattern
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
HTTP/1.1 200
Server: Werkzeug/~~~ Python/~~
Date: ~~~
Content-Type: text/html; charset=utf-8
Content-Length: 45
Server: Flask Server
Connection: close

<html><head><title>Test</title></head></html>

error: Assert header value
--> tests_failed/error_format_long.hurl:3:15
|
3 | Content-Type: text/html
| ^^^^^^^^^ actual value is <text/html; charset=utf-8>
|

error: Assert failure
--> tests_failed/error_format_long.hurl:5:0
|
5 | xpath "string(//head/title)" equals "Welcome!"
| actual: string <Test>
| expected: string <Welcome!>
|

error: Assert failure
--> tests_failed/error_format_long.hurl:7:0
|
7 | xpath "//title" count == 2
| actual: int <1>
| expected: int <2>
|

1 change: 1 addition & 0 deletions integration/tests_failed/error_format_long.exit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4
8 changes: 8 additions & 0 deletions integration/tests_failed/error_format_long.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<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/error-assert-xpath</span></span>
</span><span class="response"><span class="line"><span class="version">HTTP</span> <span class="number">200</span></span>
<span class="line"><span class="string">Content-Type</span>: <span class="string">text/html</span></span>
<span class="line"><span class="section-header">[Asserts]</span></span>
<span class="line"><span class="query-type">xpath</span> <span class="string">"string(//head/title)"</span> <span class="predicate-type">equals</span> <span class="string">"Welcome!"</span></span>
<span class="line"><span class="query-type">xpath</span> <span class="string">"//foo"</span> <span class="predicate-type">isEmpty</span></span>
<span class="line"><span class="query-type">xpath</span> <span class="string">"//title"</span> <span class="filter-type">count</span> <span class="predicate-type">==</span> <span class="number">2</span></span>
</span></span></code></pre>
7 changes: 7 additions & 0 deletions integration/tests_failed/error_format_long.hurl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
GET http://localhost:8000/error-assert-xpath
HTTP 200
Content-Type: text/html
[Asserts]
xpath "string(//head/title)" equals "Welcome!"
xpath "//foo" isEmpty
xpath "//title" count == 2
1 change: 1 addition & 0 deletions integration/tests_failed/error_format_long.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/error-assert-xpath"},"response":{"status":200,"headers":[{"name":"Content-Type","value":"text/html"}],"asserts":[{"query":{"type":"xpath","expr":"string(//head/title)"},"predicate":{"type":"equal","value":"Welcome!"}},{"query":{"type":"xpath","expr":"//foo"},"predicate":{"type":"isEmpty"}},{"query":{"type":"xpath","expr":"//title"},"filters":[{"type":"count"}],"predicate":{"type":"equal","value":2}}]}}]}
3 changes: 3 additions & 0 deletions integration/tests_failed/error_format_long.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurl --error-format long tests_failed/error_format_long.hurl
3 changes: 3 additions & 0 deletions integration/tests_failed/error_format_long.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
set -Eeuo pipefail
hurl --error-format long tests_failed/error_format_long.hurl
2 changes: 1 addition & 1 deletion integration/tests_failed/option_retry.err.pattern
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@
< Connection: close
<
*
*
* Retry max count reached, no more retry
*
error: Assert status code
--> tests_failed/option_retry.hurl:6:6
|
Expand Down
2 changes: 1 addition & 1 deletion integration/tests_failed/retry.err.pattern
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@
< Connection: close
<
*
*
* Retry max count reached, no more retry
*
error: Assert status code
--> tests_failed/retry.hurl:2:6
|
Expand Down
13 changes: 12 additions & 1 deletion packages/hurl/src/cli/options/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ pub fn cookies_output_file() -> clap::Arg {
.help("Write cookies to FILE after running the session (only for one session)")
.num_args(1)
}

pub fn error_format() -> clap::Arg {
clap::Arg::new("error_format")
.long("error-format")
.value_name("FORMAT")
.value_parser(["short", "long"])
.default_value("short")
.help("Control the format of error messages")
}

pub fn fail_at_en() -> clap::Arg {
clap::Arg::new("fail_at_end")
.long("fail-at-end")
Expand All @@ -105,7 +115,7 @@ pub fn file_root() -> clap::Arg {
clap::Arg::new("file_root")
.long("file-root")
.value_name("DIR")
.help("Set root filesystem to import files (default is current directory)")
.help("Set root filesystem to import files [default: current directory]")
.num_args(1)
}

Expand Down Expand Up @@ -156,6 +166,7 @@ pub fn insecure() -> clap::Arg {
.help("Allow insecure SSL connections")
.action(ArgAction::SetTrue)
}

pub fn interactive() -> clap::Arg {
clap::Arg::new("interactive")
.long("interactive")
Expand Down
10 changes: 10 additions & 0 deletions packages/hurl/src/cli/options/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
use super::variables::{parse as parse_variable, parse_value};
use super::OptionsError;
use crate::cli::options::ErrorFormat;
use crate::cli::OutputType;
use atty::Stream;
use clap::ArgMatches;
Expand Down Expand Up @@ -110,6 +111,15 @@ pub fn cookie_output_file(arg_matches: &ArgMatches) -> Option<String> {
get::<String>(arg_matches, "cookies_output_file")
}

pub fn error_format(arg_matches: &ArgMatches) -> ErrorFormat {
let error_format = get::<String>(arg_matches, "error_format");
match error_format.as_deref() {
Some("long") => ErrorFormat::Long,
Some("short") => ErrorFormat::Short,
_ => ErrorFormat::Short,
}
}

pub fn fail_fast(arg_matches: &ArgMatches) -> bool {
!has_flag(arg_matches, "fail_at_end")
}
Expand Down
24 changes: 22 additions & 2 deletions packages/hurl/src/cli/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct Options {
pub connects_to: Vec<String>,
pub cookie_input_file: Option<String>,
pub cookie_output_file: Option<String>,
pub error_format: ErrorFormat,
pub fail_fast: bool,
pub file_root: Option<String>,
pub follow_location: bool,
Expand Down Expand Up @@ -89,6 +90,22 @@ impl From<clap::Error> for OptionsError {
}
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ErrorFormat {
Short,
Long,
}

impl From<ErrorFormat> for hurl::util::logger::ErrorFormat {
fn from(value: ErrorFormat) -> Self {
match value {
ErrorFormat::Short => hurl::util::logger::ErrorFormat::Short,
ErrorFormat::Long => hurl::util::logger::ErrorFormat::Long,
}
}
}

fn get_version() -> String {
let libcurl_version = libcurl_version_info();
format!(
Expand All @@ -113,6 +130,7 @@ pub fn parse() -> Result<Options, OptionsError> {
.arg(commands::connect_to())
.arg(commands::cookies_input_file())
.arg(commands::cookies_output_file())
.arg(commands::error_format())
.arg(commands::fail_at_en())
.arg(commands::file_root())
.arg(commands::follow_location())
Expand Down Expand Up @@ -174,6 +192,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result<Options, OptionsError> {
let connects_to = matches::connects_to(arg_matches);
let cookie_input_file = matches::cookie_input_file(arg_matches);
let cookie_output_file = matches::cookie_output_file(arg_matches);
let error_format = matches::error_format(arg_matches);
let fail_fast = matches::fail_fast(arg_matches);
let file_root = matches::file_root(arg_matches);
let follow_location = matches::follow_location(arg_matches);
Expand Down Expand Up @@ -212,6 +231,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result<Options, OptionsError> {
connects_to,
cookie_input_file,
cookie_output_file,
error_format,
fail_fast,
file_root,
follow_location,
Expand Down Expand Up @@ -303,8 +323,8 @@ impl Options {
let ignore_asserts = self.ignore_asserts;
let ssl_no_revoke = self.ssl_no_revoke;

let mut bd = RunnerOptionsBuilder::new();
bd.cacert_file(cacert_file)
RunnerOptionsBuilder::new()
.cacert_file(cacert_file)
.client_cert_file(client_cert_file)
.client_key_file(client_key_file)
.compressed(compressed)
Expand Down
6 changes: 3 additions & 3 deletions packages/hurl/src/http/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ impl Client {
logger.info(">");

if very_verbose {
debug_request.log_body(logger);
debug_request.log_body(true, logger);
}
}
}
Expand All @@ -256,7 +256,7 @@ impl Client {
logger.info(">");

if very_verbose {
debug_request.log_body(logger);
debug_request.log_body(true, logger);
}
}
}
Expand Down Expand Up @@ -369,7 +369,7 @@ impl Client {
}
logger.info("<");
if very_verbose {
response.log_body(logger);
response.log_body(true, logger);
}
}

Expand Down
35 changes: 22 additions & 13 deletions packages/hurl/src/http/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,39 @@
use crate::util::logger::Logger;

/// Debug log text.
pub fn log_text(text: &str, logger: &Logger) {
pub fn log_text(text: &str, debug: bool, logger: &Logger) {
if text.is_empty() {
logger.debug("");
if debug {
logger.debug("");
} else {
logger.info("");
}
} else {
text.split('\n').for_each(|l| logger.debug(l))
let lines = text.split('\n');
if debug {
lines.for_each(|l| logger.debug(l))
} else {
lines.for_each(|l| logger.info(l))
}
}
}

/// Debug log bytes with a maximum size.
///
/// # Arguments
///
/// * `bytes`- the bytes to log
/// * `max` - The maximum number if bytes to log
pub fn log_bytes(bytes: &[u8], max: usize, logger: &Logger) {
/// Debug log `bytes` with a maximum size of `max` bytes.
pub fn log_bytes(bytes: &[u8], max: usize, debug: bool, logger: &Logger) {
let bytes = if bytes.len() > max {
&bytes[..max]
} else {
bytes
};

if bytes.is_empty() {
logger.debug("");
let log = if bytes.is_empty() {
"".to_string()
} else {
logger.debug(format!("Bytes <{}...>", hex::encode(bytes)).as_str());
format!("Bytes <{}...>", hex::encode(bytes))
};
if debug {
logger.debug(&log);
} else {
logger.info(&log)
}
}
8 changes: 4 additions & 4 deletions packages/hurl/src/http/request_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ use crate::util::logger::Logger;

impl Request {
/// Log request body.
pub fn log_body(&self, logger: &Logger) {
pub fn log_body(&self, debug: bool, logger: &Logger) {
logger.debug_important("Request body:");

// We try to decode the HTTP body as text if the response has a text kind content type.
// If it ok, we print each line of the body in debug format. Otherwise, we
// print the body first 64 bytes.
if let Some(content_type) = self.content_type() {
if !mimetype::is_kind_of_text(&content_type) {
debug::log_bytes(&self.body, 64, logger);
debug::log_bytes(&self.body, 64, debug, logger);
return;
}
}
match self.text() {
Ok(text) => debug::log_text(&text, logger),
Err(_) => debug::log_bytes(&self.body, 64, logger),
Ok(text) => debug::log_text(&text, debug, logger),
Err(_) => debug::log_bytes(&self.body, 64, debug, logger),
}
}
}
41 changes: 36 additions & 5 deletions packages/hurl/src/http/response_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,55 @@

use crate::http::{debug, mimetype, Response};
use crate::util::logger::Logger;
use colored::Colorize;

impl Response {
/// Log a response body as text if possible, or a slice of body bytes.
pub fn log_body(&self, logger: &Logger) {
logger.debug_important("Response body:");
pub fn log_body(&self, debug: bool, logger: &Logger) {
if debug {
logger.debug_important("Response body:");
}

// We try to decode the HTTP body as text if the request has a text kind content type.
// If it ok, we print each line of the body in debug format. Otherwise, we
// print the body first 64 bytes.
if let Some(content_type) = self.content_type() {
if !mimetype::is_kind_of_text(&content_type) {
debug::log_bytes(&self.body, 64, logger);
debug::log_bytes(&self.body, 64, debug, logger);
return;
}
}
match self.text() {
Ok(text) => debug::log_text(&text, logger),
Err(_) => debug::log_bytes(&self.body, 64, logger),
Ok(text) => debug::log_text(&text, debug, logger),
Err(_) => debug::log_bytes(&self.body, 64, debug, logger),
}
}

pub fn log_all(&self, logger: &Logger) {
let status_line = self.get_status_line_headers(logger.color);
logger.info(&status_line);
self.log_body(false, logger);
logger.info("");
}

/// Returns status, version and HTTP headers from this HTTP response.
pub fn get_status_line_headers(&self, color: bool) -> String {
let mut str = String::new();
let status_line = format!("{} {}\n", self.version, self.status);
let status_line = if color {
format!("{}", status_line.green().bold())
} else {
status_line
};
str.push_str(&status_line);
for header in self.headers.iter() {
let header_line = if color {
format!("{}: {}\n", header.name.cyan().bold(), header.value)
} else {
format!("{}: {}\n", header.name, header.value)
};
str.push_str(&header_line);
}
str
}
}
7 changes: 4 additions & 3 deletions packages/hurl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,12 @@ fn main() {
let content = unwrap_or_exit(content, EXIT_ERROR_PARSING, &base_logger);

let logger = LoggerBuilder::new()
.filename(filename)
.color(opts.color)
.verbose(verbose)
.test(opts.test)
.error_format(opts.error_format.clone().into())
.filename(filename)
.progress_bar(progress_bar)
.test(opts.test)
.verbose(verbose)
.build();

let total = opts.input_files.len();
Expand Down
Loading