use hurl_core::ast::HurlFile;
use crate::http::Call;
use crate::report::html::Testcase;
use crate::report::html::nav::Tab;
use crate::runner::EntryResult;
use crate::util::redacted::Redact;
impl Testcase {
pub fn get_run_html(
&self,
hurl_file: &HurlFile,
content: &str,
entries: &[EntryResult],
secrets: &[&str],
) -> String {
let nav = self.get_nav_html(content, Tab::Run, secrets);
let nav_css = include_str!("resources/nav.css");
let run_css = include_str!("resources/run.css");
let mut run = String::new();
for (entry_index, e) in entries.iter().enumerate() {
let entry_src_index = e.entry_index.to_zero_based();
let entry_src = hurl_file.entries.get(entry_src_index).unwrap();
let line = entry_src.source_info().start.line;
let source = self.source_filename();
run.push_str("<details open>");
let info = get_entry_html(e, entry_index + 1, secrets);
run.push_str(&info);
for (call_index, c) in e.calls.iter().enumerate() {
let info = get_call_html(
c,
entry_index + 1,
call_index + 1,
&self.filename,
&source,
line,
secrets,
);
run.push_str(&info);
}
run.push_str("</details>");
}
format!(
include_str!("resources/run.html"),
filename = self.filename,
nav = nav,
nav_css = nav_css,
run = run,
run_css = run_css,
)
}
}
fn get_entry_html(entry: &EntryResult, entry_index: usize, secrets: &[&str]) -> String {
let mut text = String::new();
text.push_str(&format!("<summary>Entry {entry_index}</summary>"));
let cmd = entry.curl_cmd.to_string().redact(secrets);
let table = new_table("Debug", &[("Command", &cmd)]);
text.push_str(&table);
if !entry.captures.is_empty() {
let mut values = entry
.captures
.iter()
.map(|c| (&c.name, c.value.to_string().redact(secrets)))
.collect::<Vec<(&String, String)>>();
values.sort_by_key(|a| a.0.to_lowercase());
let table = new_table("Captures", &values);
text.push_str(&table);
}
text
}
fn get_call_html(
call: &Call,
entry_index: usize,
call_index: usize,
filename: &str,
source: &str,
line: usize,
secrets: &[&str],
) -> String {
let mut text = String::new();
let id = format!("e{entry_index}:c{call_index}");
text.push_str(&format!("<h4 id=\"{id}\">Call {call_index}</h3>"));
let status = call.response.status.to_string();
let version = call.response.version.to_string();
let url = &call.request.url.to_string().redact(secrets);
let url = format!("<a href=\"{url}\">{url}</a>");
let source = format!("<a href=\"{source}#l{line}\">{filename}:{line}</a>");
let start_date = call.timings.begin_call.to_rfc2822();
let values = vec![
("Start Date", start_date.as_str()),
("Request URL", url.as_str()),
("Request Method", call.request.method.as_str()),
("Version", version.as_str()),
("Status code", status.as_str()),
("Source", source.as_str()),
];
let table = new_table("General", &values);
text.push_str(&table);
if let Some(certificate) = &call.response.certificate {
let mut values = vec![];
if let Some(subject) = certificate.subject() {
values.push(("Subject", subject.as_str()));
}
if let Some(issuer) = certificate.issuer() {
values.push(("Issuer", issuer.as_str()));
}
let start_date = certificate.start_date().map(|d| d.to_string());
if let Some(start_date) = start_date.as_ref() {
values.push(("Start Date", start_date.as_str()));
}
let expire_date = certificate.expire_date().map(|d| d.to_string());
if let Some(expire_date) = expire_date.as_ref() {
values.push(("Expire Date", expire_date.as_str()));
}
if let Some(serial_number) = certificate.serial_number() {
values.push(("Serial Number", serial_number.as_str()));
}
if let Some(subject_alt_name) = certificate.subject_alt_name() {
values.push(("Subject Alt Name", subject_alt_name.as_str()));
}
let pem_display;
if let Some(pem) = certificate.value() {
if pem.len() > 100 {
pem_display = format!("{}...{}", &pem[..50], &pem[pem.len() - 50..]);
} else {
pem_display = pem.to_string();
}
values.push(("Value", &pem_display));
}
let table = new_table("Certificate", &values);
text.push_str(&table);
}
let mut values = call
.request
.headers
.iter()
.map(|h| (h.name.as_str(), h.value.redact(secrets)))
.collect::<Vec<(&str, String)>>();
values.sort_by_key(|a| a.0.to_lowercase());
let table = new_table("Request Headers", &values);
text.push_str(&table);
let mut values = call
.response
.headers
.iter()
.map(|h| (h.name.as_str(), h.value.redact(secrets)))
.collect::<Vec<(&str, String)>>();
values.sort_by_key(|a| a.0.to_lowercase());
let table = new_table("Response Headers", &values);
text.push_str(&table);
text
}
fn new_table<T: AsRef<str>, U: AsRef<str> + std::fmt::Display>(
title: &str,
data: &[(T, U)],
) -> String {
let mut text = String::new();
text.push_str(&format!(
"<table><thead><tr><th colspan=\"2\">{title}</tr></th></thead><tbody>"
));
data.iter().for_each(|(name, value)| {
text.push_str(&format!(
"<tr><td class=\"name\">{}</td><td class=\"value\">{}</td></tr>",
name.as_ref(),
value
));
});
text.push_str("</tbody></table>");
text
}