use std::cmp::min;
use std::path::Path;
use hurl::parallel::job::{Job, JobResult};
use hurl::parallel::runner::ParallelRunner;
use hurl::pretty::PrettyMode;
use hurl::runner::{HurlResult, Output, VariableSet};
use hurl::util::term::{Stdout, WriteMode};
use hurl::{output, parallel, runner};
use hurl_core::error::{DisplaySourceError, OutputFormat};
use hurl_core::input::Input;
use hurl_core::types::Count;
use crate::cli::CliError;
use crate::cli::options::CliOptions;
use crate::{HurlRun, cli};
pub fn run_seq(
files: &[Input],
current_dir: &Path,
options: &CliOptions,
) -> Result<Vec<HurlRun>, CliError> {
let mut runs = vec![];
let repeat = options.repeat.unwrap_or(Count::Finite(1));
let queue = InputQueue::new(files, repeat);
let mut append = false;
for filename in queue {
let content = filename.read_to_string();
let content = match content {
Ok(c) => c,
Err(error) => {
let error = CliError::InputRead(format!("Issue reading from {filename}: {error}"));
return Err(error);
}
};
let mut variables = VariableSet::from(&options.variables);
options.secrets.iter().for_each(|(name, value)| {
variables.insert_secret(name.clone(), value.clone());
});
let runner_options = options.to_runner_options(&filename, current_dir)?;
let logger_options = options.to_logger_options();
let Ok(hurl_result) = runner::run(
&content,
Some(&filename),
&runner_options,
&variables,
&logger_options,
) else {
return Err(CliError::Parsing);
};
let mut stdout = Stdout::new(WriteMode::Immediate);
print_output(
&hurl_result,
&content,
&filename,
options,
&mut stdout,
append,
)?;
append = true;
let run = HurlRun {
content,
filename: filename.clone(),
hurl_result,
};
runs.push(run);
}
Ok(runs)
}
fn print_output(
hurl_result: &HurlResult,
content: &str,
filename: &Input,
options: &CliOptions,
stdout: &mut Stdout,
append: bool,
) -> Result<(), CliError> {
let output_last_body =
hurl_result.success && matches!(options.output_type, cli::OutputType::ResponseBody);
if output_last_body {
let result = output::write_last_body(
hurl_result,
options.include,
options.color_stdout,
options.pretty,
options.output.as_ref(),
stdout,
append,
);
if let Err(e) = result {
let message = e.render(
&filename.to_string(),
content,
None,
OutputFormat::Terminal(options.color_stderr),
);
return Err(CliError::OutputWrite(message));
}
}
if matches!(options.output_type, cli::OutputType::Json) {
let result = output::write_json(
hurl_result,
content,
filename,
options.output.as_ref(),
stdout,
append,
);
if let Err(e) = result {
let filename = if let Some(Output::File(filename)) = &options.output {
filename.display().to_string()
} else {
"stdout".to_string()
};
let message = format!("{filename} can not be written ({e})");
return Err(CliError::OutputWrite(message));
}
}
Ok(())
}
pub fn run_par(
files: &[Input],
current_dir: &Path,
options: &CliOptions,
workers_count: usize,
) -> Result<Vec<HurlRun>, CliError> {
let workers_count = match options.repeat {
Some(Count::Finite(n)) => min(files.len() * n, workers_count),
Some(Count::Infinite) => workers_count,
None => min(files.len(), workers_count),
};
let mut variables = VariableSet::from(&options.variables);
options.secrets.iter().for_each(|(name, value)| {
variables.insert_secret(name.clone(), value.clone());
});
let output_type =
options
.output_type
.to_output_type(options.include, options.color_stdout, options.pretty);
let max_width = terminal_size::terminal_size().map(|(w, _)| w.0 as usize);
let jobs = files
.iter()
.enumerate()
.map(|(seq, input)| {
let runner_options = options.to_runner_options(input, current_dir)?;
let logger_options = options.to_logger_options();
Ok(Job::new(
input,
seq,
&runner_options,
&variables,
&logger_options,
))
})
.collect::<Result<Vec<Job>, CliError>>()?;
let mut runner = ParallelRunner::new(
workers_count,
output_type,
options.repeat.unwrap_or(Count::Finite(1)),
options.test,
options.progress_bar,
options.color_stderr,
max_width,
);
let results = runner.run(&jobs)?;
let results = results.into_iter().map(HurlRun::from).collect();
Ok(results)
}
impl From<JobResult> for HurlRun {
fn from(job_result: JobResult) -> Self {
HurlRun {
content: job_result.content,
filename: job_result.job.filename,
hurl_result: job_result.hurl_result,
}
}
}
impl cli::OutputType {
fn to_output_type(
&self,
include_headers: bool,
color: bool,
pretty: PrettyMode,
) -> parallel::runner::OutputType {
match self {
cli::OutputType::ResponseBody => parallel::runner::OutputType::ResponseBody {
include_headers,
color,
pretty,
},
cli::OutputType::Json => parallel::runner::OutputType::Json,
cli::OutputType::NoOutput => parallel::runner::OutputType::NoOutput,
}
}
}
pub struct InputQueue<'input> {
inputs: &'input [Input],
index: usize,
repeat: Count,
repeat_index: usize,
}
impl<'input> InputQueue<'input> {
pub fn new(inputs: &'input [Input], repeat: Count) -> Self {
InputQueue {
inputs,
index: 0,
repeat,
repeat_index: 0,
}
}
fn input_at(&self, index: usize) -> Input {
self.inputs[index].clone()
}
}
impl Iterator for InputQueue<'_> {
type Item = Input;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.inputs.len() {
self.repeat_index = self.repeat_index.checked_add(1).unwrap_or(0);
match self.repeat {
Count::Finite(n) => {
if self.repeat_index >= n {
None
} else {
self.index = 1;
Some(self.input_at(0))
}
}
Count::Infinite => {
self.index = 1;
Some(self.input_at(0))
}
}
} else {
self.index += 1;
Some(self.input_at(self.index - 1))
}
}
}
#[cfg(test)]
mod tests {
use hurl_core::input::Input;
use hurl_core::types::Count;
use crate::run::InputQueue;
#[test]
fn input_queue_is_finite() {
let files = [Input::new("a"), Input::new("b"), Input::new("c")];
let mut queue = InputQueue::new(&files, Count::Finite(4));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("b")));
assert_eq!(queue.next(), Some(Input::new("c")));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("b")));
assert_eq!(queue.next(), Some(Input::new("c")));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("b")));
assert_eq!(queue.next(), Some(Input::new("c")));
assert_eq!(queue.next(), Some(Input::new("a")));
assert_eq!(queue.next(), Some(Input::new("b")));
assert_eq!(queue.next(), Some(Input::new("c")));
assert_eq!(queue.next(), None);
}
#[test]
fn input_queue_is_infinite() {
let files = [Input::new("a")];
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")));
}
}