mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-11-30 00:37:52 +03:00
Add repeat option per request
This commit is contained in:
parent
dc6be76339
commit
c2f4514a24
33
integration/hurl/tests_ok/repeat_option.hurl
Executable file
33
integration/hurl/tests_ok/repeat_option.hurl
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
GET http://localhost:8000/repeat/hello?name=A
|
||||||
|
[Options]
|
||||||
|
repeat: 4
|
||||||
|
output: -
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
|
||||||
|
GET http://localhost:8000/repeat/hello?name=B
|
||||||
|
[Options]
|
||||||
|
repeat: 0 # repeat 0 is equivalent to skip
|
||||||
|
output: -
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
|
||||||
|
GET http://localhost:8000/repeat/hello?name=C
|
||||||
|
[Options]
|
||||||
|
repeat: 3
|
||||||
|
output: -
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
|
||||||
|
GET http://localhost:8000/repeat/hello?name=D
|
||||||
|
[Options]
|
||||||
|
repeat: 1
|
||||||
|
output: -
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
|
||||||
|
GET http://localhost:8000/repeat/hello?name=E
|
||||||
|
[Options]
|
||||||
|
repeat: 2
|
||||||
|
output: -
|
||||||
|
HTTP 200
|
10
integration/hurl/tests_ok/repeat_option.out
Normal file
10
integration/hurl/tests_ok/repeat_option.out
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Hello A!
|
||||||
|
Hello A!
|
||||||
|
Hello A!
|
||||||
|
Hello A!
|
||||||
|
Hello C!
|
||||||
|
Hello C!
|
||||||
|
Hello C!
|
||||||
|
Hello D!
|
||||||
|
Hello E!
|
||||||
|
Hello E!
|
6
integration/hurl/tests_ok/repeat_option.ps1
Normal file
6
integration/hurl/tests_ok/repeat_option.ps1
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Set-StrictMode -Version latest
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# We're deactivating output here because we explicitly enable output per request
|
||||||
|
# to control the number of repetition for each request.
|
||||||
|
hurl --no-output tests_ok/repeat_option.hurl
|
6
integration/hurl/tests_ok/repeat_option.sh
Executable file
6
integration/hurl/tests_ok/repeat_option.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# We're deactivating output here because we explicitly enable output per request
|
||||||
|
# to control the number of repetition for each request.
|
||||||
|
hurl --no-output tests_ok/repeat_option.hurl
|
@ -24,6 +24,8 @@
|
|||||||
<span class="line"><span class="string">output</span>: <span class="filename">output.txt</span></span>
|
<span class="line"><span class="string">output</span>: <span class="filename">output.txt</span></span>
|
||||||
<span class="line"><span class="string">path-as-is</span>: <span class="boolean">false</span></span>
|
<span class="line"><span class="string">path-as-is</span>: <span class="boolean">false</span></span>
|
||||||
<span class="line"><span class="string">proxy</span>: <span class="string">http://proxy.example</span></span>
|
<span class="line"><span class="string">proxy</span>: <span class="string">http://proxy.example</span></span>
|
||||||
|
<span class="line"><span class="string">repeat</span>: <span class="number">-1</span></span>
|
||||||
|
<span class="line"><span class="string">repeat</span>: <span class="number">5</span></span>
|
||||||
<span class="line"><span class="string">resolve</span>: <span class="string">example.com:443:127.0.0.1</span></span>
|
<span class="line"><span class="string">resolve</span>: <span class="string">example.com:443:127.0.0.1</span></span>
|
||||||
<span class="line"><span class="string">retry</span>: <span class="number">0</span></span>
|
<span class="line"><span class="string">retry</span>: <span class="number">0</span></span>
|
||||||
<span class="line"><span class="string">retry</span>: <span class="number">-1</span></span>
|
<span class="line"><span class="string">retry</span>: <span class="number">-1</span></span>
|
||||||
@ -65,6 +67,7 @@
|
|||||||
<span class="line"><span class="string">output</span>: <span class="filename">{{output}}</span></span>
|
<span class="line"><span class="string">output</span>: <span class="filename">{{output}}</span></span>
|
||||||
<span class="line"><span class="string">path-as-is</span>: <span class="expr">{{path-as-is}}</span></span>
|
<span class="line"><span class="string">path-as-is</span>: <span class="expr">{{path-as-is}}</span></span>
|
||||||
<span class="line"><span class="string">proxy</span>: <span class="string">{{proxy}}</span></span>
|
<span class="line"><span class="string">proxy</span>: <span class="string">{{proxy}}</span></span>
|
||||||
|
<span class="line"><span class="string">repeat</span>: <span class="expr">{{repeat}}</span></span>
|
||||||
<span class="line"><span class="string">resolve</span>: <span class="string">{{resolve}}</span></span>
|
<span class="line"><span class="string">resolve</span>: <span class="string">{{resolve}}</span></span>
|
||||||
<span class="line"><span class="string">retry</span>: <span class="expr">{{retry}}</span></span>
|
<span class="line"><span class="string">retry</span>: <span class="expr">{{retry}}</span></span>
|
||||||
<span class="line"><span class="string">retry-interval</span>: <span class="expr">{{retry-interval}}</span></span>
|
<span class="line"><span class="string">retry-interval</span>: <span class="expr">{{retry-interval}}</span></span>
|
||||||
|
@ -24,6 +24,8 @@ netrc-optional: false
|
|||||||
output: output.txt
|
output: output.txt
|
||||||
path-as-is: false
|
path-as-is: false
|
||||||
proxy: http://proxy.example
|
proxy: http://proxy.example
|
||||||
|
repeat: -1
|
||||||
|
repeat: 5
|
||||||
resolve: example.com:443:127.0.0.1
|
resolve: example.com:443:127.0.0.1
|
||||||
retry: 0
|
retry: 0
|
||||||
retry: -1
|
retry: -1
|
||||||
@ -65,6 +67,7 @@ netrc-optional: {{netrc-optional}}
|
|||||||
output: {{output}}
|
output: {{output}}
|
||||||
path-as-is: {{path-as-is}}
|
path-as-is: {{path-as-is}}
|
||||||
proxy: {{proxy}}
|
proxy: {{proxy}}
|
||||||
|
repeat: {{repeat}}
|
||||||
resolve: {{resolve}}
|
resolve: {{resolve}}
|
||||||
retry: {{retry}}
|
retry: {{retry}}
|
||||||
retry-interval: {{retry-interval}}
|
retry-interval: {{retry-interval}}
|
||||||
|
@ -1 +1 @@
|
|||||||
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/hello","options":[{"name":"aws-sigv4","value":"aws:amz:eu-central-1:sts"},{"name":"cacert","value":"cacertfile"},{"name":"cert","value":"certfile"},{"name":"cert","value":"certfile:qU114@q,[\"NO"},{"name":"key","value":"keyfile"},{"name":"compressed","value":false},{"name":"connect-to","value":"example.com:443:example.net:8443"},{"name":"delay","value":1000},{"name":"location","value":false},{"name":"location-trusted","value":false},{"name":"http1.0","value":false},{"name":"http1.1","value":false},{"name":"http2","value":false},{"name":"http3","value":false},{"name":"insecure","value":false},{"name":"ipv4","value":false},{"name":"ipv6","value":false},{"name":"max-redirs","value":10},{"name":"netrc","value":false},{"name":"netrc-file","value":"netrcfile"},{"name":"netrc-optional","value":false},{"name":"output","value":"output.txt"},{"name":"path-as-is","value":false},{"name":"proxy","value":"http://proxy.example"},{"name":"resolve","value":"example.com:443:127.0.0.1"},{"name":"retry","value":0},{"name":"retry","value":-1},{"name":"retry","value":4},{"name":"retry-interval","value":1000},{"name":"skip","value":false},{"name":"unix-socket","value":"build/unix_socket.sock"},{"name":"user","value":"bob:secret"},{"name":"variable","value":"user=null"},{"name":"variable","value":"status=true"},{"name":"variable","value":"count=2"},{"name":"variable","value":"score=7.7"},{"name":"variable","value":"name=Bob"},{"name":"verbose","value":false},{"name":"very-verbose","value":false}]}},{"request":{"method":"GET","url":"http://localhost:8000/hello","options":[{"name":"aws-sigv4","value":"{{aws-sigv4}}"},{"name":"cacert","value":"{{cacert}}"},{"name":"cert","value":"{{cert}}"},{"name":"key","value":"{{key}}"},{"name":"compressed","value":"{{compressed}}"},{"name":"connect-to","value":"{{connect-to}}"},{"name":"delay","value":"{{delay}}"},{"name":"location","value":"{{location}}"},{"name":"location-trusted","value":"{{location-trusted}}"},{"name":"http1.0","value":"{{http10}}"},{"name":"http1.1","value":"{{http11}}"},{"name":"http2","value":"{{http2}}"},{"name":"http3","value":"{{http3}}"},{"name":"insecure","value":"{{insecure}}"},{"name":"ipv4","value":"{{ipv4}}"},{"name":"ipv6","value":"{{ipv6}}"},{"name":"max-redirs","value":"{{max-redirs}}"},{"name":"netrc","value":"{{netrc}}"},{"name":"netrc-file","value":"{{netrc-file}}"},{"name":"netrc-optional","value":"{{netrc-optional}}"},{"name":"output","value":"{{output}}"},{"name":"path-as-is","value":"{{path-as-is}}"},{"name":"proxy","value":"{{proxy}}"},{"name":"resolve","value":"{{resolve}}"},{"name":"retry","value":"{{retry}}"},{"name":"retry-interval","value":"{{retry-interval}}"},{"name":"skip","value":"{{skip}}"},{"name":"unix-socket","value":"{{socket-file}}"},{"name":"user","value":"{{user}}"},{"name":"verbose","value":"{{verbose}}"},{"name":"very-verbose","value":"{{very-verbose}}"}]}}]}
|
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/hello","options":[{"name":"aws-sigv4","value":"aws:amz:eu-central-1:sts"},{"name":"cacert","value":"cacertfile"},{"name":"cert","value":"certfile"},{"name":"cert","value":"certfile:qU114@q,[\"NO"},{"name":"key","value":"keyfile"},{"name":"compressed","value":false},{"name":"connect-to","value":"example.com:443:example.net:8443"},{"name":"delay","value":1000},{"name":"location","value":false},{"name":"location-trusted","value":false},{"name":"http1.0","value":false},{"name":"http1.1","value":false},{"name":"http2","value":false},{"name":"http3","value":false},{"name":"insecure","value":false},{"name":"ipv4","value":false},{"name":"ipv6","value":false},{"name":"max-redirs","value":10},{"name":"netrc","value":false},{"name":"netrc-file","value":"netrcfile"},{"name":"netrc-optional","value":false},{"name":"output","value":"output.txt"},{"name":"path-as-is","value":false},{"name":"proxy","value":"http://proxy.example"},{"name":"repeat","value":-1},{"name":"repeat","value":5},{"name":"resolve","value":"example.com:443:127.0.0.1"},{"name":"retry","value":0},{"name":"retry","value":-1},{"name":"retry","value":4},{"name":"retry-interval","value":1000},{"name":"skip","value":false},{"name":"unix-socket","value":"build/unix_socket.sock"},{"name":"user","value":"bob:secret"},{"name":"variable","value":"user=null"},{"name":"variable","value":"status=true"},{"name":"variable","value":"count=2"},{"name":"variable","value":"score=7.7"},{"name":"variable","value":"name=Bob"},{"name":"verbose","value":false},{"name":"very-verbose","value":false}]}},{"request":{"method":"GET","url":"http://localhost:8000/hello","options":[{"name":"aws-sigv4","value":"{{aws-sigv4}}"},{"name":"cacert","value":"{{cacert}}"},{"name":"cert","value":"{{cert}}"},{"name":"key","value":"{{key}}"},{"name":"compressed","value":"{{compressed}}"},{"name":"connect-to","value":"{{connect-to}}"},{"name":"delay","value":"{{delay}}"},{"name":"location","value":"{{location}}"},{"name":"location-trusted","value":"{{location-trusted}}"},{"name":"http1.0","value":"{{http10}}"},{"name":"http1.1","value":"{{http11}}"},{"name":"http2","value":"{{http2}}"},{"name":"http3","value":"{{http3}}"},{"name":"insecure","value":"{{insecure}}"},{"name":"ipv4","value":"{{ipv4}}"},{"name":"ipv6","value":"{{ipv6}}"},{"name":"max-redirs","value":"{{max-redirs}}"},{"name":"netrc","value":"{{netrc}}"},{"name":"netrc-file","value":"{{netrc-file}}"},{"name":"netrc-optional","value":"{{netrc-optional}}"},{"name":"output","value":"{{output}}"},{"name":"path-as-is","value":"{{path-as-is}}"},{"name":"proxy","value":"{{proxy}}"},{"name":"repeat","value":"{{repeat}}"},{"name":"resolve","value":"{{resolve}}"},{"name":"retry","value":"{{retry}}"},{"name":"retry-interval","value":"{{retry-interval}}"},{"name":"skip","value":"{{skip}}"},{"name":"unix-socket","value":"{{socket-file}}"},{"name":"user","value":"{{user}}"},{"name":"verbose","value":"{{verbose}}"},{"name":"very-verbose","value":"{{very-verbose}}"}]}}]}
|
||||||
|
@ -24,6 +24,8 @@ netrc-optional: false
|
|||||||
output: output.txt
|
output: output.txt
|
||||||
path-as-is: false
|
path-as-is: false
|
||||||
proxy: http://proxy.example
|
proxy: http://proxy.example
|
||||||
|
repeat: -1
|
||||||
|
repeat: 5
|
||||||
resolve: example.com:443:127.0.0.1
|
resolve: example.com:443:127.0.0.1
|
||||||
retry: 0
|
retry: 0
|
||||||
retry: -1
|
retry: -1
|
||||||
@ -65,6 +67,7 @@ netrc-optional: {{netrc-optional}}
|
|||||||
output: {{output}}
|
output: {{output}}
|
||||||
path-as-is: {{path-as-is}}
|
path-as-is: {{path-as-is}}
|
||||||
proxy: {{proxy}}
|
proxy: {{proxy}}
|
||||||
|
repeat: {{repeat}}
|
||||||
resolve: {{resolve}}
|
resolve: {{resolve}}
|
||||||
retry: {{retry}}
|
retry: {{retry}}
|
||||||
retry-interval: {{retry-interval}}
|
retry-interval: {{retry-interval}}
|
||||||
|
@ -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::Retry;
|
use hurl_core::typing::{Repeat, Retry};
|
||||||
|
|
||||||
use crate::http::{Call, Client};
|
use crate::http::{Call, Client};
|
||||||
use crate::runner::event::EventListener;
|
use crate::runner::event::EventListener;
|
||||||
@ -140,6 +140,7 @@ pub fn run_entries(
|
|||||||
let mut entries_result = vec![];
|
let mut entries_result = vec![];
|
||||||
let mut variables = variables.clone();
|
let mut variables = variables.clone();
|
||||||
let mut entry_index = runner_options.from_entry.unwrap_or(1);
|
let mut entry_index = runner_options.from_entry.unwrap_or(1);
|
||||||
|
let mut repeat_count = 0;
|
||||||
let n = runner_options.to_entry.unwrap_or(entries.len());
|
let n = runner_options.to_entry.unwrap_or(entries.len());
|
||||||
let default_verbosity = logger.verbosity;
|
let default_verbosity = logger.verbosity;
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
@ -215,6 +216,14 @@ pub fn run_entries(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repeat 0 is equivalent to skip.
|
||||||
|
if options.repeat == Some(Repeat::Count(0)) {
|
||||||
|
logger.debug("");
|
||||||
|
logger.debug_important(&format!("Entry {entry_index} is skipped (repeat 0 times)"));
|
||||||
|
entry_index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Should we delay?
|
// Should we delay?
|
||||||
let delay = options.delay;
|
let delay = options.delay;
|
||||||
let delay_ms = delay.as_millis();
|
let delay_ms = delay.as_millis();
|
||||||
@ -252,9 +261,28 @@ pub fn run_entries(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We pass to the next entry
|
// We pass to the next entry if the repeat count is reached.
|
||||||
|
repeat_count += 1;
|
||||||
|
match options.repeat {
|
||||||
|
None => {
|
||||||
|
repeat_count = 0;
|
||||||
entry_index += 1;
|
entry_index += 1;
|
||||||
}
|
}
|
||||||
|
Some(Repeat::Count(n)) => {
|
||||||
|
if repeat_count >= n {
|
||||||
|
repeat_count = 0;
|
||||||
|
entry_index += 1;
|
||||||
|
} else {
|
||||||
|
logger.debug_important(&format!(
|
||||||
|
"Repeat entry {entry_index} (x{repeat_count}/{n})"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Repeat::Forever) => {
|
||||||
|
logger.debug_important(&format!("Repeat entry {entry_index} (x{repeat_count})"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let time_in_ms = start.elapsed().as_millis();
|
let time_in_ms = start.elapsed().as_millis();
|
||||||
let cookies = http_client.get_cookie_storage();
|
let cookies = http_client.get_cookie_storage();
|
||||||
|
@ -20,9 +20,9 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use hurl_core::ast::{
|
use hurl_core::ast::{
|
||||||
BooleanOption, Entry, EntryOption, Float, NaturalOption, Number as AstNumber, OptionKind,
|
BooleanOption, Entry, EntryOption, Float, NaturalOption, Number as AstNumber, OptionKind,
|
||||||
RetryOption, SectionValue, VariableDefinition, VariableValue,
|
RepeatOption, RetryOption, SectionValue, VariableDefinition, VariableValue,
|
||||||
};
|
};
|
||||||
use hurl_core::typing::Retry;
|
use hurl_core::typing::{Repeat, Retry};
|
||||||
|
|
||||||
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};
|
||||||
@ -199,6 +199,10 @@ pub fn get_entry_options(
|
|||||||
let value = eval_template(value, variables)?;
|
let value = eval_template(value, variables)?;
|
||||||
entry_options.proxy = Some(value);
|
entry_options.proxy = Some(value);
|
||||||
}
|
}
|
||||||
|
OptionKind::Repeat(value) => {
|
||||||
|
let value = eval_repeat_option(value, variables)?;
|
||||||
|
entry_options.repeat = Some(value);
|
||||||
|
}
|
||||||
OptionKind::Resolve(value) => {
|
OptionKind::Resolve(value) => {
|
||||||
let value = eval_template(value, variables)?;
|
let value = eval_template(value, variables)?;
|
||||||
entry_options.resolves.push(value);
|
entry_options.resolves.push(value);
|
||||||
@ -347,6 +351,41 @@ fn eval_natural_option(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render an AST repeat option with a `variables` set.
|
||||||
|
fn eval_repeat_option(
|
||||||
|
repeat_option_value: &RepeatOption,
|
||||||
|
variables: &HashMap<String, Value>,
|
||||||
|
) -> Result<Repeat, RunnerError> {
|
||||||
|
match repeat_option_value {
|
||||||
|
RepeatOption::Literal(repeat) => Ok(*repeat),
|
||||||
|
RepeatOption::Expression(expr) => match eval_expression(expr, variables)? {
|
||||||
|
Value::Number(Number::Integer(value)) => {
|
||||||
|
if value == -1 {
|
||||||
|
Ok(Repeat::Forever)
|
||||||
|
} else if value >= 0 {
|
||||||
|
Ok(Repeat::Count(value as usize))
|
||||||
|
} else {
|
||||||
|
let kind = RunnerErrorKind::TemplateVariableInvalidType {
|
||||||
|
name: expr.variable.name.clone(),
|
||||||
|
value: value.to_string(),
|
||||||
|
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.to_string(),
|
||||||
|
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(
|
fn eval_retry_option(
|
||||||
retry_option_value: &RetryOption,
|
retry_option_value: &RetryOption,
|
||||||
variables: &HashMap<String, Value>,
|
variables: &HashMap<String, Value>,
|
||||||
|
@ -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::Retry;
|
use hurl_core::typing::{Repeat, Retry};
|
||||||
|
|
||||||
use crate::http::{IpResolve, RequestedHttpVersion};
|
use crate::http::{IpResolve, RequestedHttpVersion};
|
||||||
use crate::runner::Output;
|
use crate::runner::Output;
|
||||||
@ -54,6 +54,7 @@ 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>,
|
||||||
resolves: Vec<String>,
|
resolves: Vec<String>,
|
||||||
retry: Option<Retry>,
|
retry: Option<Retry>,
|
||||||
retry_interval: Duration,
|
retry_interval: Duration,
|
||||||
@ -98,6 +99,7 @@ impl Default for RunnerOptionsBuilder {
|
|||||||
post_entry: None,
|
post_entry: None,
|
||||||
pre_entry: None,
|
pre_entry: None,
|
||||||
proxy: None,
|
proxy: None,
|
||||||
|
repeat: None,
|
||||||
resolves: vec![],
|
resolves: vec![],
|
||||||
retry: None,
|
retry: None,
|
||||||
retry_interval: Duration::from_millis(1000),
|
retry_interval: Duration::from_millis(1000),
|
||||||
@ -319,6 +321,12 @@ impl RunnerOptionsBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the number of repetition for a given entry.
|
||||||
|
pub fn repeat(&mut self, repeat: Option<Repeat>) -> &mut Self {
|
||||||
|
self.repeat = repeat;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Provides a custom address for a specific host and port pair.
|
/// Provides a custom address for a specific host and port pair.
|
||||||
pub fn resolves(&mut self, resolves: &[String]) -> &mut Self {
|
pub fn resolves(&mut self, resolves: &[String]) -> &mut Self {
|
||||||
self.resolves = resolves.to_vec();
|
self.resolves = resolves.to_vec();
|
||||||
@ -416,6 +424,7 @@ impl RunnerOptionsBuilder {
|
|||||||
post_entry: self.post_entry,
|
post_entry: self.post_entry,
|
||||||
pre_entry: self.pre_entry,
|
pre_entry: self.pre_entry,
|
||||||
proxy: self.proxy.clone(),
|
proxy: self.proxy.clone(),
|
||||||
|
repeat: self.repeat,
|
||||||
resolves: self.resolves.clone(),
|
resolves: self.resolves.clone(),
|
||||||
retry: self.retry,
|
retry: self.retry,
|
||||||
retry_interval: self.retry_interval,
|
retry_interval: self.retry_interval,
|
||||||
@ -461,6 +470,7 @@ 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) resolves: Vec<String>,
|
pub(crate) resolves: Vec<String>,
|
||||||
pub(crate) retry: Option<Retry>,
|
pub(crate) retry: Option<Retry>,
|
||||||
pub(crate) retry_interval: Duration,
|
pub(crate) retry_interval: Duration,
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
use crate::ast::json;
|
use crate::ast::json;
|
||||||
use crate::typing::Retry;
|
use crate::typing::{Repeat, Retry};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Hurl AST
|
/// Hurl AST
|
||||||
@ -737,6 +737,7 @@ pub enum OptionKind {
|
|||||||
Output(Template),
|
Output(Template),
|
||||||
PathAsIs(BooleanOption),
|
PathAsIs(BooleanOption),
|
||||||
Proxy(Template),
|
Proxy(Template),
|
||||||
|
Repeat(RepeatOption),
|
||||||
Resolve(Template),
|
Resolve(Template),
|
||||||
Retry(RetryOption),
|
Retry(RetryOption),
|
||||||
RetryInterval(NaturalOption),
|
RetryInterval(NaturalOption),
|
||||||
@ -774,6 +775,7 @@ impl OptionKind {
|
|||||||
OptionKind::Output(_) => "output",
|
OptionKind::Output(_) => "output",
|
||||||
OptionKind::PathAsIs(_) => "path-as-is",
|
OptionKind::PathAsIs(_) => "path-as-is",
|
||||||
OptionKind::Proxy(_) => "proxy",
|
OptionKind::Proxy(_) => "proxy",
|
||||||
|
OptionKind::Repeat(_) => "repeat",
|
||||||
OptionKind::Resolve(_) => "resolve",
|
OptionKind::Resolve(_) => "resolve",
|
||||||
OptionKind::Retry(_) => "retry",
|
OptionKind::Retry(_) => "retry",
|
||||||
OptionKind::RetryInterval(_) => "retry-interval",
|
OptionKind::RetryInterval(_) => "retry-interval",
|
||||||
@ -811,6 +813,7 @@ impl OptionKind {
|
|||||||
OptionKind::Output(filename) => filename.to_string(),
|
OptionKind::Output(filename) => filename.to_string(),
|
||||||
OptionKind::PathAsIs(value) => value.to_string(),
|
OptionKind::PathAsIs(value) => value.to_string(),
|
||||||
OptionKind::Proxy(value) => value.to_string(),
|
OptionKind::Proxy(value) => value.to_string(),
|
||||||
|
OptionKind::Repeat(value) => value.to_string(),
|
||||||
OptionKind::Resolve(value) => value.to_string(),
|
OptionKind::Resolve(value) => value.to_string(),
|
||||||
OptionKind::Retry(value) => value.to_string(),
|
OptionKind::Retry(value) => value.to_string(),
|
||||||
OptionKind::RetryInterval(value) => value.to_string(),
|
OptionKind::RetryInterval(value) => value.to_string(),
|
||||||
@ -838,6 +841,12 @@ pub enum NaturalOption {
|
|||||||
Expression(Expr),
|
Expression(Expr),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum RepeatOption {
|
||||||
|
Literal(Repeat),
|
||||||
|
Expression(Expr),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum RetryOption {
|
pub enum RetryOption {
|
||||||
Literal(Retry),
|
Literal(Retry),
|
||||||
|
@ -187,6 +187,15 @@ impl fmt::Display for NaturalOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RepeatOption {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
RepeatOption::Literal(v) => write!(f, "{}", v),
|
||||||
|
RepeatOption::Expression(v) => write!(f, "{}", v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for RetryOption {
|
impl fmt::Display for RetryOption {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
use crate::typing::Retry;
|
use crate::typing::{Repeat, Retry};
|
||||||
|
|
||||||
/// Returns an HTML string of the Hurl file `hurl_file`.
|
/// Returns an HTML string of the Hurl file `hurl_file`.
|
||||||
///
|
///
|
||||||
@ -238,6 +238,7 @@ 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::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_retry_option(value),
|
||||||
OptionKind::RetryInterval(value) => self.fmt_natural_option(value),
|
OptionKind::RetryInterval(value) => self.fmt_natural_option(value),
|
||||||
@ -252,6 +253,20 @@ impl HtmlFormatter {
|
|||||||
self.fmt_lt(&option.line_terminator0);
|
self.fmt_lt(&option.line_terminator0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fmt_repeat_option(&mut self, repeat_option: &RepeatOption) {
|
||||||
|
match repeat_option {
|
||||||
|
RepeatOption::Literal(repeat) => self.fmt_repeat(repeat),
|
||||||
|
RepeatOption::Expression(expr) => self.fmt_expr(expr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_repeat(&mut self, repeat: &Repeat) {
|
||||||
|
match repeat {
|
||||||
|
Repeat::Count(n) => self.fmt_number(n),
|
||||||
|
Repeat::Forever => self.fmt_number(-1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn fmt_retry_option(&mut self, retry_option: &RetryOption) {
|
fn fmt_retry_option(&mut self, retry_option: &RetryOption) {
|
||||||
match retry_option {
|
match retry_option {
|
||||||
RetryOption::Literal(retry) => self.fmt_retry(retry),
|
RetryOption::Literal(retry) => self.fmt_retry(retry),
|
||||||
|
@ -23,7 +23,7 @@ use crate::parser::primitives::*;
|
|||||||
use crate::parser::reader::Reader;
|
use crate::parser::reader::Reader;
|
||||||
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::typing::Retry;
|
use crate::typing::{Repeat, Retry};
|
||||||
|
|
||||||
/// 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> {
|
||||||
@ -62,6 +62,7 @@ pub fn parse(reader: &mut Reader) -> ParseResult<EntryOption> {
|
|||||||
"output" => option_output(reader)?,
|
"output" => option_output(reader)?,
|
||||||
"path-as-is" => option_path_as_is(reader)?,
|
"path-as-is" => option_path_as_is(reader)?,
|
||||||
"proxy" => option_proxy(reader)?,
|
"proxy" => option_proxy(reader)?,
|
||||||
|
"repeat" => option_repeat(reader)?,
|
||||||
"resolve" => option_resolve(reader)?,
|
"resolve" => option_resolve(reader)?,
|
||||||
"retry" => option_retry(reader)?,
|
"retry" => option_retry(reader)?,
|
||||||
"retry-interval" => option_retry_interval(reader)?,
|
"retry-interval" => option_retry_interval(reader)?,
|
||||||
@ -206,6 +207,11 @@ fn option_proxy(reader: &mut Reader) -> ParseResult<OptionKind> {
|
|||||||
Ok(OptionKind::Proxy(value))
|
Ok(OptionKind::Proxy(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn option_repeat(reader: &mut Reader) -> ParseResult<OptionKind> {
|
||||||
|
let value = repeat_option(reader)?;
|
||||||
|
Ok(OptionKind::Repeat(value))
|
||||||
|
}
|
||||||
|
|
||||||
fn option_resolve(reader: &mut Reader) -> ParseResult<OptionKind> {
|
fn option_resolve(reader: &mut Reader) -> ParseResult<OptionKind> {
|
||||||
let value = unquoted_template(reader)?;
|
let value = unquoted_template(reader)?;
|
||||||
Ok(OptionKind::Resolve(value))
|
Ok(OptionKind::Resolve(value))
|
||||||
@ -251,6 +257,21 @@ fn option_very_verbose(reader: &mut Reader) -> ParseResult<OptionKind> {
|
|||||||
Ok(OptionKind::VeryVerbose(value))
|
Ok(OptionKind::VeryVerbose(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn repeat(reader: &mut Reader) -> ParseResult<Repeat> {
|
||||||
|
let pos = reader.state.pos;
|
||||||
|
let value = nonrecover(integer, reader)?;
|
||||||
|
if value == -1 {
|
||||||
|
Ok(Repeat::Forever)
|
||||||
|
} else if value >= 0 {
|
||||||
|
Ok(Repeat::Count(value as usize))
|
||||||
|
} else {
|
||||||
|
let kind = ParseErrorKind::Expecting {
|
||||||
|
value: "Expecting a repeat value".to_string(),
|
||||||
|
};
|
||||||
|
Err(ParseError::new(pos, false, kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn retry(reader: &mut Reader) -> ParseResult<Retry> {
|
fn retry(reader: &mut Reader) -> ParseResult<Retry> {
|
||||||
let pos = reader.state.pos;
|
let pos = reader.state.pos;
|
||||||
let value = nonrecover(integer, reader)?;
|
let value = nonrecover(integer, reader)?;
|
||||||
@ -300,6 +321,23 @@ fn natural_option(reader: &mut Reader) -> ParseResult<NaturalOption> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn repeat_option(reader: &mut Reader) -> ParseResult<RepeatOption> {
|
||||||
|
let start = reader.state;
|
||||||
|
match repeat(reader) {
|
||||||
|
Ok(v) => Ok(RepeatOption::Literal(v)),
|
||||||
|
Err(_) => {
|
||||||
|
reader.state = 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> {
|
fn retry_option(reader: &mut Reader) -> ParseResult<RetryOption> {
|
||||||
let start = reader.state;
|
let start = reader.state;
|
||||||
match retry(reader) {
|
match retry(reader) {
|
||||||
|
@ -31,6 +31,15 @@ impl Default for Repeat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// 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
|
/// Contrary to [`Repeat`], [`Retry`] has a notion of retry only on operation failure, while
|
||||||
/// [`Repeat`] is unconditional.
|
/// [`Repeat`] is unconditional.
|
||||||
@ -43,7 +52,7 @@ pub enum Retry {
|
|||||||
impl fmt::Display for Retry {
|
impl fmt::Display for Retry {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let value = match self {
|
let value = match self {
|
||||||
Retry::Finite(n) => *n as i32,
|
Retry::Finite(n) => *n as i64,
|
||||||
Retry::Infinite => -1,
|
Retry::Infinite => -1,
|
||||||
};
|
};
|
||||||
write!(f, "{}", value)
|
write!(f, "{}", value)
|
||||||
|
@ -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::Retry;
|
use hurl_core::typing::{Repeat, Retry};
|
||||||
|
|
||||||
use super::serialize_json::*;
|
use super::serialize_json::*;
|
||||||
|
|
||||||
@ -316,6 +316,7 @@ impl ToJson for EntryOption {
|
|||||||
OptionKind::Output(filename) => JValue::String(filename.to_string()),
|
OptionKind::Output(filename) => JValue::String(filename.to_string()),
|
||||||
OptionKind::PathAsIs(value) => value.to_json(),
|
OptionKind::PathAsIs(value) => value.to_json(),
|
||||||
OptionKind::Proxy(value) => JValue::String(value.to_string()),
|
OptionKind::Proxy(value) => JValue::String(value.to_string()),
|
||||||
|
OptionKind::Repeat(value) => value.to_json(),
|
||||||
OptionKind::Resolve(value) => JValue::String(value.to_string()),
|
OptionKind::Resolve(value) => JValue::String(value.to_string()),
|
||||||
OptionKind::Retry(value) => value.to_json(),
|
OptionKind::Retry(value) => value.to_json(),
|
||||||
OptionKind::RetryInterval(value) => value.to_json(),
|
OptionKind::RetryInterval(value) => value.to_json(),
|
||||||
@ -352,6 +353,24 @@ impl ToJson for NaturalOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToJson for RepeatOption {
|
||||||
|
fn to_json(&self) -> JValue {
|
||||||
|
match self {
|
||||||
|
RepeatOption::Literal(value) => value.to_json(),
|
||||||
|
RepeatOption::Expression(expr) => expr.to_json(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToJson for Repeat {
|
||||||
|
fn to_json(&self) -> JValue {
|
||||||
|
match self {
|
||||||
|
Repeat::Count(n) => JValue::Number(n.to_string()),
|
||||||
|
Repeat::Forever => JValue::Number("-1".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToJson for RetryOption {
|
impl ToJson for RetryOption {
|
||||||
fn to_json(&self) -> JValue {
|
fn to_json(&self) -> JValue {
|
||||||
match self {
|
match self {
|
||||||
@ -364,7 +383,7 @@ impl ToJson for RetryOption {
|
|||||||
impl ToJson for Retry {
|
impl ToJson for Retry {
|
||||||
fn to_json(&self) -> JValue {
|
fn to_json(&self) -> JValue {
|
||||||
match self {
|
match self {
|
||||||
Retry::Finite(value) => JValue::Number(value.to_string()),
|
Retry::Finite(n) => JValue::Number(n.to_string()),
|
||||||
Retry::Infinite => JValue::Number("-1".to_string()),
|
Retry::Infinite => JValue::Number("-1".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serde json son can not be easily used for serialization here because of the orphan rule.
|
* Serde-json can not be easily used for serialization here because of the orphan rule.
|
||||||
* It seems easier just to reimplement it from scratch (around 50 lines of code)
|
* It seems easier just to reimplement it from scratch (around 50 lines of code)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
use hurl_core::ast::*;
|
use hurl_core::ast::*;
|
||||||
use hurl_core::typing::Retry;
|
use hurl_core::typing::{Repeat, Retry};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Token {
|
pub enum Token {
|
||||||
@ -903,6 +903,7 @@ impl Tokenizable for OptionKind {
|
|||||||
OptionKind::Output(filename) => filename.tokenize(),
|
OptionKind::Output(filename) => filename.tokenize(),
|
||||||
OptionKind::PathAsIs(value) => value.tokenize(),
|
OptionKind::PathAsIs(value) => value.tokenize(),
|
||||||
OptionKind::Proxy(value) => value.tokenize(),
|
OptionKind::Proxy(value) => value.tokenize(),
|
||||||
|
OptionKind::Repeat(value) => value.tokenize(),
|
||||||
OptionKind::Resolve(value) => value.tokenize(),
|
OptionKind::Resolve(value) => value.tokenize(),
|
||||||
OptionKind::Retry(value) => value.tokenize(),
|
OptionKind::Retry(value) => value.tokenize(),
|
||||||
OptionKind::RetryInterval(value) => value.tokenize(),
|
OptionKind::RetryInterval(value) => value.tokenize(),
|
||||||
@ -934,6 +935,24 @@ impl Tokenizable for NaturalOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Tokenizable for RepeatOption {
|
||||||
|
fn tokenize(&self) -> Vec<Token> {
|
||||||
|
match self {
|
||||||
|
RepeatOption::Literal(retry) => retry.tokenize(),
|
||||||
|
RepeatOption::Expression(expr) => expr.tokenize(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tokenizable for Repeat {
|
||||||
|
fn tokenize(&self) -> Vec<Token> {
|
||||||
|
match self {
|
||||||
|
Repeat::Count(n) => vec![Token::Number(n.to_string())],
|
||||||
|
Repeat::Forever => vec![Token::Number("-1".to_string())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Tokenizable for RetryOption {
|
impl Tokenizable for RetryOption {
|
||||||
fn tokenize(&self) -> Vec<Token> {
|
fn tokenize(&self) -> Vec<Token> {
|
||||||
match self {
|
match self {
|
||||||
|
Loading…
Reference in New Issue
Block a user