use lychee_lib::{CacheStatus, ResponseBody, Status};
use crate::formatters::color::{DIM, GREEN, PINK, YELLOW};
use super::{MAX_RESPONSE_OUTPUT_WIDTH, ResponseFormatter};
pub(crate) struct ColorFormatter;
impl ColorFormatter {
fn status_color(status: &Status) -> &'static std::sync::LazyLock<console::Style> {
match status {
Status::Ok(_) | Status::Cached(CacheStatus::Ok(_)) => &GREEN,
Status::Excluded
| Status::Unsupported(_)
| Status::Cached(CacheStatus::Excluded | CacheStatus::Unsupported) => &DIM,
Status::UnknownStatusCode(_) | Status::UnknownMailStatus(_) | Status::Timeout(_) => {
&YELLOW
}
Status::Error(_) | Status::RequestError(_) | Status::Cached(CacheStatus::Error(_)) => {
&PINK
}
}
}
fn format_status(status: &Status) -> String {
let status_code_or_text = status.code_as_string();
let padding = MAX_RESPONSE_OUTPUT_WIDTH.saturating_sub(status_code_or_text.len() + 2);
format!("{}[{}]", " ".repeat(padding), status_code_or_text)
}
fn format_response_status(status: &Status) -> String {
let status_color = ColorFormatter::status_color(status);
let formatted_status = ColorFormatter::format_status(status);
status_color.apply_to(formatted_status).to_string()
}
}
impl ResponseFormatter for ColorFormatter {
fn format_response(&self, body: &ResponseBody) -> String {
let colored_status = ColorFormatter::format_response_status(&body.status);
format!("{colored_status} {body}")
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::StatusCode;
use lychee_lib::{ErrorKind, Status, Uri};
use pretty_assertions::assert_eq;
use test_utils::mock_response_body;
fn strip_ansi_codes(s: &str) -> String {
console::strip_ansi_codes(s).to_string()
}
#[test]
fn test_format_status() {
let status = Status::Ok(StatusCode::OK);
assert_eq!(ColorFormatter::format_status(&status).trim_start(), "[200]");
}
#[test]
fn test_format_response_with_ok_status() {
let formatter = ColorFormatter;
let body = mock_response_body!(Status::Ok(StatusCode::OK), "https://example.com");
let formatted_response = strip_ansi_codes(&formatter.format_response(&body));
assert_eq!(formatted_response, " [200] https://example.com/");
}
#[test]
fn test_format_response_with_error_status() {
let formatter = ColorFormatter;
let body = mock_response_body!(
Status::Error(ErrorKind::EmptyUrl),
"https://example.com/404",
);
let formatted_response = strip_ansi_codes(&formatter.format_response(&body));
assert_eq!(
formatted_response,
" [ERROR] https://example.com/404 | Empty URL found but a URL must not be empty"
);
}
#[test]
fn test_format_response_with_long_uri() {
let formatter = ColorFormatter;
let long_uri =
"https://example.com/some/very/long/path/to/a/resource/that/exceeds/normal/lengths";
let body = mock_response_body!(Status::Ok(StatusCode::OK), long_uri);
let formatted_response = strip_ansi_codes(&formatter.format_response(&body));
assert!(formatted_response.contains(long_uri));
}
#[test]
fn test_error_response_output() {
let formatter = ColorFormatter;
let body = mock_response_body!(
Status::Error(ErrorKind::EmptyUrl),
"https://example.com/404",
);
let response = strip_ansi_codes(&formatter.format_response(&body));
assert_eq!(
response,
" [ERROR] https://example.com/404 | Empty URL found but a URL must not be empty"
);
}
}