Add --curl option to export executed requests to curl commands

This commit is contained in:
Jean-Christophe Amiel 2024-11-07 16:57:56 +01:00
parent fb85f78137
commit 9ab2e17716
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
28 changed files with 151 additions and 10 deletions

View File

@ -1229,6 +1229,7 @@ will follow a redirection only for the second entry.
| <a href="#continue-on-error" id="continue-on-error"><code>--continue-on-error</code></a> | Continue executing requests to the end of the Hurl file even when an assert error occurs.<br>By default, Hurl exits after an assert error in the HTTP response.<br><br>Note that this option does not affect the behavior with multiple input Hurl files.<br><br>All the input files are executed independently. The result of one file does not affect the execution of the other Hurl files.<br><br>This is a cli-only option.<br> |
| <a href="#cookie" id="cookie"><code>-b, --cookie &lt;FILE&gt;</code></a> | Read cookies from FILE (using the Netscape cookie file format).<br><br>Combined with [`-c, --cookie-jar`](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.<br><br>This is a cli-only option.<br> |
| <a href="#cookie-jar" id="cookie-jar"><code>-c, --cookie-jar &lt;FILE&gt;</code></a> | Write cookies to FILE after running the session (only for one session).<br>The file will be written using the Netscape cookie file format.<br><br>Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage between successive Hurl runs.<br><br>This is a cli-only option.<br> |
| <a href="#curl" id="curl"><code>--curl &lt;FILE&gt;</code></a> | Export each request to a list of curl commands.<br><br>This is a cli-only option.<br> |
| <a href="#delay" id="delay"><code>--delay &lt;MILLISECONDS&gt;</code></a> | Sets delay before each request. The delay is not applied to requests that have been retried because of [`--retry`](#retry). See [`--retry-interval`](#retry-interval) to space retried requests.<br><br>You can specify time units in the delay expression. Set Hurl to use a delay of 2 seconds with `--delay 2s` or set it to 500 milliseconds with `--delay 500ms`. No spaces allowed.<br> |
| <a href="#error-format" id="error-format"><code>--error-format &lt;FORMAT&gt;</code></a> | Control the format of error message (short by default or long)<br><br>This is a cli-only option.<br> |
| <a href="#file-root" id="file-root"><code>--file-root &lt;DIR&gt;</code></a> | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.<br>When it is not explicitly defined, files are relative to the Hurl file's directory.<br><br>This is a cli-only option.<br> |

View File

@ -26,6 +26,7 @@ _hurl() {
'--continue-on-error[Continue executing requests even if an error occurs]' \
'(-b --cookie)'{-b,--cookie}'[Read cookies from FILE]: :_files' \
'(-c --cookie-jar)'{-c,--cookie-jar}'[Write cookies to FILE after running the session (only for one session)]: :_files' \
'--curl[Export each request to a list of curl commands]: :_files' \
'--delay[Sets delay before each request]: :' \
'--error-format[Control the format of error messages]: :' \
'--fail-at-end[Fail at end]' \

View File

@ -31,6 +31,7 @@ Register-ArgumentCompleter -Native -CommandName 'hurl' -ScriptBlock {
[CompletionResult]::new('--continue-on-error', 'continue-on-error', [CompletionResultType]::ParameterName, 'Continue executing requests even if an error occurs')
[CompletionResult]::new('--cookie', 'cookie', [CompletionResultType]::ParameterName, 'Read cookies from FILE')
[CompletionResult]::new('--cookie-jar', 'cookie-jar', [CompletionResultType]::ParameterName, 'Write cookies to FILE after running the session (only for one session)')
[CompletionResult]::new('--curl', 'curl', [CompletionResultType]::ParameterName, 'Export each request to a list of curl commands')
[CompletionResult]::new('--delay', 'delay', [CompletionResultType]::ParameterName, 'Sets delay before each request')
[CompletionResult]::new('--error-format', 'error-format', [CompletionResultType]::ParameterName, 'Control the format of error messages')
[CompletionResult]::new('--fail-at-end', 'fail-at-end', [CompletionResultType]::ParameterName, 'Fail at end')

View File

@ -5,7 +5,7 @@ _hurl()
_init_completion || return
if [[ $cur == -* ]]; then
COMPREPLY=($(compgen -W '--aws-sigv4 --cacert --cert --key --color --compressed --connect-timeout --connect-to --continue-on-error --cookie --cookie-jar --delay --error-format --fail-at-end --file-root --location --location-trusted --from-entry --glob --http1.0 --http1.1 --http2 --http3 --ignore-asserts --include --insecure --interactive --ipv4 --ipv6 --jobs --json --limit-rate --max-filesize --max-redirs --max-time --netrc --netrc-file --netrc-optional --no-color --no-output --noproxy --output --parallel --path-as-is --proxy --repeat --report-html --report-json --report-junit --report-tap --resolve --retry --retry-interval --ssl-no-revoke --test --to-entry --unix-socket --user --user-agent --variable --variables-file --verbose --very-verbose --help --version' -- "$cur"))
COMPREPLY=($(compgen -W '--aws-sigv4 --cacert --cert --key --color --compressed --connect-timeout --connect-to --continue-on-error --cookie --cookie-jar --curl --delay --error-format --fail-at-end --file-root --location --location-trusted --from-entry --glob --http1.0 --http1.1 --http2 --http3 --ignore-asserts --include --insecure --interactive --ipv4 --ipv6 --jobs --json --limit-rate --max-filesize --max-redirs --max-time --netrc --netrc-file --netrc-optional --no-color --no-output --noproxy --output --parallel --path-as-is --proxy --repeat --report-html --report-json --report-junit --report-tap --resolve --retry --retry-interval --ssl-no-revoke --test --to-entry --unix-socket --user --user-agent --variable --variables-file --verbose --very-verbose --help --version' -- "$cur"))
return
fi

View File

@ -9,6 +9,7 @@ complete -c hurl -l connect-to -d 'For a request to the given HOST1:PORT1 pair,
complete -c hurl -l continue-on-error -d 'Continue executing requests even if an error occurs'
complete -c hurl -l cookie -d 'Read cookies from FILE'
complete -c hurl -l cookie-jar -d 'Write cookies to FILE after running the session (only for one session)'
complete -c hurl -l curl -d 'Export each request to a list of curl commands'
complete -c hurl -l delay -d 'Sets delay before each request'
complete -c hurl -l error-format -d 'Control the format of error messages'
complete -c hurl -l fail-at-end -d 'Fail at end'

View File

@ -159,6 +159,7 @@ will follow a redirection only for the second entry.
| <a href="#continue-on-error" id="continue-on-error"><code>--continue-on-error</code></a> | Continue executing requests to the end of the Hurl file even when an assert error occurs.<br>By default, Hurl exits after an assert error in the HTTP response.<br><br>Note that this option does not affect the behavior with multiple input Hurl files.<br><br>All the input files are executed independently. The result of one file does not affect the execution of the other Hurl files.<br><br>This is a cli-only option.<br> |
| <a href="#cookie" id="cookie"><code>-b, --cookie &lt;FILE&gt;</code></a> | Read cookies from FILE (using the Netscape cookie file format).<br><br>Combined with [`-c, --cookie-jar`](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.<br><br>This is a cli-only option.<br> |
| <a href="#cookie-jar" id="cookie-jar"><code>-c, --cookie-jar &lt;FILE&gt;</code></a> | Write cookies to FILE after running the session (only for one session).<br>The file will be written using the Netscape cookie file format.<br><br>Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage between successive Hurl runs.<br><br>This is a cli-only option.<br> |
| <a href="#curl" id="curl"><code>--curl &lt;FILE&gt;</code></a> | Export each request to a list of curl commands.<br><br>This is a cli-only option.<br> |
| <a href="#delay" id="delay"><code>--delay &lt;MILLISECONDS&gt;</code></a> | Sets delay before each request. The delay is not applied to requests that have been retried because of [`--retry`](#retry). See [`--retry-interval`](#retry-interval) to space retried requests.<br><br>You can specify time units in the delay expression. Set Hurl to use a delay of 2 seconds with `--delay 2s` or set it to 500 milliseconds with `--delay 500ms`. No spaces allowed.<br> |
| <a href="#error-format" id="error-format"><code>--error-format &lt;FORMAT&gt;</code></a> | Control the format of error message (short by default or long)<br><br>This is a cli-only option.<br> |
| <a href="#file-root" id="file-root"><code>--file-root &lt;DIR&gt;</code></a> | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.<br>When it is not explicitly defined, files are relative to the Hurl file's directory.<br><br>This is a cli-only option.<br> |

View File

@ -1,4 +1,4 @@
.TH hurl 1 "28 Oct 2024" "hurl 6.0.0-SNAPSHOT" " Hurl Manual"
.TH hurl 1 "07 Nov 2024" "hurl 6.0.0-SNAPSHOT" " Hurl Manual"
.SH NAME
hurl - run and test HTTP requests.
@ -197,6 +197,12 @@ Combined with \fI-b, --cookie\fP, you can simulate a cookie storage between succ
This is a cli-only option.
.IP "--curl <FILE> "
Export each request to a list of curl commands.
This is a cli-only option.
.IP "--delay <MILLISECONDS> "
Sets delay before each request. The delay is not applied to requests that have been retried because of \fI--retry\fP. See \fI--retry-interval\fP to space retried requests.

View File

@ -216,6 +216,12 @@ Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage betwe
This is a cli-only option.
### --curl <FILE> {#curl}
Export each request to a list of curl commands.
This is a cli-only option.
### --delay <MILLISECONDS> {#delay}
Sets delay before each request. The delay is not applied to requests that have been retried because of [`--retry`](#retry). See [`--retry-interval`](#retry-interval) to space retried requests.

View File

@ -1,4 +1,4 @@
.TH hurl 1 "28 Oct 2024" "hurl 6.0.0-SNAPSHOT" " Hurl Manual"
.TH hurl 1 "07 Nov 2024" "hurl 6.0.0-SNAPSHOT" " Hurl Manual"
.SH NAME
hurlfmt - format Hurl files

View File

@ -0,0 +1,8 @@
name: curl
long: curl
value: FILE
help: Export each request to a list of curl commands
help_heading: Output options
cli_only: true
---
Export each request to a list of curl commands.

View File

@ -1,3 +1,4 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurl --verbose tests_ok/assert_body.hurl

View File

@ -1,3 +1,4 @@
#!/bin/bash
set -Eeuo pipefail
hurl --verbose tests_ok/assert_body.hurl

View File

@ -0,0 +1,4 @@
curl 'http://localhost:8000/assert-body'
curl 'http://localhost:8000/assert-body'
curl 'http://localhost:8000/assert-body-with-crlf'
curl 'http://localhost:8000/assert-body'

View File

@ -0,0 +1,6 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurl --curl build/assert_body.curl --no-output tests_ok/assert_body.hurl
Write-Host (Get-Content build/assert_body.curl -Raw) -NoNewLine

View File

@ -0,0 +1,6 @@
#!/bin/bash
set -Eeuo pipefail
hurl --curl build/assert_body.curl --no-output tests_ok/assert_body.hurl
cat build/assert_body.curl

View File

@ -72,6 +72,7 @@ HTTP options:
Output options:
--color Colorize output
--curl <FILE> Export each request to a list of curl commands
--error-format <FORMAT> Control the format of error messages [default: short] [possible
values: short, long]
-i, --include Include the HTTP headers in the output

View File

@ -1,3 +1,4 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurl tests_ok/multilines.hurl --verbose
hurl --verbose tests_ok/multilines.hurl

View File

@ -1,3 +1,4 @@
#!/bin/bash
set -Eeuo pipefail
hurl tests_ok/multilines.hurl --verbose
hurl --verbose tests_ok/multilines.hurl

View File

@ -0,0 +1,4 @@
curl --header 'Content-Type:' --data $'line1\nline2\nline3\n' 'http://localhost:8000/multilines/plain-text'
curl --header 'Content-Type: application/json' --data $'{\n "foo": "bar"\n "baz": 123456\n}\n' 'http://localhost:8000/multilines/json'
curl --header 'Content-Type: application/xml' --data $'<?xml version="1.0"?>\n<catalog>\n <book id="bk101">\n <author>Gambardella, Matthew</author>\n <title>XML Developer\'s Guide</title>\n <genre>Computer</genre>\n <price>44.95</price>\n <publish_date>2000-10-01</publish_date>\n <description>An in-depth look at creating applications\n with XML.</description>\n </book>\n</catalog>\n' 'http://localhost:8000/multilines/xml'
curl --header 'Content-Type: application/json' --data '{"query":"{\n hero {\n name\n # Queries can have comments!\n friends {\n name\n }\n }\n}"}' 'http://localhost:8000/multilines/graphql'

View File

@ -0,0 +1,6 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurl --verbose --no-output --curl build/multilines.curl tests_ok/multilines.hurl
Write-Host (Get-Content build/multilines.curl -Raw) -NoNewLine

View File

@ -0,0 +1,6 @@
#!/bin/bash
set -Eeuo pipefail
hurl --verbose --no-output --curl build/multilines.curl tests_ok/multilines.hurl
cat build/multilines.curl

View File

@ -1229,6 +1229,7 @@ will follow a redirection only for the second entry.
| <a href="#continue-on-error" id="continue-on-error"><code>--continue-on-error</code></a> | Continue executing requests to the end of the Hurl file even when an assert error occurs.<br>By default, Hurl exits after an assert error in the HTTP response.<br><br>Note that this option does not affect the behavior with multiple input Hurl files.<br><br>All the input files are executed independently. The result of one file does not affect the execution of the other Hurl files.<br><br>This is a cli-only option.<br> |
| <a href="#cookie" id="cookie"><code>-b, --cookie &lt;FILE&gt;</code></a> | Read cookies from FILE (using the Netscape cookie file format).<br><br>Combined with [`-c, --cookie-jar`](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.<br><br>This is a cli-only option.<br> |
| <a href="#cookie-jar" id="cookie-jar"><code>-c, --cookie-jar &lt;FILE&gt;</code></a> | Write cookies to FILE after running the session (only for one session).<br>The file will be written using the Netscape cookie file format.<br><br>Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage between successive Hurl runs.<br><br>This is a cli-only option.<br> |
| <a href="#curl" id="curl"><code>--curl &lt;FILE&gt;</code></a> | Export each request to a list of curl commands.<br><br>This is a cli-only option.<br> |
| <a href="#delay" id="delay"><code>--delay &lt;MILLISECONDS&gt;</code></a> | Sets delay before each request. The delay is not applied to requests that have been retried because of [`--retry`](#retry). See [`--retry-interval`](#retry-interval) to space retried requests.<br><br>You can specify time units in the delay expression. Set Hurl to use a delay of 2 seconds with `--delay 2s` or set it to 500 milliseconds with `--delay 500ms`. No spaces allowed.<br> |
| <a href="#error-format" id="error-format"><code>--error-format &lt;FORMAT&gt;</code></a> | Control the format of error message (short by default or long)<br><br>This is a cli-only option.<br> |
| <a href="#file-root" id="file-root"><code>--file-root &lt;DIR&gt;</code></a> | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.<br>When it is not explicitly defined, files are relative to the Hurl file's directory.<br><br>This is a cli-only option.<br> |

View File

@ -128,6 +128,15 @@ pub fn cookies_output_file() -> clap::Arg {
.num_args(1)
}
pub fn curl() -> clap::Arg {
clap::Arg::new("curl")
.long("curl")
.value_name("FILE")
.help("Export each request to a list of curl commands")
.help_heading("Output options")
.num_args(1)
}
pub fn delay() -> clap::Arg {
clap::Arg::new("delay")
.long("delay")

View File

@ -126,6 +126,10 @@ pub fn cookie_output_file(arg_matches: &ArgMatches) -> Option<PathBuf> {
get::<String>(arg_matches, "cookies_output_file").map(PathBuf::from)
}
pub fn curl_file(arg_matches: &ArgMatches) -> Option<PathBuf> {
get::<String>(arg_matches, "curl").map(PathBuf::from)
}
pub fn delay(arg_matches: &ArgMatches) -> Result<Duration, CliOptionsError> {
let s = get::<String>(arg_matches, "delay").unwrap_or_default();
get_duration(&s, DurationUnit::MilliSecond)

View File

@ -54,6 +54,7 @@ pub struct CliOptions {
pub continue_on_error: bool,
pub cookie_input_file: Option<String>,
pub cookie_output_file: Option<PathBuf>,
pub curl_file: Option<PathBuf>,
pub delay: Duration,
pub error_format: ErrorFormat,
pub file_root: Option<String>,
@ -180,6 +181,7 @@ pub fn parse() -> Result<CliOptions, CliOptionsError> {
.arg(commands::continue_on_error())
.arg(commands::cookies_input_file())
.arg(commands::cookies_output_file())
.arg(commands::curl())
.arg(commands::delay())
.arg(commands::error_format())
.arg(commands::fail_at_end())
@ -269,6 +271,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result<CliOptions, CliOptionsError
let continue_on_error = matches::continue_on_error(arg_matches);
let cookie_input_file = matches::cookie_input_file(arg_matches);
let cookie_output_file = matches::cookie_output_file(arg_matches);
let curl_file = matches::curl_file(arg_matches);
let delay = matches::delay(arg_matches)?;
let error_format = matches::error_format(arg_matches);
let file_root = matches::file_root(arg_matches);
@ -325,6 +328,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result<CliOptions, CliOptionsError
continue_on_error,
cookie_input_file,
cookie_output_file,
curl_file,
delay,
error_format,
file_root,

View File

@ -23,7 +23,7 @@ use std::path::Path;
use std::time::Instant;
use std::{env, process, thread};
use hurl::report::{html, json, junit, tap};
use hurl::report::{curl, html, json, junit, tap};
use hurl::runner;
use hurl::runner::HurlResult;
use hurl_core::input::Input;
@ -138,6 +138,9 @@ fn export_results(
opts: &CliOptions,
logger: &BaseLogger,
) -> Result<(), CliError> {
if let Some(file) = &opts.curl_file {
create_curl_export(runs, file)?;
}
if let Some(file) = &opts.junit_file {
logger.debug(&format!("Writing JUnit report to {}", file.display()));
create_junit_report(runs, file)?;
@ -161,7 +164,14 @@ fn export_results(
Ok(())
}
/// Create a JUnit report for this run.
/// Creates an export of all curl commands for this run.
fn create_curl_export(runs: &[HurlRun], filename: &Path) -> Result<(), CliError> {
let results = runs.iter().map(|r| &r.hurl_result).collect::<Vec<_>>();
curl::write_curl(&results, filename)?;
Ok(())
}
/// Creates a JUnit report for this run.
fn create_junit_report(runs: &[HurlRun], filename: &Path) -> Result<(), CliError> {
let testcases = runs
.iter()
@ -171,7 +181,7 @@ fn create_junit_report(runs: &[HurlRun], filename: &Path) -> Result<(), CliError
Ok(())
}
/// Create a TAP report for this run.
/// Creates a TAP report for this run.
fn create_tap_report(runs: &[HurlRun], filename: &Path) -> Result<(), CliError> {
let testcases = runs
.iter()
@ -181,7 +191,7 @@ fn create_tap_report(runs: &[HurlRun], filename: &Path) -> Result<(), CliError>
Ok(())
}
/// Create an HTML report for this run.
/// Creates an HTML report for this run.
fn create_html_report(runs: &[HurlRun], dir_path: &Path) -> Result<(), CliError> {
// We ensure that the containing folder exists.
let store_path = dir_path.join("store");
@ -198,7 +208,7 @@ fn create_html_report(runs: &[HurlRun], dir_path: &Path) -> Result<(), CliError>
Ok(())
}
/// Create an JSON report for this run.
/// Creates an JSON report for this run.
fn create_json_report(runs: &[HurlRun], dir_path: &Path) -> Result<(), CliError> {
// We ensure that the containing folder exists.
let store_path = dir_path.join("store");

View File

@ -0,0 +1,49 @@
/*
* Hurl (https://hurl.dev)
* Copyright (C) 2024 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use crate::report::ReportError;
use crate::runner::HurlResult;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
/// Creates a curl export from a list of `hurl_results`.
pub fn write_curl(hurl_results: &[&HurlResult], filename: &Path) -> Result<(), ReportError> {
// We ensure that parent folder is created.
if let Some(parent) = filename.parent() {
match std::fs::create_dir_all(parent) {
Ok(_) => {}
Err(err) => return Err(ReportError::from_error(err, filename, "Issue curl export")),
}
}
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.append(false)
.open(filename)?;
let mut cmds = hurl_results
.iter()
.flat_map(|h| &h.entries)
.map(|e| e.curl_cmd.to_string())
.collect::<Vec<_>>()
.join("\n");
cmds.push('\n');
file.write_all(cmds.as_bytes())?;
Ok(())
}

View File

@ -19,9 +19,11 @@
//! Various reports for Hurl runs (JUnit, HTML etc...) A report aggregates multiple runs into
//! a single unit.
pub mod curl;
mod error;
pub mod html;
pub mod json;
pub mod junit;
pub mod tap;
pub use error::ReportError;