diff --git a/README.md b/README.md index 4d8d9afc894..260636e23b5 100644 --- a/README.md +++ b/README.md @@ -1490,7 +1490,7 @@ will follow a redirection only for the second entry. |------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | --color | Colorize standard output and standard error.

By default, Hurl outputs a prettified and colorized response. When redirected through pipes, standard streams are not colorized and color can be forced with this option.

Environment variables: HURL_COLOR

This is a cli-only option.
| | --curl <FILE> | Export each request to a list of curl commands.

This is a cli-only option.
| -| --error-format <FORMAT> | Control the format of error message (short by default or long). When using long,
the response body is logged when there are errors.

This is a cli-only option.
| +| --error-format <FORMAT> | Control the format of error message (short by default or long). When using long, the response body is logged when there are errors.

Environment variables: HURL_ERROR_FORMAT

This is a cli-only option.
| | -i, --include | Include the HTTP headers in the output

This is a cli-only option.
| | --json | Output each Hurl file result to JSON. The format is very closed to HAR format.

This is a cli-only option.
| | --no-color | Do not colorize standard output nor standard error.

Environment variables: HURL_NO_COLOR NO_COLOR

This is a cli-only option.
| diff --git a/docs/manual.md b/docs/manual.md index e3086bd3ee5..144f29c3eb5 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -192,7 +192,7 @@ will follow a redirection only for the second entry. |------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | --color | Colorize standard output and standard error.

By default, Hurl outputs a prettified and colorized response. When redirected through pipes, standard streams are not colorized and color can be forced with this option.

Environment variables: HURL_COLOR

This is a cli-only option.
| | --curl <FILE> | Export each request to a list of curl commands.

This is a cli-only option.
| -| --error-format <FORMAT> | Control the format of error message (short by default or long). When using long,
the response body is logged when there are errors.

This is a cli-only option.
| +| --error-format <FORMAT> | Control the format of error message (short by default or long). When using long, the response body is logged when there are errors.

Environment variables: HURL_ERROR_FORMAT

This is a cli-only option.
| | -i, --include | Include the HTTP headers in the output

This is a cli-only option.
| | --json | Output each Hurl file result to JSON. The format is very closed to HAR format.

This is a cli-only option.
| | --no-color | Do not colorize standard output nor standard error.

Environment variables: HURL_NO_COLOR NO_COLOR

This is a cli-only option.
| diff --git a/docs/manual/hurl.1 b/docs/manual/hurl.1 index df499459435..342d96718eb 100644 --- a/docs/manual/hurl.1 +++ b/docs/manual/hurl.1 @@ -1,4 +1,4 @@ -.TH hurl 1 "09 Mar 2026" "hurl 8.0.0-SNAPSHOT" " Hurl Manual" +.TH hurl 1 "13 Mar 2026" "hurl 8.0.0-SNAPSHOT" " Hurl Manual" .SH NAME hurl - run and test HTTP requests. @@ -332,8 +332,9 @@ This is a cli-only option. .IP "--error-format " -Control the format of error message (short by default or long). When using long, -the response body is logged when there are errors. +Control the format of error message (short by default or long). When using long, the response body is logged when there are errors. + +Environment variables: HURL_ERROR_FORMAT This is a cli-only option. diff --git a/docs/manual/hurl.md b/docs/manual/hurl.md index 82e5f5115e3..3c3aa89d100 100644 --- a/docs/manual/hurl.md +++ b/docs/manual/hurl.md @@ -350,8 +350,9 @@ This is a cli-only option. #### --error-format {#error-format} -Control the format of error message (short by default or long). When using long, -the response body is logged when there are errors. +Control the format of error message (short by default or long). When using long, the response body is logged when there are errors. + +Environment variables: HURL_ERROR_FORMAT This is a cli-only option. diff --git a/docs/manual/hurlfmt.1 b/docs/manual/hurlfmt.1 index 61e4b918eae..2db95bb4ee2 100644 --- a/docs/manual/hurlfmt.1 +++ b/docs/manual/hurlfmt.1 @@ -1,4 +1,4 @@ -.TH hurl 1 "09 Mar 2026" "hurl 8.0.0-SNAPSHOT" " Hurl Manual" +.TH hurl 1 "13 Mar 2026" "hurl 8.0.0-SNAPSHOT" " Hurl Manual" .SH NAME hurlfmt - format Hurl files diff --git a/docs/spec/options/hurl/error_format.option b/docs/spec/options/hurl/error_format.option index beb90e5eaf2..490f0c94b19 100644 --- a/docs/spec/options/hurl/error_format.option +++ b/docs/spec/options/hurl/error_format.option @@ -6,6 +6,6 @@ value_parser: ["short", "long"] help: Control the format of error messages help_heading: Output options cli_only: true +env_var: HURL_ERROR_FORMAT --- -Control the format of error message (short by default or long). When using long, -the response body is logged when there are errors. +Control the format of error message (short by default or long). When using long, the response body is logged when there are errors. diff --git a/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.err.pattern b/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.err.pattern new file mode 100644 index 00000000000..fab2b5bfaf7 --- /dev/null +++ b/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.err.pattern @@ -0,0 +1,180 @@ +* Request can be run with the following curl command: +* curl 'http://localhost:8000/error-format-long/html' + +HTTP/1.1 200 +Content-Length: 45 +Content-Type: text/html; charset=utf-8 +Date: <<<.*?>>> +Server: Flask Server +Via: waitress + +Test + +error: Assert header value + --> tests_failed/error_format_long/error_format_long.hurl:7:15 + | + | GET http://localhost:8000/error-format-long/html + | ... + 7 | Content-Type: text/html + | ^^^^^^^^^ actual value is + | + +error: Assert failure + --> tests_failed/error_format_long/error_format_long.hurl:9:0 + | + | GET http://localhost:8000/error-format-long/html + | ... + 9 | xpath "string(//head/title)" == "Welcome!" + | actual: string + | expected: string + | + +error: Assert failure + --> tests_failed/error_format_long/error_format_long.hurl:11:0 + | + | GET http://localhost:8000/error-format-long/html + | ... +11 | xpath "//title" count == 2 + | actual: integer <1> + | expected: integer <2> + | + +* Request can be run with the following curl command: +* curl 'http://localhost:8000/error-format-long/json' + +HTTP/1.1 200 +Content-Length: 115 +Content-Type: application/json +Date: <<<.*?>>> +Server: Flask Server +Via: waitress + +{"books": [{"name": "Dune", "author": "Franck Herbert"}, {"name": "Les Mis\u00e9rables", "author": "Victor Hugo"}]} + +error: Assert failure + --> tests_failed/error_format_long/error_format_long.hurl:18:0 + | + | GET http://localhost:8000/error-format-long/json + | ... +18 | jsonpath "$.books" count == 12 + | actual: integer <2> + | expected: integer <12> + | + +* Request can be run with the following curl command: +* curl 'http://localhost:8000/error-format-long/rfc-7807' + +HTTP/1.1 200 +Content-Length: 258 +Content-Type: application/problem+json +Date: <<<.*?>>> +Server: Flask Server +Via: waitress + +{"type": "https://example.com/probs/out-of-credit", "title": "You do not have enough credit.", "detail": "Your current balance is 30, but that costs 50.", "instance": "/account/12345/msgs/abc", "balance": 30, "accounts": ["/account/12345", "/account/67890"]} + +error: Assert failure + --> tests_failed/error_format_long/error_format_long.hurl:26:0 + | + | GET http://localhost:8000/error-format-long/rfc-7807 + | ... +26 | jsonpath "$.title" == "You have enough credit." + | actual: string + | expected: string + | + +* Request can be run with the following curl command: +* curl 'http://localhost:8000/error-format-long/fhir' + +HTTP/1.1 200 +Content-Length: 902 +Content-Type: application/fhir+json +Date: <<<.*?>>> +Server: Flask Server +Via: waitress + +{"resourceType": "Practitioner", "id": "example", "text": {"status": "generated", "div": "
\n

Dr Adam Careful is a Referring Practitioner for Acme Hospital from 1-Jan 2012 to 31-Mar\n 2012

\n
"}, "identifier": [{"system": "http://www.acme.org/practitioners", "value": "23"}], "active": true, "name": [{"family": "Careful", "given": ["Adam"], "prefix": ["Dr"]}], "address": [{"use": "home", "line": ["534 Erewhon St"], "city": "PleasantVille", "state": "Vic", "postalCode": "3999"}], "qualification": [{"identifier": [{"system": "http://example.org/UniversityIdentifier", "value": "12345"}], "code": {"coding": [{"system": "http://terminology.hl7.org/CodeSystem/v2-0360/2.7", "code": "BS", "display": "Bachelor of Science"}], "text": "Bachelor of Science"}, "period": {"start": "1995"}, "issuer": {"display": "Example University"}}]} + +error: Assert failure + --> tests_failed/error_format_long/error_format_long.hurl:34:0 + | + | GET http://localhost:8000/error-format-long/fhir + | ... +34 | jsonpath "$.id" == "foo" + | actual: string + | expected: string + | + +* Request can be run with the following curl command: +* curl 'http://localhost:8000/error-format-long/csv' + +HTTP/1.1 200 +Content-Length: 726 +Content-Type: text/csv; charset=utf-8 +Date: <<<.*?>>> +Server: Flask Server +Via: waitress + +"Year","Score","Title" +1968,86,"Greetings" +1970,17,"Bloody Mama" +1970,73,"Hi, Mom!" +1971,40,"Born to Win" +1973,98,"Mean Streets" +1973,88,"Bang the Drum Slowly" +1974,97,"The Godfather, Part II" +1976,41,"The Last Tycoon" +1976,99,"Taxi Driver" +1977,47,"1900" +1977,67,"New York, New York" +1978,93,"The Deer Hunter" +1980,97,"Raging Bull" +1981,75,"True Confessions" +1983,90,"The King of Comedy" +1984,89,"Once Upon a Time in America" +1984,60,"Falling in Love" +1985,98,"Brazil" +1986,65,"The Mission" +1987,00,"Dear America: Letters Home From Vietnam" +1987,80,"The Untouchables" +1987,78,"Angel Heart" +1988,96,"Midnight Run" +1989,64,"Jacknife" +1989,47,"We're No Angels" +1990,88,"Awakenings" +1990,29,"Stanley & Iris" +1990,96,"Goodfellas" + + +error: Assert failure + --> tests_failed/error_format_long/error_format_long.hurl:42:0 + | + | GET http://localhost:8000/error-format-long/csv + | ... +42 | body split "\n" nth 9 split "," nth 2 == "\"Taxi\"" + | actual: string <"Taxi Driver"> + | expected: string <"Taxi"> + | + +* Request can be run with the following curl command: +* curl 'http://localhost:8000/error-format-long/bytes' + +HTTP/1.1 200 +Content-Length: 4 +Content-Type: application/octet-stream +Date: <<<.*?>>> +Server: Flask Server +Via: waitress + +Bytes + +error: Assert failure + --> tests_failed/error_format_long/error_format_long.hurl:50:0 + | + | GET http://localhost:8000/error-format-long/bytes + | ... +50 | bytes == hex,beef; + | actual: bytes + | expected: bytes + | + diff --git a/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.exit b/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.exit new file mode 100644 index 00000000000..b8626c4cff2 --- /dev/null +++ b/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.exit @@ -0,0 +1 @@ +4 diff --git a/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.ps1 b/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.ps1 new file mode 100755 index 00000000000..9f98c57cea1 --- /dev/null +++ b/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.ps1 @@ -0,0 +1,5 @@ +Set-StrictMode -Version latest +$ErrorActionPreference = 'Stop' + +$env:HURL_ERROR_FORMAT = 'long' +hurl --continue-on-error tests_failed/error_format_long/error_format_long.hurl diff --git a/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.sh b/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.sh new file mode 100755 index 00000000000..554530b1f84 --- /dev/null +++ b/integration/hurl/tests_failed/error_format_long/error_format_long_env_var.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -Eeuo pipefail + +export HURL_ERROR_FORMAT=long +hurl --continue-on-error tests_failed/error_format_long/error_format_long.hurl diff --git a/packages/hurl/README.md b/packages/hurl/README.md index 707252e1abe..6e6b1962e59 100644 --- a/packages/hurl/README.md +++ b/packages/hurl/README.md @@ -1490,7 +1490,7 @@ will follow a redirection only for the second entry. |------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | --color | Colorize standard output and standard error.

By default, Hurl outputs a prettified and colorized response. When redirected through pipes, standard streams are not colorized and color can be forced with this option.

Environment variables: HURL_COLOR

This is a cli-only option.
| | --curl <FILE> | Export each request to a list of curl commands.

This is a cli-only option.
| -| --error-format <FORMAT> | Control the format of error message (short by default or long). When using long,
the response body is logged when there are errors.

This is a cli-only option.
| +| --error-format <FORMAT> | Control the format of error message (short by default or long). When using long, the response body is logged when there are errors.

Environment variables: HURL_ERROR_FORMAT

This is a cli-only option.
| | -i, --include | Include the HTTP headers in the output

This is a cli-only option.
| | --json | Output each Hurl file result to JSON. The format is very closed to HAR format.

This is a cli-only option.
| | --no-color | Do not colorize standard output nor standard error.

Environment variables: HURL_NO_COLOR NO_COLOR

This is a cli-only option.
| diff --git a/packages/hurl/src/cli/options/args.rs b/packages/hurl/src/cli/options/args.rs index d4adab6467e..608c4c753d9 100644 --- a/packages/hurl/src/cli/options/args.rs +++ b/packages/hurl/src/cli/options/args.rs @@ -188,7 +188,7 @@ fn parse_arg_matches( let curl_file = curl_file(arg_matches, default_options.curl_file); let delay = delay(arg_matches, default_options.delay)?; let digest = digest(arg_matches, default_options.digest); - let error_format = error_format(arg_matches, default_options.error_format); + let error_format = error_format(arg_matches, default_options.error_format)?; let file_root = file_root(arg_matches, default_options.file_root); let (follow_location, follow_location_trusted) = follow_location( arg_matches, @@ -457,14 +457,13 @@ fn digest(arg_matches: &ArgMatches, default_value: bool) -> bool { } } -fn error_format(arg_matches: &ArgMatches, default_value: ErrorFormat) -> ErrorFormat { +fn error_format( + arg_matches: &ArgMatches, + default_value: ErrorFormat, +) -> Result { match get::(arg_matches, "error_format") { - Some(error_format) => match error_format.as_str() { - "long" => ErrorFormat::Long, - "short" => ErrorFormat::Short, - _ => ErrorFormat::Short, - }, - None => default_value, + Some(value) => ErrorFormat::from_str(&value), + None => Ok(default_value), } } diff --git a/packages/hurl/src/cli/options/context.rs b/packages/hurl/src/cli/options/context.rs index 4379224edc5..2b4774c6525 100644 --- a/packages/hurl/src/cli/options/context.rs +++ b/packages/hurl/src/cli/options/context.rs @@ -48,17 +48,18 @@ const LEGACY_HURL_VARIABLE_PREFIX: &str = "HURL_"; const HURL_PREFIX: &str = "HURL_"; const HURL_SECRET_PREFIX: &str = "HURL_SECRET_"; -const HURL_COLOR: &str = "HURL_COLOR"; -const HURL_CONNECT_TIMEOUT: &str = "HURL_CONNECT_TIMEOUT"; -const HURL_NO_COLOR: &str = "HURL_NO_COLOR"; -const HURL_HEADER: &str = "HURL_HEADER"; -const HURL_IPV4: &str = "HURL_IPV4"; -const HURL_IPV6: &str = "HURL_IPV6"; -const HURL_VARIABLE_PREFIX: &str = "HURL_VARIABLE_"; -const HURL_VERBOSE: &str = "HURL_VERBOSE"; -const HURL_VERY_VERBOSE: &str = "HURL_VERY_VERBOSE"; -const HURL_VERBOSITY: &str = "HURL_VERBOSITY"; -const HURL_MAX_TIME: &str = "HURL_MAX_TIME"; +pub const HURL_COLOR: &str = "HURL_COLOR"; +pub const HURL_CONNECT_TIMEOUT: &str = "HURL_CONNECT_TIMEOUT"; +pub const HURL_ERROR_FORMAT: &str = "HURL_ERROR_FORMAT"; +pub const HURL_NO_COLOR: &str = "HURL_NO_COLOR"; +pub const HURL_HEADER: &str = "HURL_HEADER"; +pub const HURL_IPV4: &str = "HURL_IPV4"; +pub const HURL_IPV6: &str = "HURL_IPV6"; +pub const HURL_VARIABLE_PREFIX: &str = "HURL_VARIABLE_"; +pub const HURL_VERBOSE: &str = "HURL_VERBOSE"; +pub const HURL_VERY_VERBOSE: &str = "HURL_VERY_VERBOSE"; +pub const HURL_VERBOSITY: &str = "HURL_VERBOSITY"; +pub const HURL_MAX_TIME: &str = "HURL_MAX_TIME"; impl RunContext { /// Creates a new context. The environment is captured and will be seen as non-mutable for the @@ -86,6 +87,26 @@ impl RunContext { } } + /// Returns the config file path if any. + pub fn config_file_path(&self) -> Option<&Path> { + self.config_file.as_deref() + } + + /// Checks if standard input is a terminal. + pub fn is_stdin_term(&self) -> bool { + self.stdin_term + } + + /// Checks if standard output is a terminal. + pub fn is_stdout_term(&self) -> bool { + self.stdout_term + } + + /// Checks if standard error is a terminal. + pub fn is_stderr_term(&self) -> bool { + self.stderr_term + } + /// Returns the env var for connect timeout duration. pub fn connect_timeout_env_var(&self) -> Option<&str> { self.hurl_env_vars @@ -93,23 +114,11 @@ impl RunContext { .map(|v| v.as_str()) } - /// Returns `Some(true)` if ANSI escape codes are explicitly enabled, `Some(false)` if ANSI escape - /// codes are explicitly disabled, `None` otherwise. . - pub fn use_color_env_var(&self) -> Option { - // According to the NO_COLOR spec, any presence of the variable should disable color, but to - // maintain backward compatibility with code < 7.1.0, we check that the NO_COLOR env is at - // least not empty. - if let Some(v) = self.env_vars.get("NO_COLOR") { - if !v.is_empty() { - Some(false) - } else { - None - } - } else if let Some(v) = self.get_env_var_bool(HURL_NO_COLOR) { - Some(!v) - } else { - self.get_env_var_bool(HURL_COLOR) - } + /// Returns the env var for error format. + pub fn error_format_env_var(&self) -> Option<&str> { + self.hurl_env_vars + .get(HURL_ERROR_FORMAT) + .map(|v| v.as_str()) } /// Returns the map of Hurl headers injected by environment variables. @@ -130,23 +139,6 @@ impl RunContext { self.get_env_var_bool(HURL_IPV6) } - pub fn verbose_env_var(&self) -> Option { - self.get_env_var_bool(HURL_VERBOSE) - } - - pub fn very_verbose_env_var(&self) -> Option { - self.get_env_var_bool(HURL_VERY_VERBOSE) - } - - pub fn verbosity_env_var(&self) -> Option<&str> { - self.hurl_env_vars.get(HURL_VERBOSITY).map(|v| v.as_str()) - } - - /// Returns the env var for max time duration. - pub fn max_time_env_var(&self) -> Option<&str> { - self.hurl_env_vars.get(HURL_MAX_TIME).map(|v| v.as_str()) - } - /// Returns `true` if the context is run from a CI context (like GitHub Actions, GitLab CI/CD etc...) /// `false` otherwise. pub fn is_ci_env_var(&self) -> bool { @@ -154,70 +146,85 @@ impl RunContext { self.env_vars.contains_key("CI") || self.env_vars.contains_key("TF_BUILD") } - /// Returns the map of Hurl variables injected by environment variables. + /// Returns the env var for max time duration. + pub fn max_time_env_var(&self) -> Option<&str> { + self.hurl_env_vars.get(HURL_MAX_TIME).map(|v| v.as_str()) + } + + /// Returns the map of Hurl secrets injected by environment variables. /// - /// Environment variables are prefixed with `HURL_VARIABLE_` and returned values have their name + /// Environment variables are prefixed with `HURL_SECRET_` and returned values have their name /// stripped of this prefix. - pub fn var_env_vars(&self) -> HashMap<&str, &str> { + pub fn secret_env_vars(&self) -> HashMap<&str, &str> { self.hurl_env_vars .iter() .filter_map(|(name, value)| { - name.strip_prefix(HURL_VARIABLE_PREFIX) + name.strip_prefix(HURL_SECRET_PREFIX) .filter(|n| !n.is_empty()) .map(|stripped| (stripped, value.as_str())) }) .collect() } - /// Returns the map of legacy Hurl variables injected by environment variables. + /// Returns `Some(true)` if ANSI escape codes are explicitly enabled, `Some(false)` if ANSI escape + /// codes are explicitly disabled, `None` otherwise. . + pub fn use_color_env_var(&self) -> Option { + // According to the NO_COLOR spec, any presence of the variable should disable color, but to + // maintain backward compatibility with code < 7.1.0, we check that the NO_COLOR env is at + // least not empty. + if let Some(v) = self.env_vars.get("NO_COLOR") { + if !v.is_empty() { + Some(false) + } else { + None + } + } else if let Some(v) = self.get_env_var_bool(HURL_NO_COLOR) { + Some(!v) + } else { + self.get_env_var_bool(HURL_COLOR) + } + } + + /// Returns the map of Hurl variables injected by environment variables. /// - /// Environment variables are prefixed with `HURL_` and returned values have their name + /// Environment variables are prefixed with `HURL_VARIABLE_` and returned values have their name /// stripped of this prefix. - pub fn legacy_var_env_vars(&self) -> HashMap<&str, &str> { + pub fn var_env_vars(&self) -> HashMap<&str, &str> { self.hurl_env_vars .iter() - .filter(|(name, _)| !is_hurl_option(name)) .filter_map(|(name, value)| { - name.strip_prefix(LEGACY_HURL_VARIABLE_PREFIX) + name.strip_prefix(HURL_VARIABLE_PREFIX) .filter(|n| !n.is_empty()) .map(|stripped| (stripped, value.as_str())) }) .collect() } - /// Returns the map of Hurl secrets injected by environment variables. + /// Returns the map of legacy Hurl variables injected by environment variables. /// - /// Environment variables are prefixed with `HURL_SECRET_` and returned values have their name + /// Environment variables are prefixed with `HURL_` and returned values have their name /// stripped of this prefix. - pub fn secret_env_vars(&self) -> HashMap<&str, &str> { + pub fn legacy_var_env_vars(&self) -> HashMap<&str, &str> { self.hurl_env_vars .iter() + .filter(|(name, _)| !is_hurl_option(name)) .filter_map(|(name, value)| { - name.strip_prefix(HURL_SECRET_PREFIX) + name.strip_prefix(LEGACY_HURL_VARIABLE_PREFIX) .filter(|n| !n.is_empty()) .map(|stripped| (stripped, value.as_str())) }) .collect() } - /// Checks if standard input is a terminal. - pub fn is_stdin_term(&self) -> bool { - self.stdin_term - } - - /// Checks if standard output is a terminal. - pub fn is_stdout_term(&self) -> bool { - self.stdout_term + pub fn verbose_env_var(&self) -> Option { + self.get_env_var_bool(HURL_VERBOSE) } - - /// Checks if standard error is a terminal. - pub fn is_stderr_term(&self) -> bool { - self.stderr_term + pub fn verbosity_env_var(&self) -> Option<&str> { + self.hurl_env_vars.get(HURL_VERBOSITY).map(|v| v.as_str()) } - /// Returns the config file path if any. - pub fn config_file_path(&self) -> Option<&Path> { - self.config_file.as_deref() + pub fn very_verbose_env_var(&self) -> Option { + self.get_env_var_bool(HURL_VERY_VERBOSE) } fn get_env_var_bool(&self, name: &'static str) -> Option { @@ -239,6 +246,7 @@ fn is_hurl_option(name: &str) -> bool { || name.starts_with(HURL_SECRET_PREFIX) || name == HURL_COLOR || name == HURL_CONNECT_TIMEOUT + || name == HURL_ERROR_FORMAT || name == HURL_HEADER || name == HURL_IPV4 || name == HURL_IPV6 diff --git a/packages/hurl/src/cli/options/env_vars.rs b/packages/hurl/src/cli/options/env_vars.rs index ecbb7a1ef3a..46aa77ca801 100644 --- a/packages/hurl/src/cli/options/env_vars.rs +++ b/packages/hurl/src/cli/options/env_vars.rs @@ -15,14 +15,17 @@ * limitations under the License. * */ +use std::collections::HashMap; +use std::str::FromStr; + use super::variables::TypeKind; use super::{ - duration, secret, variables, CliOptions, CliOptionsError, IpResolve, RunContext, Verbosity, + context::HURL_CONNECT_TIMEOUT, context::HURL_ERROR_FORMAT, context::HURL_HEADER, + context::HURL_MAX_TIME, context::HURL_VERBOSITY, duration, secret, variables, CliOptions, + CliOptionsError, ErrorFormat, IpResolve, RunContext, Verbosity, }; use hurl::runner::Value; use hurl_core::types::DurationUnit; -use std::collections::HashMap; -use std::str::FromStr; /// Parses Hurl configuration defined in environment variables. pub fn parse_env_vars( @@ -30,12 +33,29 @@ pub fn parse_env_vars( default_options: CliOptions, ) -> Result { let mut options = default_options; - options.variables = parse_variables(context, options.variables)?; - options.secrets = parse_secrets(context, options.secrets)?; if let Some(color) = context.use_color_env_var() { options.color_stdout = color; options.color_stderr = color; } + if let Some(timeout) = context.connect_timeout_env_var() { + options.connect_timeout = duration::duration_from_str(timeout, DurationUnit::Second) + .map_err(|e| with_env_var(e, HURL_CONNECT_TIMEOUT))?; + } + if let Some(error_format) = context.error_format_env_var() { + let error_format = + ErrorFormat::from_str(error_format).map_err(|e| with_env_var(e, HURL_ERROR_FORMAT))?; + options.error_format = error_format; + } + if let Some(header) = context.header_env_var() { + let headers = header.split("|").map(|h| h.to_string()).collect::>(); + for h in &headers { + if !h.contains(':') { + let msg = "Invalid header <{h}> missing `:`".to_string(); + return Err(with_env_var(CliOptionsError::Error(msg), HURL_HEADER)); + } + } + options.headers.extend(headers); + } if let Some(ipv4) = context.ipv4_env_var() { if ipv4 { options.ip_resolve = Some(IpResolve::IpV4); @@ -50,31 +70,20 @@ pub fn parse_env_vars( options.ip_resolve = Some(IpResolve::IpV4); } } + options.variables = parse_variables(context, options.variables)?; + options.secrets = parse_secrets(context, options.secrets)?; if let Some(true) = context.verbose_env_var() { options.verbosity = Some(Verbosity::Verbose); } else if let Some(true) = context.very_verbose_env_var() { options.verbosity = Some(Verbosity::Debug); } else if let Some(verbosity) = context.verbosity_env_var() { - let verbosity = Verbosity::from_str(verbosity)?; + let verbosity = + Verbosity::from_str(verbosity).map_err(|e| with_env_var(e, HURL_VERBOSITY))?; options.verbosity = Some(verbosity); } if let Some(timeout) = context.max_time_env_var() { - options.timeout = duration::duration_from_str(timeout, DurationUnit::Second)?; - } - if let Some(timeout) = context.connect_timeout_env_var() { - options.connect_timeout = duration::duration_from_str(timeout, DurationUnit::Second)?; - } - if let Some(header) = context.header_env_var() { - let headers = header.split("|").map(|h| h.to_string()).collect::>(); - for h in &headers { - if !h.contains(':') { - let msg = format!( - "Invalid header <{h}> in HURL_HEADER environment variable, missing `:`" - ); - return Err(CliOptionsError::Error(msg)); - } - } - options.headers.extend(headers); + options.timeout = duration::duration_from_str(timeout, DurationUnit::Second) + .map_err(|e| with_env_var(e, HURL_MAX_TIME))?; } Ok(options) } @@ -128,6 +137,19 @@ fn parse_secrets( Ok(secrets) } +fn with_env_var(error: CliOptionsError, env: &'static str) -> CliOptionsError { + match error { + CliOptionsError::DisplayHelp(_) => error, + CliOptionsError::DisplayVersion(_) => error, + CliOptionsError::NoInput(_) => error, + CliOptionsError::Error(message) => { + let message = format!("{message} ({env} environment variable)"); + CliOptionsError::Error(message) + } + CliOptionsError::InvalidInputFile(_) => error, + } +} + #[cfg(test)] mod tests { use super::{parse_env_vars, CliOptions, RunContext}; diff --git a/packages/hurl/src/cli/options/mod.rs b/packages/hurl/src/cli/options/mod.rs index 4b2cbd3ca4c..a582412e77a 100644 --- a/packages/hurl/src/cli/options/mod.rs +++ b/packages/hurl/src/cli/options/mod.rs @@ -133,7 +133,7 @@ impl FromStr for Verbosity { "debug" => Ok(Verbosity::Debug), _ => { let message = format!( - "invalid value '{s}' for verbosity [possible values: brief, verbose, debug]" + "Invalid value '{s}' for verbosity [possible values: brief, verbose, debug]" ); Err(CliOptionsError::Error(message)) } @@ -148,6 +148,22 @@ pub enum ErrorFormat { Long, } +impl FromStr for ErrorFormat { + type Err = CliOptionsError; + + fn from_str(s: &str) -> Result { + match s { + "long" => Ok(ErrorFormat::Long), + "short" => Ok(ErrorFormat::Short), + _ => { + let message = + format!("Invalid value '{s}' for error-format [possible values: long, short]"); + Err(CliOptionsError::Error(message)) + } + } + } +} + impl From for logger::ErrorFormat { fn from(value: ErrorFormat) -> Self { match value {