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 atty::Stream;
use clap::ArgMatches; use clap::ArgMatches;
use hurl::runner::Value; use hurl::runner::Value;
use hurl_core::ast::Retry;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
use std::fs::File; use std::fs::File;
@ -243,10 +244,11 @@ pub fn resolves(arg_matches: &ArgMatches) -> Vec<String> {
get_strings(arg_matches, "resolve").unwrap_or_default() 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() { match get::<i32>(arg_matches, "retry").unwrap() {
r if r == -1 => None, r if r == -1 => Retry::Infinite,
r => Some(r as usize), 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::libcurl_version_info;
use hurl::util::logger::{LoggerOptions, LoggerOptionsBuilder, Verbosity}; use hurl::util::logger::{LoggerOptions, LoggerOptionsBuilder, Verbosity};
use hurl::util::path::ContextDir; use hurl::util::path::ContextDir;
use hurl_core::ast::Entry; use hurl_core::ast::{Entry, Retry};
use crate::cli; use crate::cli;
use crate::runner::{RunnerOptions, RunnerOptionsBuilder, Value}; use crate::runner::{RunnerOptions, RunnerOptionsBuilder, Value};
@ -63,7 +63,7 @@ pub struct Options {
pub progress_bar: bool, pub progress_bar: bool,
pub proxy: Option<String>, pub proxy: Option<String>,
pub resolves: Vec<String>, pub resolves: Vec<String>,
pub retry: Option<usize>, pub retry: Retry,
pub retry_interval: Duration, pub retry_interval: Duration,
pub ssl_no_revoke: bool, pub ssl_no_revoke: bool,
pub test: bool, pub test: bool,

View File

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

View File

@ -157,25 +157,26 @@ pub fn run(
Ok(options) => (options.retry, options.retry_interval), Ok(options) => (options.retry, options.retry_interval),
Err(_) => (runner_options.retry, runner_options.retry_interval), Err(_) => (runner_options.retry, runner_options.retry_interval),
}; };
let retry_max_reached = match retry_opts { // The retry threshold can only reached with a finite positive number of retries
Some(r) => retry_count > r, let retry_max_reached = if let Retry::Finite(r) = retry_opts {
None => false, 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 // 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. // 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 // 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. // we first log the error and a potential warning about retrying.
logger.test_erase_line(); 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_important("Retry max count reached, no more retry");
logger.debug(""); logger.debug("");
} }
let retry = !matches!(retry_opts, Retry::None) && !retry_max_reached && has_error;
if has_error { if has_error {
log_errors(&entry_result, content, retry, &logger); log_errors(&entry_result, content, retry, &logger);
} }
entries.push(entry_result); entries.push(entry_result);
if retry { if retry {
@ -323,10 +324,7 @@ fn log_run_info(
if let Some(proxy) = &runner_options.proxy { if let Some(proxy) = &runner_options.proxy {
logger.debug(format!(" proxy: {proxy}").as_str()); logger.debug(format!(" proxy: {proxy}").as_str());
} }
match runner_options.retry { logger.debug(format!(" retry: {}", runner_options.retry).as_str());
None => logger.debug(" retry: indefinitely".to_string().as_str()),
Some(n) => logger.debug(format!(" retry: {n}").as_str()),
}
if !variables.is_empty() { if !variables.is_empty() {
logger.debug_important("Variables:"); logger.debug_important("Variables:");
for (name, value) in variables.iter() { for (name, value) in variables.iter() {

View File

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

View File

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

View File

@ -796,7 +796,7 @@ pub struct RetryOption {
pub space0: Whitespace, pub space0: Whitespace,
pub space1: Whitespace, pub space1: Whitespace,
pub space2: Whitespace, pub space2: Whitespace,
pub value: usize, pub value: Retry,
pub line_terminator0: LineTerminator, pub line_terminator0: LineTerminator,
} }
@ -910,3 +910,10 @@ pub enum FilterValue {
UrlDecode, UrlDecode,
UrlEncode, 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -316,7 +316,7 @@ impl HtmlFormatter {
self.fmt_space(&option.space1); self.fmt_space(&option.space1);
self.buffer.push(':'); self.buffer.push(':');
self.fmt_space(&option.space2); self.fmt_space(&option.space2);
self.fmt_number(option.value); self.fmt_retry(option.value);
self.fmt_span_close(); self.fmt_span_close();
self.fmt_lt(&option.line_terminator0); self.fmt_lt(&option.line_terminator0);
} }
@ -929,6 +929,14 @@ impl HtmlFormatter {
self.fmt_lt(line_terminator); 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 { 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)?; let space1 = zero_or_more_spaces(reader)?;
try_literal(":", reader)?; try_literal(":", reader)?;
let space2 = zero_or_more_spaces(reader)?; let space2 = zero_or_more_spaces(reader)?;
let value = nonrecover(natural, reader)?; let value = retry(reader)?;
let line_terminator0 = line_terminator(reader)?; let line_terminator0 = line_terminator(reader)?;
// FIXME: try to not unwrap redirect value
// and returns an error if not possible
let option = RetryOption { let option = RetryOption {
line_terminators, line_terminators,
space0, space0,
space1, space1,
space2, space2,
value: usize::try_from(value).unwrap(), value,
line_terminator0, line_terminator0,
}; };
Ok(EntryOption::Retry(option)) 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> { fn option_retry_interval(reader: &mut Reader) -> ParseResult<'static, EntryOption> {
let line_terminators = optional_line_terminators(reader)?; let line_terminators = optional_line_terminators(reader)?;
let space0 = zero_or_more_spaces(reader)?; let space0 = zero_or_more_spaces(reader)?;

View File

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