mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-11-23 00:44:55 +03:00
Using explicit stdout output
This commit is contained in:
parent
3927dcc622
commit
65e00babca
30
integration/hurl/tests_ok/stdout.err.pattern
Normal file
30
integration/hurl/tests_ok/stdout.err.pattern
Normal file
@ -0,0 +1,30 @@
|
||||
* ------------------------------------------------------------------------------
|
||||
* Executing entry 1
|
||||
*
|
||||
* Entry options:
|
||||
* output: -
|
||||
*
|
||||
* Cookie store:
|
||||
*
|
||||
* Request:
|
||||
* GET http://localhost:8000/stdout/text
|
||||
*
|
||||
* Request can be run with the following curl command:
|
||||
* curl --output - 'http://localhost:8000/stdout/text'
|
||||
*
|
||||
> GET /stdout/text HTTP/1.1
|
||||
> Host: localhost:8000
|
||||
> Accept: */*
|
||||
> User-Agent: hurl/~~~
|
||||
>
|
||||
* Response: (received 5 bytes in ~~~ ms)
|
||||
*
|
||||
< HTTP/1.1 200 OK
|
||||
< Server: Werkzeug/~~~
|
||||
< Date: ~~~
|
||||
< Content-Type: text/html; charset=utf-8
|
||||
< Content-Length: 5
|
||||
< Server: Flask Server
|
||||
< Connection: close
|
||||
<
|
||||
*
|
8
integration/hurl/tests_ok/stdout.hurl
Executable file
8
integration/hurl/tests_ok/stdout.hurl
Executable file
@ -0,0 +1,8 @@
|
||||
GET http://localhost:8000/stdout/text
|
||||
[Options]
|
||||
output: -
|
||||
HTTP 200
|
||||
`Hello`
|
||||
|
||||
|
||||
|
1
integration/hurl/tests_ok/stdout.out
Normal file
1
integration/hurl/tests_ok/stdout.out
Normal file
@ -0,0 +1 @@
|
||||
HelloHello
|
5
integration/hurl/tests_ok/stdout.ps1
Executable file
5
integration/hurl/tests_ok/stdout.ps1
Executable file
@ -0,0 +1,5 @@
|
||||
Set-StrictMode -Version latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
hurl --verbose --output - tests_ok/stdout.hurl
|
||||
|
||||
|
7
integration/hurl/tests_ok/stdout.py
Normal file
7
integration/hurl/tests_ok/stdout.py
Normal file
@ -0,0 +1,7 @@
|
||||
# coding=utf-8
|
||||
from app import app
|
||||
|
||||
|
||||
@app.route("/stdout/text")
|
||||
def stdout_text():
|
||||
return "Hello"
|
5
integration/hurl/tests_ok/stdout.sh
Executable file
5
integration/hurl/tests_ok/stdout.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
hurl --verbose tests_ok/stdout.hurl
|
||||
|
||||
|
@ -28,7 +28,7 @@ use hurl_core::ast::Retry;
|
||||
|
||||
use super::variables::{parse as parse_variable, parse_value};
|
||||
use super::OptionsError;
|
||||
use crate::cli::options::{ErrorFormat, HttpVersion, IpResolve};
|
||||
use crate::cli::options::{ErrorFormat, HttpVersion, IpResolve, Output};
|
||||
use crate::cli::OutputType;
|
||||
|
||||
pub fn cacert_file(arg_matches: &ArgMatches) -> Result<Option<String>, OptionsError> {
|
||||
@ -254,8 +254,14 @@ pub fn no_proxy(arg_matches: &ArgMatches) -> Option<String> {
|
||||
get::<String>(arg_matches, "noproxy")
|
||||
}
|
||||
|
||||
pub fn output(arg_matches: &ArgMatches) -> Option<String> {
|
||||
get::<String>(arg_matches, "output")
|
||||
pub fn output(arg_matches: &ArgMatches) -> Option<Output> {
|
||||
get::<String>(arg_matches, "output").map(|filename| {
|
||||
if filename == "-" {
|
||||
Output::StdOut
|
||||
} else {
|
||||
Output::File(filename)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn output_type(arg_matches: &ArgMatches) -> OutputType {
|
||||
|
@ -27,6 +27,7 @@ use std::time::Duration;
|
||||
use clap::ArgMatches;
|
||||
use hurl::http;
|
||||
use hurl::http::RequestedHttpVersion;
|
||||
use hurl::runner::Output;
|
||||
use hurl::util::logger::{LoggerOptions, LoggerOptionsBuilder, Verbosity};
|
||||
use hurl::util::path::ContextDir;
|
||||
use hurl_core::ast::{Entry, Retry};
|
||||
@ -63,7 +64,7 @@ pub struct Options {
|
||||
pub junit_file: Option<String>,
|
||||
pub max_redirect: Option<usize>,
|
||||
pub no_proxy: Option<String>,
|
||||
pub output: Option<String>,
|
||||
pub output: Option<Output>,
|
||||
pub output_type: OutputType,
|
||||
pub path_as_is: bool,
|
||||
pub progress_bar: bool,
|
||||
|
@ -35,6 +35,7 @@ use crate::http::request_spec::*;
|
||||
use crate::http::response::*;
|
||||
use crate::http::timings::Timings;
|
||||
use crate::http::{easy_ext, Call, Header, HttpError, Verbosity};
|
||||
use crate::runner::Output;
|
||||
use crate::util::logger::Logger;
|
||||
use crate::util::path::ContextDir;
|
||||
|
||||
@ -737,7 +738,7 @@ impl Client {
|
||||
&mut self,
|
||||
request_spec: &RequestSpec,
|
||||
context_dir: &ContextDir,
|
||||
output: Option<&str>,
|
||||
output: Option<&Output>,
|
||||
options: &ClientOptions,
|
||||
) -> String {
|
||||
let mut arguments = vec!["curl".to_string()];
|
||||
@ -973,7 +974,7 @@ mod tests {
|
||||
let data = b"GET /hello HTTP/1.1\r\nHost: localhost:8000\r\n\r\n";
|
||||
let lines = split_lines(data);
|
||||
assert_eq!(lines.len(), 3);
|
||||
assert_eq!(lines.get(0).unwrap().as_str(), "GET /hello HTTP/1.1");
|
||||
assert_eq!(lines.first().unwrap().as_str(), "GET /hello HTTP/1.1");
|
||||
assert_eq!(lines.get(1).unwrap().as_str(), "Host: localhost:8000");
|
||||
assert_eq!(lines.get(2).unwrap().as_str(), "");
|
||||
}
|
||||
@ -1118,7 +1119,8 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
let context_dir = ContextDir::default();
|
||||
let output = Some("/tmp/foo.bin");
|
||||
let file = Output::File("/tmp/foo.bin".to_string());
|
||||
let output = Some(&file);
|
||||
let options = ClientOptions {
|
||||
aws_sigv4: Some("aws:amz:sts".to_string()),
|
||||
cacert_file: Some("/etc/cert.pem".to_string()),
|
||||
|
@ -27,15 +27,15 @@ pub fn write_json(
|
||||
hurl_result: &HurlResult,
|
||||
content: &str,
|
||||
filename_in: &str,
|
||||
filename_out: &Option<String>,
|
||||
filename_out: &Option<Output>,
|
||||
) -> Result<(), Error> {
|
||||
let json_result = hurl_result.to_json(content, filename_in);
|
||||
let serialized = serde_json::to_string(&json_result).unwrap();
|
||||
let s = format!("{serialized}\n");
|
||||
let bytes = s.into_bytes();
|
||||
match filename_out {
|
||||
Some(file) => Output::File(file.to_string()).write(&bytes)?,
|
||||
None => Output::StdOut.write(&bytes)?,
|
||||
Some(Output::File(file)) => Output::File(file.to_string()).write(&bytes)?,
|
||||
_ => Output::StdOut.write(&bytes)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ pub fn write_body(
|
||||
filename_in: &str,
|
||||
include_headers: bool,
|
||||
color: bool,
|
||||
filename_out: &Option<String>,
|
||||
filename_out: &Option<Output>,
|
||||
logger: &Logger,
|
||||
) -> Result<(), Error> {
|
||||
// By default, we output the body response bytes of the last entry
|
||||
@ -65,8 +65,8 @@ pub fn write_body(
|
||||
output.extend(bytes);
|
||||
}
|
||||
match filename_out {
|
||||
Some(file) => Output::File(file.to_string()).write(&output)?,
|
||||
None => Output::StdOut.write(&output)?,
|
||||
Some(Output::File(file)) => Output::File(file.to_string()).write(&output)?,
|
||||
_ => runner::Output::StdOut.write(&output)?,
|
||||
}
|
||||
} else {
|
||||
logger.info("No response has been received");
|
||||
|
@ -84,13 +84,9 @@ pub fn run(
|
||||
log_request_spec(&http_request, logger);
|
||||
|
||||
logger.debug("Request can be run with the following curl command:");
|
||||
let output = &runner_options.output;
|
||||
let curl_command = http_client.curl_command_line(
|
||||
&http_request,
|
||||
context_dir,
|
||||
output.as_deref(),
|
||||
&client_options,
|
||||
);
|
||||
let output = runner_options.output.clone();
|
||||
let curl_command =
|
||||
http_client.curl_command_line(&http_request, context_dir, output.as_ref(), &client_options);
|
||||
logger.debug(curl_command.as_str());
|
||||
logger.debug("");
|
||||
|
||||
|
@ -28,7 +28,7 @@ use hurl_core::parser;
|
||||
|
||||
use crate::http::Call;
|
||||
use crate::runner::runner_options::RunnerOptions;
|
||||
use crate::runner::{entry, options, EntryResult, HurlResult, RunnerError, Value};
|
||||
use crate::runner::{entry, options, EntryResult, HurlResult, Output, RunnerError, Value};
|
||||
use crate::util::logger::{ErrorFormat, Logger, LoggerOptions, LoggerOptionsBuilder};
|
||||
use crate::{http, runner};
|
||||
|
||||
@ -220,14 +220,25 @@ pub fn run(
|
||||
// an error. If we want to treat it as an error, we've to add it to the current
|
||||
// `entry_result` errors, and optionally deals with retry if we can't write to the
|
||||
// specified path.
|
||||
if !runner_options.context_dir.is_access_allowed(&output) {
|
||||
let inner = RunnerError::UnauthorizedFileAccess {
|
||||
path: PathBuf::from(output.clone()),
|
||||
};
|
||||
let error = runner::Error::new(entry.request.source_info, inner, false);
|
||||
logger.warning(&error.fixme());
|
||||
} else if let Err(error) = entry_result.write_response(output) {
|
||||
logger.warning(&error.fixme());
|
||||
|
||||
let authorized = if let Output::File(filename) = &output {
|
||||
if !runner_options.context_dir.is_access_allowed(filename) {
|
||||
let inner = RunnerError::UnauthorizedFileAccess {
|
||||
path: PathBuf::from(filename.clone()),
|
||||
};
|
||||
let error = runner::Error::new(entry.request.source_info, inner, false);
|
||||
logger.warning(&error.fixme());
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if authorized {
|
||||
if let Err(error) = entry_result.write_response(&output) {
|
||||
logger.warning(&error.fixme());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -508,7 +519,7 @@ mod test {
|
||||
let non_default_options = get_non_default_options(&options);
|
||||
assert_eq!(non_default_options.len(), 1);
|
||||
|
||||
let first_non_default = non_default_options.get(0).unwrap();
|
||||
let first_non_default = non_default_options.first().unwrap();
|
||||
|
||||
assert_eq!(first_non_default.0, "delay");
|
||||
assert_eq!(first_non_default.1, "500ms");
|
||||
|
@ -25,7 +25,7 @@ use hurl_core::ast::{
|
||||
|
||||
use crate::http::{IpResolve, RequestedHttpVersion};
|
||||
use crate::runner::template::{eval_expression, eval_template};
|
||||
use crate::runner::{Error, Number, RunnerError, RunnerOptions, Value};
|
||||
use crate::runner::{Error, Number, Output, RunnerError, RunnerOptions, Value};
|
||||
use crate::util::logger::{Logger, Verbosity};
|
||||
|
||||
/// Returns a new [`RunnerOptions`] based on the `entry` optional Options section
|
||||
@ -166,9 +166,14 @@ pub fn get_entry_options(
|
||||
let value = eval_natural_option(value, variables)?;
|
||||
runner_options.max_redirect = Some(value as usize)
|
||||
}
|
||||
OptionKind::Output(filename) => {
|
||||
let value = eval_template(filename, variables)?;
|
||||
runner_options.output = Some(value)
|
||||
OptionKind::Output(output) => {
|
||||
let filename = eval_template(output, variables)?;
|
||||
let output = if filename == "-" {
|
||||
Output::StdOut
|
||||
} else {
|
||||
Output::File(filename)
|
||||
};
|
||||
runner_options.output = Some(output)
|
||||
}
|
||||
OptionKind::PathAsIs(value) => {
|
||||
let value = eval_boolean_option(value, variables)?;
|
||||
|
@ -16,20 +16,31 @@
|
||||
*
|
||||
*/
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
#[cfg(target_family = "windows")]
|
||||
use std::io::IsTerminal;
|
||||
use std::io::Write;
|
||||
use std::{fmt, io};
|
||||
|
||||
use crate::runner::{Error, RunnerError};
|
||||
use hurl_core::ast::{Pos, SourceInfo};
|
||||
|
||||
/// Represents the output of write operation: can be either a file or stdout.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Output {
|
||||
StdOut,
|
||||
File(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Output {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let output = match self {
|
||||
Output::StdOut => "-".to_string(),
|
||||
Output::File(file) => file.to_string(),
|
||||
};
|
||||
write!(f, "{output}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Output {
|
||||
/// Writes these `bytes` to the output.
|
||||
pub fn write(&self, bytes: &[u8]) -> Result<(), Error> {
|
||||
|
@ -108,7 +108,7 @@ pub type PredicateResult = Result<(), Error>;
|
||||
impl EntryResult {
|
||||
/// Writes the last HTTP response of this entry result to the file `filename`.
|
||||
/// The HTTP response can be decompressed if the entry's `compressed` option has been set.
|
||||
pub fn write_response(&self, filename: String) -> Result<(), Error> {
|
||||
pub fn write_response(&self, output: &Output) -> Result<(), Error> {
|
||||
match self.calls.last() {
|
||||
Some(call) => {
|
||||
let response = &call.response;
|
||||
@ -124,9 +124,9 @@ impl EntryResult {
|
||||
return Err(Error::new(source_info, e.into(), false));
|
||||
}
|
||||
};
|
||||
Output::File(filename).write(&bytes)
|
||||
output.write(&bytes)
|
||||
} else {
|
||||
Output::File(filename).write(&response.body)
|
||||
output.write(&response.body)
|
||||
}
|
||||
}
|
||||
None => Ok(()),
|
||||
|
@ -20,6 +20,7 @@ use std::time::Duration;
|
||||
use hurl_core::ast::{Entry, Retry};
|
||||
|
||||
use crate::http::{IpResolve, RequestedHttpVersion};
|
||||
use crate::runner::Output;
|
||||
use crate::util::path::ContextDir;
|
||||
|
||||
pub struct RunnerOptionsBuilder {
|
||||
@ -42,7 +43,7 @@ pub struct RunnerOptionsBuilder {
|
||||
ip_resolve: IpResolve,
|
||||
max_redirect: Option<usize>,
|
||||
no_proxy: Option<String>,
|
||||
output: Option<String>,
|
||||
output: Option<Output>,
|
||||
path_as_is: bool,
|
||||
post_entry: Option<fn() -> bool>,
|
||||
pre_entry: Option<fn(Entry) -> bool>,
|
||||
@ -256,7 +257,7 @@ impl RunnerOptionsBuilder {
|
||||
}
|
||||
|
||||
/// Specifies the file to output the HTTP response instead of stdout.
|
||||
pub fn output(&mut self, output: Option<String>) -> &mut Self {
|
||||
pub fn output(&mut self, output: Option<Output>) -> &mut Self {
|
||||
self.output = output;
|
||||
self
|
||||
}
|
||||
@ -404,7 +405,7 @@ pub struct RunnerOptions {
|
||||
pub(crate) insecure: bool,
|
||||
pub(crate) max_redirect: Option<usize>,
|
||||
pub(crate) no_proxy: Option<String>,
|
||||
pub(crate) output: Option<String>,
|
||||
pub(crate) output: Option<Output>,
|
||||
pub(crate) path_as_is: bool,
|
||||
pub(crate) post_entry: Option<fn() -> bool>,
|
||||
pub(crate) pre_entry: Option<fn(Entry) -> bool>,
|
||||
|
Loading…
Reference in New Issue
Block a user