Unify Retry and Repeat types with Count.

This commit is contained in:
Jean-Christophe Amiel 2024-07-06 21:34:10 +02:00
parent 25d6716145
commit 1cdc491867
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
16 changed files with 118 additions and 272 deletions

View File

@ -31,7 +31,7 @@ error: Invalid variable type
| GET http://localhost:8000/unused | GET http://localhost:8000/unused
| ... | ...
29 | repeat: {{count}} 29 | repeat: {{count}}
| ^^^^^ expecting integer, actual value is integer <-2> | ^^^^^ expecting integer >= -1, actual value is integer <-2>
| |
error: Invalid variable type error: Invalid variable type
@ -58,6 +58,6 @@ error: Invalid variable type
| GET http://localhost:8000/unused | GET http://localhost:8000/unused
| ... | ...
50 | retry: {{count}} 50 | retry: {{count}}
| ^^^^^ expecting integer, actual value is foo | ^^^^^ expecting integer, actual value is string <foo>
| |

View File

@ -24,7 +24,7 @@ use std::{env, fs, io};
use clap::ArgMatches; use clap::ArgMatches;
use hurl::runner::{Input, Value}; use hurl::runner::{Input, Value};
use hurl_core::typing::{Repeat, Retry}; use hurl_core::typing::Count;
use super::variables::{parse as parse_variable, parse_value}; use super::variables::{parse as parse_variable, parse_value};
use super::CliOptionsError; use super::CliOptionsError;
@ -366,10 +366,10 @@ pub fn proxy(arg_matches: &ArgMatches) -> Option<String> {
get::<String>(arg_matches, "proxy") get::<String>(arg_matches, "proxy")
} }
pub fn repeat(arg_matches: &ArgMatches) -> Option<Repeat> { pub fn repeat(arg_matches: &ArgMatches) -> Option<Count> {
match get::<i32>(arg_matches, "repeat") { match get::<i32>(arg_matches, "repeat") {
Some(-1) => Some(Repeat::Forever), Some(-1) => Some(Count::Infinite),
Some(n) => Some(Repeat::Count(n as usize)), Some(n) => Some(Count::Finite(n as usize)),
None => None, None => None,
} }
} }
@ -378,10 +378,10 @@ 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<Retry> { pub fn retry(arg_matches: &ArgMatches) -> Option<Count> {
match get::<i32>(arg_matches, "retry") { match get::<i32>(arg_matches, "retry") {
Some(-1) => Some(Retry::Infinite), Some(-1) => Some(Count::Infinite),
Some(r) => Some(Retry::Finite(r as usize)), Some(r) => Some(Count::Finite(r as usize)),
None => None, None => None,
} }
} }

View File

@ -36,7 +36,7 @@ use hurl_core::ast::Entry;
use crate::cli; use crate::cli;
use crate::runner::{RunnerOptions, RunnerOptionsBuilder, Value}; use crate::runner::{RunnerOptions, RunnerOptionsBuilder, Value};
pub use error::CliOptionsError; pub use error::CliOptionsError;
use hurl_core::typing::{Repeat, Retry}; use hurl_core::typing::Count;
/// Represents the list of all options that can be used in Hurl command line. /// Represents the list of all options that can be used in Hurl command line.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -81,9 +81,9 @@ pub struct CliOptions {
pub path_as_is: bool, pub path_as_is: bool,
pub progress_bar: bool, pub progress_bar: bool,
pub proxy: Option<String>, pub proxy: Option<String>,
pub repeat: Option<Repeat>, pub repeat: Option<Count>,
pub resolves: Vec<String>, pub resolves: Vec<String>,
pub retry: Option<Retry>, pub retry: Option<Count>,
pub retry_interval: Duration, pub retry_interval: Duration,
pub ssl_no_revoke: bool, pub ssl_no_revoke: bool,
pub tap_file: Option<PathBuf>, pub tap_file: Option<PathBuf>,

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
* *
*/ */
use hurl_core::typing::Repeat; use hurl_core::typing::Count;
use std::collections::HashMap; use std::collections::HashMap;
use crate::runner::{HurlResult, Input, RunnerOptions, Value}; use crate::runner::{HurlResult, Input, RunnerOptions, Value};
@ -87,14 +87,14 @@ pub struct JobQueue<'job> {
/// Current index of the job, referencing the input job list. /// Current index of the job, referencing the input job list.
index: usize, index: usize,
/// Repeat mode of this queue (finite or infinite). /// Repeat mode of this queue (finite or infinite).
repeat: Repeat, repeat: Count,
/// Current index of the repeat. /// Current index of the repeat.
repeat_index: usize, repeat_index: usize,
} }
impl<'job> JobQueue<'job> { impl<'job> JobQueue<'job> {
/// Create a new queue, with a list of `jobs` and a `repeat` mode. /// Create a new queue, with a list of `jobs` and a `repeat` mode.
pub fn new(jobs: &'job [Job], repeat: Repeat) -> Self { pub fn new(jobs: &'job [Job], repeat: Count) -> Self {
JobQueue { JobQueue {
jobs, jobs,
index: 0, index: 0,
@ -108,8 +108,8 @@ impl<'job> JobQueue<'job> {
/// If queue is created in loop forever mode ([`Repeat::Forever`]), returns `None`. /// If queue is created in loop forever mode ([`Repeat::Forever`]), returns `None`.
pub fn jobs_count(&self) -> Option<usize> { pub fn jobs_count(&self) -> Option<usize> {
match self.repeat { match self.repeat {
Repeat::Count(n) => Some(self.jobs.len() * n), Count::Finite(n) => Some(self.jobs.len() * n),
Repeat::Forever => None, Count::Infinite => None,
} }
} }
@ -130,7 +130,7 @@ impl Iterator for JobQueue<'_> {
if self.index >= self.jobs.len() { if self.index >= self.jobs.len() {
self.repeat_index = self.repeat_index.checked_add(1).unwrap_or(0); self.repeat_index = self.repeat_index.checked_add(1).unwrap_or(0);
match self.repeat { match self.repeat {
Repeat::Count(n) => { Count::Finite(n) => {
if self.repeat_index >= n { if self.repeat_index >= n {
None None
} else { } else {
@ -138,7 +138,7 @@ impl Iterator for JobQueue<'_> {
Some(self.job_at(0)) Some(self.job_at(0))
} }
} }
Repeat::Forever => { Count::Infinite => {
self.index = 1; self.index = 1;
Some(self.job_at(0)) Some(self.job_at(0))
} }
@ -155,7 +155,7 @@ mod tests {
use crate::parallel::job::{Job, JobQueue}; use crate::parallel::job::{Job, JobQueue};
use crate::runner::{Input, RunnerOptionsBuilder}; use crate::runner::{Input, RunnerOptionsBuilder};
use crate::util::logger::LoggerOptionsBuilder; use crate::util::logger::LoggerOptionsBuilder;
use hurl_core::typing::Repeat; use hurl_core::typing::Count;
use std::collections::HashMap; use std::collections::HashMap;
fn new_job(file: &str, index: usize) -> Job { fn new_job(file: &str, index: usize) -> Job {
@ -179,7 +179,7 @@ mod tests {
new_job("c.hurl", 2), new_job("c.hurl", 2),
]; ];
let mut queue = JobQueue::new(&jobs, Repeat::Count(2)); let mut queue = JobQueue::new(&jobs, Count::Finite(2));
assert_eq!(queue.next(), Some(new_job("a.hurl", 0))); assert_eq!(queue.next(), Some(new_job("a.hurl", 0)));
assert_eq!(queue.next(), Some(new_job("b.hurl", 1))); assert_eq!(queue.next(), Some(new_job("b.hurl", 1)));
@ -196,7 +196,7 @@ mod tests {
fn input_queue_is_infinite() { fn input_queue_is_infinite() {
let jobs = [new_job("foo.hurl", 0)]; let jobs = [new_job("foo.hurl", 0)];
let mut queue = JobQueue::new(&jobs, Repeat::Forever); let mut queue = JobQueue::new(&jobs, Count::Infinite);
assert_eq!(queue.next(), Some(new_job("foo.hurl", 0))); assert_eq!(queue.next(), Some(new_job("foo.hurl", 0)));
assert_eq!(queue.next(), Some(new_job("foo.hurl", 1))); assert_eq!(queue.next(), Some(new_job("foo.hurl", 1)));
assert_eq!(queue.next(), Some(new_job("foo.hurl", 2))); assert_eq!(queue.next(), Some(new_job("foo.hurl", 2)));

View File

@ -16,7 +16,7 @@
* *
*/ */
use hurl_core::error::{DisplaySourceError, OutputFormat}; use hurl_core::error::{DisplaySourceError, OutputFormat};
use hurl_core::typing::Repeat; use hurl_core::typing::Count;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use std::sync::{mpsc, Arc, Mutex}; use std::sync::{mpsc, Arc, Mutex};
@ -53,7 +53,7 @@ pub struct ParallelRunner {
/// Output type for each completed job on standard output. /// Output type for each completed job on standard output.
output_type: OutputType, output_type: OutputType,
/// Repeat mode for the runner: infinite or finite. /// Repeat mode for the runner: infinite or finite.
repeat: Repeat, repeat: Count,
} }
/// Represents a worker's state. /// Represents a worker's state.
@ -106,7 +106,7 @@ impl ParallelRunner {
pub fn new( pub fn new(
workers_count: usize, workers_count: usize,
output_type: OutputType, output_type: OutputType,
repeat: Repeat, repeat: Count,
test: bool, test: bool,
progress_bar: bool, progress_bar: bool,
color: bool, color: bool,

View File

@ -28,7 +28,7 @@ use hurl::runner::{HurlResult, Input, Output};
use hurl::util::term::{Stdout, WriteMode}; use hurl::util::term::{Stdout, WriteMode};
use hurl::{output, parallel, runner}; use hurl::{output, parallel, runner};
use hurl_core::error::{DisplaySourceError, OutputFormat}; use hurl_core::error::{DisplaySourceError, OutputFormat};
use hurl_core::typing::Repeat; use hurl_core::typing::Count;
/// Runs Hurl `files` sequentially, given a current directory and command-line options (see /// Runs Hurl `files` sequentially, given a current directory and command-line options (see
/// [`crate::cli::options::CliOptions`]). This function returns a list of [`HurlRun`] results or /// [`crate::cli::options::CliOptions`]). This function returns a list of [`HurlRun`] results or
@ -40,7 +40,7 @@ pub fn run_seq(
) -> Result<Vec<HurlRun>, CliError> { ) -> Result<Vec<HurlRun>, CliError> {
let mut runs = vec![]; let mut runs = vec![];
let repeat = options.repeat.unwrap_or_default(); let repeat = options.repeat.unwrap_or(Count::Finite(1));
let queue = InputQueue::new(files, repeat); let queue = InputQueue::new(files, repeat);
// When dumped HTTP responses, we truncate existing output file on first save, then append // When dumped HTTP responses, we truncate existing output file on first save, then append
@ -165,8 +165,8 @@ pub fn run_par(
// We're going to use the right numbers of workers. We don't need to use more workers than there // We're going to use the right numbers of workers. We don't need to use more workers than there
// are input files (repeat option act as if we're dealing with a multiplied number of files) // are input files (repeat option act as if we're dealing with a multiplied number of files)
let workers_count = match options.repeat { let workers_count = match options.repeat {
Some(Repeat::Count(n)) => min(files.len() * n, workers_count), Some(Count::Finite(n)) => min(files.len() * n, workers_count),
Some(Repeat::Forever) => workers_count, Some(Count::Infinite) => workers_count,
None => min(files.len(), workers_count), None => min(files.len(), workers_count),
}; };
let variables = &options.variables; let variables = &options.variables;
@ -188,7 +188,7 @@ pub fn run_par(
let mut runner = ParallelRunner::new( let mut runner = ParallelRunner::new(
workers_count, workers_count,
output_type, output_type,
options.repeat.unwrap_or_default(), options.repeat.unwrap_or(Count::Finite(1)),
options.test, options.test,
options.progress_bar, options.progress_bar,
options.color, options.color,
@ -233,14 +233,14 @@ pub struct InputQueue<'input> {
/// Current index of the input, referencing the input list. /// Current index of the input, referencing the input list.
index: usize, index: usize,
/// Repeat mode of this queue (finite or infinite). /// Repeat mode of this queue (finite or infinite).
repeat: Repeat, repeat: Count,
/// Current index of the repeat. /// Current index of the repeat.
repeat_index: usize, repeat_index: usize,
} }
impl<'input> InputQueue<'input> { impl<'input> InputQueue<'input> {
/// Create a new queue, with a list of `inputs` and a `repeat` mode. /// Create a new queue, with a list of `inputs` and a `repeat` mode.
pub fn new(inputs: &'input [Input], repeat: Repeat) -> Self { pub fn new(inputs: &'input [Input], repeat: Count) -> Self {
InputQueue { InputQueue {
inputs, inputs,
index: 0, index: 0,
@ -262,7 +262,7 @@ impl Iterator for InputQueue<'_> {
if self.index >= self.inputs.len() { if self.index >= self.inputs.len() {
self.repeat_index = self.repeat_index.checked_add(1).unwrap_or(0); self.repeat_index = self.repeat_index.checked_add(1).unwrap_or(0);
match self.repeat { match self.repeat {
Repeat::Count(n) => { Count::Finite(n) => {
if self.repeat_index >= n { if self.repeat_index >= n {
None None
} else { } else {
@ -270,7 +270,7 @@ impl Iterator for InputQueue<'_> {
Some(self.input_at(0)) Some(self.input_at(0))
} }
} }
Repeat::Forever => { Count::Infinite => {
self.index = 1; self.index = 1;
Some(self.input_at(0)) Some(self.input_at(0))
} }
@ -286,13 +286,13 @@ impl Iterator for InputQueue<'_> {
mod tests { mod tests {
use crate::run::InputQueue; use crate::run::InputQueue;
use hurl::runner::Input; use hurl::runner::Input;
use hurl_core::typing::Repeat; use hurl_core::typing::Count;
#[test] #[test]
fn input_queue_is_finite() { fn input_queue_is_finite() {
let files = [Input::new("a"), Input::new("b"), Input::new("c")]; let files = [Input::new("a"), Input::new("b"), Input::new("c")];
let mut queue = InputQueue::new(&files, Repeat::Count(4)); let mut queue = InputQueue::new(&files, Count::Finite(4));
assert_eq!(queue.next(), Some(Input::new("a"))); assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("b"))); assert_eq!(queue.next(), Some(Input::new("b")));
assert_eq!(queue.next(), Some(Input::new("c"))); assert_eq!(queue.next(), Some(Input::new("c")));
@ -312,7 +312,7 @@ mod tests {
fn input_queue_is_infinite() { fn input_queue_is_infinite() {
let files = [Input::new("a")]; let files = [Input::new("a")];
let mut queue = InputQueue::new(&files, Repeat::Forever); let mut queue = InputQueue::new(&files, Count::Infinite);
assert_eq!(queue.next(), Some(Input::new("a"))); assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("a"))); assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("a"))); assert_eq!(queue.next(), Some(Input::new("a")));

View File

@ -26,7 +26,7 @@ use hurl_core::ast::{
}; };
use hurl_core::error::DisplaySourceError; use hurl_core::error::DisplaySourceError;
use hurl_core::parser; use hurl_core::parser;
use hurl_core::typing::{Repeat, Retry}; use hurl_core::typing::Count;
use crate::http::{Call, Client}; use crate::http::{Call, Client};
use crate::runner::event::EventListener; use crate::runner::event::EventListener;
@ -217,7 +217,7 @@ pub fn run_entries(
} }
// Repeat 0 is equivalent to skip. // Repeat 0 is equivalent to skip.
if options.repeat == Some(Repeat::Count(0)) { if options.repeat == Some(Count::Finite(0)) {
logger.debug(""); logger.debug("");
logger.debug_important(&format!("Entry {entry_index} is skipped (repeat 0 times)")); logger.debug_important(&format!("Entry {entry_index} is skipped (repeat 0 times)"));
entry_index += 1; entry_index += 1;
@ -268,7 +268,7 @@ pub fn run_entries(
repeat_count = 0; repeat_count = 0;
entry_index += 1; entry_index += 1;
} }
Some(Repeat::Count(n)) => { Some(Count::Finite(n)) => {
if repeat_count >= n { if repeat_count >= n {
repeat_count = 0; repeat_count = 0;
entry_index += 1; entry_index += 1;
@ -278,7 +278,7 @@ pub fn run_entries(
)); ));
} }
} }
Some(Repeat::Forever) => { Some(Count::Infinite) => {
logger.debug_important(&format!("Repeat entry {entry_index} (x{repeat_count})")); logger.debug_important(&format!("Repeat entry {entry_index} (x{repeat_count})"));
} }
} }
@ -319,7 +319,7 @@ fn run_request(
let mut has_error = !result.errors.is_empty(); let mut has_error = !result.errors.is_empty();
// The retry threshold can only be reached with a finite positive number of retries // The retry threshold can only be reached with a finite positive number of retries
let retry_max_reached = if let Some(Retry::Finite(r)) = options.retry { let retry_max_reached = if let Some(Count::Finite(r)) = options.retry {
retry_count > r retry_count > r
} else { } else {
false false

View File

@ -19,10 +19,10 @@ use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use hurl_core::ast::{ use hurl_core::ast::{
BooleanOption, Entry, EntryOption, Float, NaturalOption, Number as AstNumber, OptionKind, BooleanOption, CountOption, Entry, EntryOption, Float, NaturalOption, Number as AstNumber,
RepeatOption, RetryOption, SectionValue, VariableDefinition, VariableValue, OptionKind, SectionValue, VariableDefinition, VariableValue,
}; };
use hurl_core::typing::{Repeat, Retry}; use hurl_core::typing::Count;
use crate::http::{IpResolve, RequestedHttpVersion}; use crate::http::{IpResolve, RequestedHttpVersion};
use crate::runner::template::{eval_expression, eval_template}; use crate::runner::template::{eval_expression, eval_template};
@ -200,7 +200,7 @@ pub fn get_entry_options(
entry_options.proxy = Some(value); entry_options.proxy = Some(value);
} }
OptionKind::Repeat(value) => { OptionKind::Repeat(value) => {
let value = eval_repeat_option(value, variables)?; let value = eval_count_option(value, variables)?;
entry_options.repeat = Some(value); entry_options.repeat = Some(value);
} }
OptionKind::Resolve(value) => { OptionKind::Resolve(value) => {
@ -208,7 +208,7 @@ pub fn get_entry_options(
entry_options.resolves.push(value); entry_options.resolves.push(value);
} }
OptionKind::Retry(value) => { OptionKind::Retry(value) => {
let value = eval_retry_option(value, variables)?; let value = eval_count_option(value, variables)?;
entry_options.retry = Some(value); entry_options.retry = Some(value);
} }
OptionKind::RetryInterval(value) => { OptionKind::RetryInterval(value) => {
@ -320,7 +320,6 @@ fn eval_boolean_option(
} }
} }
/// Evals a natural option value (>=0), given a set of `variables`.
fn eval_natural_option( fn eval_natural_option(
natural_value: &NaturalOption, natural_value: &NaturalOption,
variables: &HashMap<String, Value>, variables: &HashMap<String, Value>,
@ -352,53 +351,18 @@ fn eval_natural_option(
} }
} }
/// Render an AST repeat option with a `variables` set. fn eval_count_option(
fn eval_repeat_option( count_value: &CountOption,
repeat_option_value: &RepeatOption,
variables: &HashMap<String, Value>, variables: &HashMap<String, Value>,
) -> Result<Repeat, RunnerError> { ) -> Result<Count, RunnerError> {
match repeat_option_value { match count_value {
RepeatOption::Literal(repeat) => Ok(*repeat), CountOption::Literal(repeat) => Ok(*repeat),
RepeatOption::Expression(expr) => match eval_expression(expr, variables)? { CountOption::Expression(expr) => match eval_expression(expr, variables)? {
Value::Number(Number::Integer(value)) => { Value::Number(Number::Integer(value)) => {
if value == -1 { if value == -1 {
Ok(Repeat::Forever) Ok(Count::Infinite)
} else if value >= 0 { } else if value >= 0 {
Ok(Repeat::Count(value as usize)) Ok(Count::Finite(value as usize))
} else {
let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(),
value: format!("integer <{value}>"),
expecting: "integer".to_string(),
};
Err(RunnerError::new(expr.variable.source_info, kind, false))
}
}
v => {
let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(),
value: v.format(),
expecting: "integer".to_string(),
};
Err(RunnerError::new(expr.variable.source_info, kind, false))
}
},
}
}
/// Render an AST retry option with a `variables` set.
fn eval_retry_option(
retry_option_value: &RetryOption,
variables: &HashMap<String, Value>,
) -> Result<Retry, RunnerError> {
match retry_option_value {
RetryOption::Literal(retry) => Ok(*retry),
RetryOption::Expression(expr) => match eval_expression(expr, variables)? {
Value::Number(Number::Integer(value)) => {
if value == -1 {
Ok(Retry::Infinite)
} else if value >= 0 {
Ok(Retry::Finite(value as usize))
} else { } else {
let kind = RunnerErrorKind::TemplateVariableInvalidType { let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(), name: expr.variable.name.clone(),
@ -411,7 +375,7 @@ fn eval_retry_option(
v => { v => {
let kind = RunnerErrorKind::TemplateVariableInvalidType { let kind = RunnerErrorKind::TemplateVariableInvalidType {
name: expr.variable.name.clone(), name: expr.variable.name.clone(),
value: v.to_string(), value: v.format(),
expecting: "integer".to_string(), expecting: "integer".to_string(),
}; };
Err(RunnerError::new(expr.variable.source_info, kind, false)) Err(RunnerError::new(expr.variable.source_info, kind, false))

View File

@ -18,7 +18,7 @@
use std::time::Duration; use std::time::Duration;
use hurl_core::ast::Entry; use hurl_core::ast::Entry;
use hurl_core::typing::{Repeat, Retry}; use hurl_core::typing::Count;
use crate::http::{IpResolve, RequestedHttpVersion}; use crate::http::{IpResolve, RequestedHttpVersion};
use crate::runner::Output; use crate::runner::Output;
@ -54,9 +54,9 @@ pub struct RunnerOptionsBuilder {
post_entry: Option<fn() -> bool>, post_entry: Option<fn() -> bool>,
pre_entry: Option<fn(Entry) -> bool>, pre_entry: Option<fn(Entry) -> bool>,
proxy: Option<String>, proxy: Option<String>,
repeat: Option<Repeat>, repeat: Option<Count>,
resolves: Vec<String>, resolves: Vec<String>,
retry: Option<Retry>, retry: Option<Count>,
retry_interval: Duration, retry_interval: Duration,
skip: bool, skip: bool,
ssl_no_revoke: bool, ssl_no_revoke: bool,
@ -322,7 +322,7 @@ impl RunnerOptionsBuilder {
} }
/// Set the number of repetition for a given entry. /// Set the number of repetition for a given entry.
pub fn repeat(&mut self, repeat: Option<Repeat>) -> &mut Self { pub fn repeat(&mut self, repeat: Option<Count>) -> &mut Self {
self.repeat = repeat; self.repeat = repeat;
self self
} }
@ -336,7 +336,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<Retry>) -> &mut Self { pub fn retry(&mut self, retry: Option<Count>) -> &mut Self {
self.retry = retry; self.retry = retry;
self self
} }
@ -470,9 +470,9 @@ pub struct RunnerOptions {
pub(crate) post_entry: Option<fn() -> bool>, pub(crate) post_entry: Option<fn() -> bool>,
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) repeat: Option<Repeat>, pub(crate) repeat: Option<Count>,
pub(crate) resolves: Vec<String>, pub(crate) resolves: Vec<String>,
pub(crate) retry: Option<Retry>, pub(crate) retry: Option<Count>,
pub(crate) retry_interval: Duration, pub(crate) retry_interval: Duration,
pub(crate) skip: bool, pub(crate) skip: bool,
pub(crate) ssl_no_revoke: bool, pub(crate) ssl_no_revoke: bool,

View File

@ -17,7 +17,7 @@
*/ */
use crate::ast::json; use crate::ast::json;
use crate::reader::Pos; use crate::reader::Pos;
use crate::typing::{Repeat, Retry}; use crate::typing::Count;
/// ///
/// Hurl AST /// Hurl AST
@ -726,9 +726,9 @@ pub enum OptionKind {
Output(Template), Output(Template),
PathAsIs(BooleanOption), PathAsIs(BooleanOption),
Proxy(Template), Proxy(Template),
Repeat(RepeatOption), Repeat(CountOption),
Resolve(Template), Resolve(Template),
Retry(RetryOption), Retry(CountOption),
RetryInterval(NaturalOption), RetryInterval(NaturalOption),
Skip(BooleanOption), Skip(BooleanOption),
UnixSocket(Template), UnixSocket(Template),
@ -831,14 +831,8 @@ pub enum NaturalOption {
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum RepeatOption { pub enum CountOption {
Literal(Repeat), Literal(Count),
Expression(Expr),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RetryOption {
Literal(Retry),
Expression(Expr), Expression(Expr),
} }

View File

@ -187,20 +187,11 @@ impl fmt::Display for NaturalOption {
} }
} }
impl fmt::Display for RepeatOption { impl fmt::Display for CountOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
RepeatOption::Literal(v) => write!(f, "{}", v), CountOption::Literal(v) => write!(f, "{}", v),
RepeatOption::Expression(v) => write!(f, "{}", v), CountOption::Expression(v) => write!(f, "{}", v),
}
}
}
impl fmt::Display for RetryOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RetryOption::Literal(v) => write!(f, "{}", v),
RetryOption::Expression(v) => write!(f, "{}", v),
} }
} }
} }

View File

@ -18,7 +18,7 @@
use std::fmt::Display; use std::fmt::Display;
use crate::ast::*; use crate::ast::*;
use crate::typing::{Repeat, Retry}; use crate::typing::Count;
/// Returns an HTML string of the Hurl file `hurl_file`. /// Returns an HTML string of the Hurl file `hurl_file`.
/// ///
@ -238,9 +238,9 @@ impl HtmlFormatter {
OptionKind::Output(filename) => self.fmt_filename(filename), OptionKind::Output(filename) => self.fmt_filename(filename),
OptionKind::PathAsIs(value) => self.fmt_bool_option(value), OptionKind::PathAsIs(value) => self.fmt_bool_option(value),
OptionKind::Proxy(value) => self.fmt_template(value), OptionKind::Proxy(value) => self.fmt_template(value),
OptionKind::Repeat(value) => self.fmt_repeat_option(value), OptionKind::Repeat(value) => self.fmt_count_option(value),
OptionKind::Resolve(value) => self.fmt_template(value), OptionKind::Resolve(value) => self.fmt_template(value),
OptionKind::Retry(value) => self.fmt_retry_option(value), OptionKind::Retry(value) => self.fmt_count_option(value),
OptionKind::RetryInterval(value) => self.fmt_natural_option(value), OptionKind::RetryInterval(value) => self.fmt_natural_option(value),
OptionKind::Skip(value) => self.fmt_bool_option(value), OptionKind::Skip(value) => self.fmt_bool_option(value),
OptionKind::UnixSocket(value) => self.fmt_template(value), OptionKind::UnixSocket(value) => self.fmt_template(value),
@ -253,31 +253,17 @@ impl HtmlFormatter {
self.fmt_lt(&option.line_terminator0); self.fmt_lt(&option.line_terminator0);
} }
fn fmt_repeat_option(&mut self, repeat_option: &RepeatOption) { fn fmt_count_option(&mut self, count_option: &CountOption) {
match repeat_option { match count_option {
RepeatOption::Literal(repeat) => self.fmt_repeat(repeat), CountOption::Literal(repeat) => self.fmt_count(*repeat),
RepeatOption::Expression(expr) => self.fmt_expr(expr), CountOption::Expression(expr) => self.fmt_expr(expr),
} }
} }
fn fmt_repeat(&mut self, repeat: &Repeat) { fn fmt_count(&mut self, count: Count) {
match repeat { match count {
Repeat::Count(n) => self.fmt_number(n), Count::Finite(n) => self.fmt_number(n),
Repeat::Forever => self.fmt_number(-1), Count::Infinite => self.fmt_number(-1),
};
}
fn fmt_retry_option(&mut self, retry_option: &RetryOption) {
match retry_option {
RetryOption::Literal(retry) => self.fmt_retry(retry),
RetryOption::Expression(expr) => self.fmt_expr(expr),
};
}
fn fmt_retry(&mut self, retry: &Retry) {
match retry {
Retry::Finite(n) => self.fmt_number(n),
Retry::Infinite => self.fmt_number(-1),
}; };
} }

View File

@ -23,7 +23,7 @@ use crate::parser::primitives::*;
use crate::parser::string::*; use crate::parser::string::*;
use crate::parser::{expr, filename, filename_password, ParseResult}; use crate::parser::{expr, filename, filename_password, ParseResult};
use crate::reader::Reader; use crate::reader::Reader;
use crate::typing::{Repeat, Retry}; use crate::typing::Count;
/// Parse an option in an `[Options]` section. /// Parse an option in an `[Options]` section.
pub fn parse(reader: &mut Reader) -> ParseResult<EntryOption> { pub fn parse(reader: &mut Reader) -> ParseResult<EntryOption> {
@ -208,7 +208,7 @@ fn option_proxy(reader: &mut Reader) -> ParseResult<OptionKind> {
} }
fn option_repeat(reader: &mut Reader) -> ParseResult<OptionKind> { fn option_repeat(reader: &mut Reader) -> ParseResult<OptionKind> {
let value = repeat_option(reader)?; let value = non_recover(count_option, reader)?;
Ok(OptionKind::Repeat(value)) Ok(OptionKind::Repeat(value))
} }
@ -218,7 +218,7 @@ fn option_resolve(reader: &mut Reader) -> ParseResult<OptionKind> {
} }
fn option_retry(reader: &mut Reader) -> ParseResult<OptionKind> { fn option_retry(reader: &mut Reader) -> ParseResult<OptionKind> {
let value = retry_option(reader)?; let value = non_recover(count_option, reader)?;
Ok(OptionKind::Retry(value)) Ok(OptionKind::Retry(value))
} }
@ -257,31 +257,16 @@ fn option_very_verbose(reader: &mut Reader) -> ParseResult<OptionKind> {
Ok(OptionKind::VeryVerbose(value)) Ok(OptionKind::VeryVerbose(value))
} }
fn repeat(reader: &mut Reader) -> ParseResult<Repeat> { fn count(reader: &mut Reader) -> ParseResult<Count> {
let start = reader.cursor(); let start = reader.cursor();
let value = non_recover(integer, reader)?; let value = non_recover(integer, reader)?;
if value == -1 { if value == -1 {
Ok(Repeat::Forever) Ok(Count::Infinite)
} else if value >= 0 { } else if value >= 0 {
Ok(Repeat::Count(value as usize)) Ok(Count::Finite(value as usize))
} else { } else {
let kind = ParseErrorKind::Expecting { let kind = ParseErrorKind::Expecting {
value: "Expecting a repeat value".to_string(), value: "Expecting a count value".to_string(),
};
Err(ParseError::new(start.pos, false, kind))
}
}
fn retry(reader: &mut Reader) -> ParseResult<Retry> {
let start = reader.cursor();
let value = non_recover(integer, reader)?;
if value == -1 {
Ok(Retry::Infinite)
} else if value >= 0 {
Ok(Retry::Finite(value as usize))
} else {
let kind = ParseErrorKind::Expecting {
value: "Expecting a retry value".to_string(),
}; };
Err(ParseError::new(start.pos, false, kind)) Err(ParseError::new(start.pos, false, kind))
} }
@ -321,27 +306,10 @@ fn natural_option(reader: &mut Reader) -> ParseResult<NaturalOption> {
} }
} }
fn repeat_option(reader: &mut Reader) -> ParseResult<RepeatOption> { fn count_option(reader: &mut Reader) -> ParseResult<CountOption> {
let start = reader.cursor(); let start = reader.cursor();
match repeat(reader) { match count(reader) {
Ok(v) => Ok(RepeatOption::Literal(v)), Ok(v) => Ok(CountOption::Literal(v)),
Err(_) => {
reader.seek(start);
let exp = expr::parse(reader).map_err(|e| {
let kind = ParseErrorKind::Expecting {
value: "integer".to_string(),
};
ParseError::new(e.pos, false, kind)
})?;
Ok(RepeatOption::Expression(exp))
}
}
}
fn retry_option(reader: &mut Reader) -> ParseResult<RetryOption> {
let start = reader.cursor();
match retry(reader) {
Ok(v) => Ok(RetryOption::Literal(v)),
Err(_) => { Err(_) => {
reader.seek(start); reader.seek(start);
let exp = expr::parse(reader).map_err(|e| { let exp = expr::parse(reader).map_err(|e| {
@ -350,7 +318,7 @@ fn retry_option(reader: &mut Reader) -> ParseResult<RetryOption> {
}; };
ParseError::new(e.pos, false, kind) ParseError::new(e.pos, false, kind)
})?; })?;
Ok(RetryOption::Expression(exp)) Ok(CountOption::Expression(exp))
} }
} }
} }

View File

@ -18,43 +18,22 @@
//! Hurl common types. //! Hurl common types.
use core::fmt; use core::fmt;
/// Represents a repeat operation, either finite or infinite. /// Represents a count operation, either finite or infinite.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Repeat { pub enum Count {
Count(usize),
Forever,
}
impl Default for Repeat {
fn default() -> Self {
Repeat::Count(1)
}
}
impl fmt::Display for Repeat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match self {
Repeat::Count(n) => *n as i64,
Repeat::Forever => -1,
};
write!(f, "{}", value)
}
}
/// Represents a retry operation (when an operation has failed), either finite or infinite.
/// Contrary to [`Repeat`], [`Retry`] has a notion of retry only on operation failure, while
/// [`Repeat`] is unconditional.
#[derive(Clone, Debug, PartialEq, Eq, Copy)]
pub enum Retry {
Finite(usize), Finite(usize),
Infinite, Infinite,
} }
impl fmt::Display for Retry { impl fmt::Display for Count {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = match self { match self {
Retry::Finite(n) => *n as i64, Count::Finite(n) => {
Retry::Infinite => -1, write!(f, "{n}")
}; }
write!(f, "{}", value) Count::Infinite => {
write!(f, "-1")
}
}
} }
} }

View File

@ -18,7 +18,7 @@
use base64::engine::general_purpose; use base64::engine::general_purpose;
use base64::Engine; use base64::Engine;
use hurl_core::ast::*; use hurl_core::ast::*;
use hurl_core::typing::{Repeat, Retry}; use hurl_core::typing::Count;
use super::serialize_json::*; use super::serialize_json::*;
@ -353,38 +353,20 @@ impl ToJson for NaturalOption {
} }
} }
impl ToJson for RepeatOption { impl ToJson for CountOption {
fn to_json(&self) -> JValue { fn to_json(&self) -> JValue {
match self { match self {
RepeatOption::Literal(value) => value.to_json(), CountOption::Literal(value) => value.to_json(),
RepeatOption::Expression(expr) => expr.to_json(), CountOption::Expression(expr) => expr.to_json(),
} }
} }
} }
impl ToJson for Repeat { impl ToJson for Count {
fn to_json(&self) -> JValue { fn to_json(&self) -> JValue {
match self { match self {
Repeat::Count(n) => JValue::Number(n.to_string()), Count::Finite(n) => JValue::Number(n.to_string()),
Repeat::Forever => JValue::Number("-1".to_string()), Count::Infinite => JValue::Number("-1".to_string()),
}
}
}
impl ToJson for RetryOption {
fn to_json(&self) -> JValue {
match self {
RetryOption::Literal(value) => value.to_json(),
RetryOption::Expression(expr) => expr.to_json(),
}
}
}
impl ToJson for Retry {
fn to_json(&self) -> JValue {
match self {
Retry::Finite(n) => JValue::Number(n.to_string()),
Retry::Infinite => JValue::Number("-1".to_string()),
} }
} }
} }

View File

@ -16,7 +16,7 @@
* *
*/ */
use hurl_core::ast::*; use hurl_core::ast::*;
use hurl_core::typing::{Repeat, Retry}; use hurl_core::typing::Count;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Token { pub enum Token {
@ -935,38 +935,20 @@ impl Tokenizable for NaturalOption {
} }
} }
impl Tokenizable for RepeatOption { impl Tokenizable for CountOption {
fn tokenize(&self) -> Vec<Token> { fn tokenize(&self) -> Vec<Token> {
match self { match self {
RepeatOption::Literal(retry) => retry.tokenize(), CountOption::Literal(retry) => retry.tokenize(),
RepeatOption::Expression(expr) => expr.tokenize(), CountOption::Expression(expr) => expr.tokenize(),
} }
} }
} }
impl Tokenizable for Repeat { impl Tokenizable for Count {
fn tokenize(&self) -> Vec<Token> { fn tokenize(&self) -> Vec<Token> {
match self { match self {
Repeat::Count(n) => vec![Token::Number(n.to_string())], Count::Finite(n) => vec![Token::Number(n.to_string())],
Repeat::Forever => vec![Token::Number("-1".to_string())], Count::Infinite => vec![Token::Number("-1".to_string())],
}
}
}
impl Tokenizable for RetryOption {
fn tokenize(&self) -> Vec<Token> {
match self {
RetryOption::Literal(retry) => retry.tokenize(),
RetryOption::Expression(expr) => expr.tokenize(),
}
}
}
impl Tokenizable for Retry {
fn tokenize(&self) -> Vec<Token> {
match self {
Retry::Finite(n) => vec![Token::Number(n.to_string())],
Retry::Infinite => vec![Token::Number("-1".to_string())],
} }
} }
} }