Add Retry Type

This commit is contained in:
Fabrice Reix 2023-05-27 13:46:46 +02:00 committed by jcamiel
parent b5277b3349
commit a149635409
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
12 changed files with 94 additions and 38 deletions

View File

@ -22,6 +22,7 @@ use crate::cli::OutputType;
use atty::Stream;
use clap::ArgMatches;
use hurl::runner::Value;
use hurl_core::ast::Retry;
use std::collections::HashMap;
use std::env;
use std::fs::File;
@ -243,10 +244,11 @@ pub fn resolves(arg_matches: &ArgMatches) -> Vec<String> {
get_strings(arg_matches, "resolve").unwrap_or_default()
}
pub fn retry(arg_matches: &ArgMatches) -> Option<usize> {
pub fn retry(arg_matches: &ArgMatches) -> Retry {
match get::<i32>(arg_matches, "retry").unwrap() {
r if r == -1 => None,
r => Some(r as usize),
r if r == -1 => Retry::Infinite,
r if r == 0 => Retry::None,
r => Retry::Finite(r as usize),
}
}

View File

@ -29,7 +29,7 @@ use clap::ArgMatches;
use hurl::libcurl_version_info;
use hurl::util::logger::{LoggerOptions, LoggerOptionsBuilder, Verbosity};
use hurl::util::path::ContextDir;
use hurl_core::ast::Entry;
use hurl_core::ast::{Entry, Retry};
use crate::cli;
use crate::runner::{RunnerOptions, RunnerOptionsBuilder, Value};
@ -63,7 +63,7 @@ pub struct Options {
pub progress_bar: bool,
pub proxy: Option<String>,
pub resolves: Vec<String>,
pub retry: Option<usize>,
pub retry: Retry,
pub retry_interval: Duration,
pub ssl_no_revoke: bool,
pub test: bool,

View File

@ -15,6 +15,7 @@
* limitations under the License.
*
*/
use hurl_core::ast::Retry;
use std::time::Duration;
#[derive(Debug, Clone)]
@ -32,7 +33,7 @@ pub struct ClientOptions {
pub no_proxy: Option<String>,
pub proxy: Option<String>,
pub resolves: Vec<String>,
pub retry: Option<usize>,
pub retry: Retry,
pub ssl_no_revoke: bool,
pub timeout: Duration,
pub user: Option<String>,
@ -62,7 +63,7 @@ impl Default for ClientOptions {
no_proxy: None,
proxy: None,
resolves: vec![],
retry: Some(0),
retry: Retry::None,
ssl_no_revoke: false,
timeout: Duration::from_secs(300),
user: None,
@ -167,7 +168,7 @@ mod tests {
"foo.com:80:192.168.0.1".to_string(),
"bar.com:443:127.0.0.1".to_string()
],
retry: Some(0),
retry: Retry::None,
ssl_no_revoke: false,
timeout: Duration::from_secs(10),
connect_timeout: Duration::from_secs(20),

View File

@ -335,7 +335,7 @@ pub fn get_entry_options(
logger.debug(format!("max-redirs: {}", option.value).as_str());
}
EntryOption::Retry(option) => {
runner_options.retry = Some(option.value);
runner_options.retry = option.value;
logger.debug(format!("retry: {}", option.value).as_str());
}
EntryOption::RetryInterval(option) => {

View File

@ -157,25 +157,26 @@ pub fn run(
Ok(options) => (options.retry, options.retry_interval),
Err(_) => (runner_options.retry, runner_options.retry_interval),
};
let retry_max_reached = match retry_opts {
Some(r) => retry_count > r,
None => false,
// The retry threshold can only reached with a finite positive number of retries
let retry_max_reached = if let Retry::Finite(r) = retry_opts {
retry_count > r
} else {
false
};
let retry = retry_opts.is_some() && !retry_max_reached && has_error;
// If `retry_max_reached` is true, we print now a warning, before displaying
// any assert error so any potential error is the last thing displayed to the user.
// If `retry_max_reached` is not true (for instance `retry`is true, or there is no error
// we first log the error and a potential warning about retrying.
logger.test_erase_line();
if retry_max_reached && retry_opts != Some(0) {
if retry_max_reached {
logger.debug_important("Retry max count reached, no more retry");
logger.debug("");
}
let retry = !matches!(retry_opts, Retry::None) && !retry_max_reached && has_error;
if has_error {
log_errors(&entry_result, content, retry, &logger);
}
entries.push(entry_result);
if retry {
@ -323,10 +324,7 @@ fn log_run_info(
if let Some(proxy) = &runner_options.proxy {
logger.debug(format!(" proxy: {proxy}").as_str());
}
match runner_options.retry {
None => logger.debug(" retry: indefinitely".to_string().as_str()),
Some(n) => logger.debug(format!(" retry: {n}").as_str()),
}
logger.debug(format!(" retry: {}", runner_options.retry).as_str());
if !variables.is_empty() {
logger.debug_important("Variables:");
for (name, value) in variables.iter() {

View File

@ -17,7 +17,7 @@
*/
use std::time::Duration;
use hurl_core::ast::Entry;
use hurl_core::ast::{Entry, Retry};
use crate::util::path::ContextDir;
@ -40,7 +40,7 @@ pub struct RunnerOptionsBuilder {
pre_entry: Option<fn(Entry) -> bool>,
proxy: Option<String>,
resolves: Vec<String>,
retry: Option<usize>,
retry: Retry,
retry_interval: Duration,
ssl_no_revoke: bool,
timeout: Duration,
@ -70,7 +70,7 @@ impl Default for RunnerOptionsBuilder {
pre_entry: None,
proxy: None,
resolves: vec![],
retry: Some(0),
retry: Retry::None,
retry_interval: Duration::from_millis(1000),
ssl_no_revoke: false,
timeout: Duration::from_secs(300),
@ -218,7 +218,7 @@ impl RunnerOptionsBuilder {
/// Sets maximum number of retries.
///
/// Default is 0.
pub fn retry(&mut self, retry: Option<usize>) -> &mut Self {
pub fn retry(&mut self, retry: Retry) -> &mut Self {
self.retry = retry;
self
}
@ -314,7 +314,7 @@ pub struct RunnerOptions {
pub(crate) pre_entry: Option<fn(Entry) -> bool>,
pub(crate) proxy: Option<String>,
pub(crate) resolves: Vec<String>,
pub(crate) retry: Option<usize>,
pub(crate) retry: Retry,
pub(crate) retry_interval: Duration,
pub(crate) ssl_no_revoke: bool,
pub(crate) timeout: Duration,

View File

@ -21,6 +21,7 @@ use hurl::runner::{
};
use hurl::util::logger::LoggerOptionsBuilder;
use hurl::util::path::ContextDir;
use hurl_core::ast::Retry;
use std::collections::HashMap;
use std::time::Duration;
@ -108,7 +109,7 @@ fn simple_sample() {
.post_entry(None)
.pre_entry(None)
.proxy(None)
.retry(Some(0))
.retry(Retry::None)
.retry_interval(Duration::from_secs(1))
.timeout(Duration::from_secs(300))
.to_entry(None)

View File

@ -796,7 +796,7 @@ pub struct RetryOption {
pub space0: Whitespace,
pub space1: Whitespace,
pub space2: Whitespace,
pub value: usize,
pub value: Retry,
pub line_terminator0: LineTerminator,
}
@ -910,3 +910,10 @@ pub enum FilterValue {
UrlDecode,
UrlEncode,
}
#[derive(Clone, Debug, PartialEq, Eq, Copy)]
pub enum Retry {
None,
Finite(usize),
Infinite,
}

View File

@ -238,6 +238,17 @@ impl PredicateFuncValue {
}
}
impl fmt::Display for Retry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match self {
Retry::None => 0,
Retry::Finite(n) => *n as i32,
Retry::Infinite => -1,
};
write!(f, "{}", value)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -316,7 +316,7 @@ impl HtmlFormatter {
self.fmt_space(&option.space1);
self.buffer.push(':');
self.fmt_space(&option.space2);
self.fmt_number(option.value);
self.fmt_retry(option.value);
self.fmt_span_close();
self.fmt_lt(&option.line_terminator0);
}
@ -929,6 +929,14 @@ impl HtmlFormatter {
self.fmt_lt(line_terminator);
}
}
fn fmt_retry(&mut self, retry: Retry) {
match retry {
Retry::Finite(n) => self.fmt_span("number", &n.to_string()),
Retry::Infinite => self.fmt_span("number", "-1"),
_ => {}
};
}
}
fn escape_xml(s: &str) -> String {

View File

@ -537,22 +537,38 @@ fn option_retry(reader: &mut Reader) -> ParseResult<'static, EntryOption> {
let space1 = zero_or_more_spaces(reader)?;
try_literal(":", reader)?;
let space2 = zero_or_more_spaces(reader)?;
let value = nonrecover(natural, reader)?;
let value = retry(reader)?;
let line_terminator0 = line_terminator(reader)?;
// FIXME: try to not unwrap redirect value
// and returns an error if not possible
let option = RetryOption {
line_terminators,
space0,
space1,
space2,
value: usize::try_from(value).unwrap(),
value,
line_terminator0,
};
Ok(EntryOption::Retry(option))
}
fn retry(reader: &mut Reader) -> ParseResult<Retry> {
let pos = reader.state.pos.clone();
let value = nonrecover(integer, reader)?;
if value == -1 {
Ok(Retry::Infinite)
} else if value == 0 {
Ok(Retry::None)
} else if value > 0 {
Ok(Retry::Finite(value as usize))
} else {
Err(Error {
pos,
recoverable: false,
inner: ParseError::Expecting {
value: "Expecting a retry value".to_string(),
},
})
}
}
fn option_retry_interval(reader: &mut Reader) -> ParseResult<'static, EntryOption> {
let line_terminators = optional_line_terminators(reader)?;
let space0 = zero_or_more_spaces(reader)?;

View File

@ -1024,16 +1024,28 @@ impl Tokenizable for RetryOption {
.collect(),
);
tokens.append(&mut self.space0.tokenize());
tokens.push(Token::String("retry-max-count".to_string()));
tokens.append(&mut self.space1.tokenize());
tokens.push(Token::Colon(String::from(":")));
tokens.append(&mut self.space2.tokenize());
tokens.push(Token::Number(self.value.to_string()));
if !matches!(self.value, Retry::None) {
tokens.push(Token::String("retry".to_string()));
tokens.append(&mut self.space1.tokenize());
tokens.push(Token::Colon(String::from(":")));
tokens.append(&mut self.space2.tokenize());
tokens.append(&mut self.value.tokenize());
}
tokens.append(&mut self.line_terminator0.tokenize());
tokens
}
}
impl Tokenizable for Retry {
fn tokenize(&self) -> Vec<Token> {
match self {
Retry::None => vec![Token::Number("0".to_string())],
Retry::Finite(n) => vec![Token::Number(n.to_string())],
Retry::Infinite => vec![Token::Number("-1".to_string())],
}
}
}
impl Tokenizable for RetryIntervalOption {
fn tokenize(&self) -> Vec<Token> {
let mut tokens: Vec<Token> = vec![];