#![allow(dead_code)]
use super::executor::ApiResponse;
use super::models::{CollectionRequest, CollectionSummary, RequestResult};
use colored::*;
use indicatif::{ProgressBar, ProgressStyle};
use std::time::Instant;
pub trait Reporter {
fn on_start(&mut self, collection_name: &str, total_requests: usize);
fn on_request_start(&mut self, request: &CollectionRequest);
fn on_request_complete(
&mut self,
request: &CollectionRequest,
response: &ApiResponse,
duration_ms: u64,
);
fn on_request_error(&mut self, request: &CollectionRequest, error: &anyhow::Error);
fn on_request_skipped(&mut self, request: &CollectionRequest, reason: &str);
fn on_complete(&mut self, summary: &CollectionSummary);
}
pub struct ConsoleReporter {
progress_bar: Option<ProgressBar>,
start_time: Instant,
verbose: bool,
}
impl ConsoleReporter {
pub fn new(verbose: bool) -> Self {
Self {
progress_bar: None,
start_time: Instant::now(),
verbose,
}
}
}
impl Reporter for ConsoleReporter {
fn on_start(&mut self, collection_name: &str, total_requests: usize) {
self.start_time = Instant::now();
println!(
"{} Collection: {}",
"โถ".cyan().bold(),
collection_name.bold()
);
println!(" {} requests to execute\n", total_requests);
let pb = ProgressBar::new(total_requests as u64);
pb.set_style(
ProgressStyle::default_bar()
.template("{prefix:.bold.dim} {bar:40.cyan/blue} {pos}/{len} {msg}")
.unwrap()
.progress_chars("=>-"),
);
pb.set_prefix("Progress");
self.progress_bar = Some(pb);
}
fn on_request_start(&mut self, request: &CollectionRequest) {
if let Some(pb) = &self.progress_bar {
pb.set_message(format!("Running: {}", request.name));
}
}
fn on_request_complete(
&mut self,
request: &CollectionRequest,
response: &ApiResponse,
duration_ms: u64,
) {
if let Some(pb) = &self.progress_bar {
pb.inc(1);
}
let status_str = format!("{}", response.status_code);
let status_colored = match response.status_code {
200..=299 => status_str.green(),
300..=399 => status_str.yellow(),
400..=499 => status_str.red(),
500..=599 => status_str.red().bold(),
_ => status_str.normal(),
};
println!(
" {} {} {} {} ({}ms)",
"โ".green().bold(),
request.name.bold(),
"โ".dimmed(),
status_colored,
duration_ms
);
if self.verbose {
if let Some(body) = &response.body {
println!(
" Response: {}",
serde_json::to_string_pretty(body)
.unwrap_or_else(|_| "Invalid JSON".to_string())
);
}
}
}
fn on_request_error(&mut self, request: &CollectionRequest, error: &anyhow::Error) {
if let Some(pb) = &self.progress_bar {
pb.inc(1);
}
println!(
" {} {} {} {}",
"โ".red().bold(),
request.name.bold(),
"โ".dimmed(),
error.to_string().red()
);
}
fn on_request_skipped(&mut self, request: &CollectionRequest, reason: &str) {
if let Some(pb) = &self.progress_bar {
pb.inc(1);
}
println!(
" {} {} {} {}",
"โ".yellow().bold(),
request.name.bold(),
"โ".dimmed(),
reason.yellow()
);
}
fn on_complete(&mut self, summary: &CollectionSummary) {
if let Some(pb) = &self.progress_bar {
pb.finish_and_clear();
}
let elapsed = self.start_time.elapsed();
println!("\n{}", "โ".repeat(50));
println!("{} Summary:", "๐".cyan());
println!(" Total: {} requests", summary.total_requests);
println!(
" Successful: {} {}",
summary.successful,
if summary.successful > 0 {
"โ".green()
} else {
"".normal()
}
);
if summary.failed > 0 {
println!(" Failed: {} {}", summary.failed, "โ".red());
}
if summary.skipped > 0 {
println!(" Skipped: {} {}", summary.skipped, "โ".yellow());
}
println!(" Duration: {:.2}s", elapsed.as_secs_f64());
if let Some(test_summary) = &summary.test_summary {
println!("\n{} Test Results:", "๐งช");
println!(
" Tests: {}, Passed: {}, Failed: {}",
test_summary.total_tests, test_summary.passed, test_summary.failed
);
if test_summary.all_passed {
println!("\n {} All tests passed!", "โจ".green().bold());
} else {
println!("\n {} Some tests failed", "โ".red().bold());
}
}
}
}
pub struct JsonReporter {
results: Vec<RequestResult>,
start_time: Instant,
}
impl JsonReporter {
pub fn new() -> Self {
Self {
results: Vec::new(),
start_time: Instant::now(),
}
}
pub fn get_results(self) -> Vec<RequestResult> {
self.results
}
}
impl Reporter for JsonReporter {
fn on_start(&mut self, _collection_name: &str, _total_requests: usize) {
self.start_time = Instant::now();
self.results.clear();
}
fn on_request_start(&mut self, _request: &CollectionRequest) {
}
fn on_request_complete(
&mut self,
request: &CollectionRequest,
response: &ApiResponse,
duration_ms: u64,
) {
self.results.push(RequestResult {
name: request.name.clone(),
operation: request.operation.clone(),
status: response.status_code,
body: response.body.clone(),
headers: response
.headers
.iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect(),
duration_ms,
error: None,
test_result: None,
});
}
fn on_request_error(&mut self, request: &CollectionRequest, error: &anyhow::Error) {
self.results.push(RequestResult {
name: request.name.clone(),
operation: request.operation.clone(),
status: 0,
body: None,
headers: Default::default(),
duration_ms: 0,
error: Some(error.to_string()),
test_result: None,
});
}
fn on_request_skipped(&mut self, request: &CollectionRequest, reason: &str) {
self.results.push(RequestResult {
name: request.name.clone(),
operation: request.operation.clone(),
status: 0,
body: None,
headers: Default::default(),
duration_ms: 0,
error: Some(format!("Skipped: {}", reason)),
test_result: None,
});
}
fn on_complete(&mut self, _summary: &CollectionSummary) {
}
}