use crate::types::{AnalysisResult, Result};
use printpdf::{BuiltinFont, Mm, PdfDocument};
use std::fs::File;
use std::io::BufWriter;
use std::path::Path;
#[allow(clippy::too_many_lines)]
pub fn generate(results: &AnalysisResult, output_path: &Path) -> Result<()> {
let (doc, page1, layer1) = PdfDocument::new(
"ICOokForms Cookie Analysis Report",
Mm(210.0), Mm(297.0), "Layer 1",
);
let current_layer = doc.get_page(page1).get_layer(layer1);
let font = doc.add_builtin_font(BuiltinFont::Helvetica)?;
let font_bold = doc.add_builtin_font(BuiltinFont::HelveticaBold)?;
let title_size = 24.0;
current_layer.use_text(
"ICOokForms Cookie Analysis Report",
title_size,
Mm(20.0),
Mm(270.0),
&font_bold,
);
let mut y_pos = 260.0;
let normal_size = 10.0;
current_layer.use_text(
format!("Analysis ID: {}", results.id).as_str(),
normal_size,
Mm(20.0),
Mm(y_pos),
&font,
);
y_pos -= 5.0;
current_layer.use_text(
format!("Scan ID: {}", results.scan_id).as_str(),
normal_size,
Mm(20.0),
Mm(y_pos),
&font,
);
y_pos -= 5.0;
current_layer.use_text(
format!(
"Date: {date}",
date = results.analyzed_at.format("%Y-%m-%d %H:%M:%S UTC")
)
.as_str(),
normal_size,
Mm(20.0),
Mm(y_pos),
&font,
);
y_pos -= 10.0;
current_layer.use_text("Scores", 14.0, Mm(20.0), Mm(y_pos), &font_bold);
y_pos -= 7.0;
current_layer.use_text(
format!("Risk Score: {score:.1}/100", score = results.risk_score).as_str(),
normal_size,
Mm(20.0),
Mm(y_pos),
&font,
);
y_pos -= 5.0;
current_layer.use_text(
format!(
"Compliance Score: {score:.1}/100",
score = results.compliance_score
)
.as_str(),
normal_size,
Mm(20.0),
Mm(y_pos),
&font,
);
y_pos -= 5.0;
current_layer.use_text(
format!(
"Privacy Score: {score:.1}/100",
score = results.privacy_score
)
.as_str(),
normal_size,
Mm(20.0),
Mm(y_pos),
&font,
);
y_pos -= 10.0;
current_layer.use_text("Summary", 14.0, Mm(20.0), Mm(y_pos), &font_bold);
y_pos -= 7.0;
let summary_items = vec![
format!("Total Cookies: {}", results.summary.total_cookies),
format!("First-Party: {}", results.summary.first_party_cookies),
format!("Third-Party: {}", results.summary.third_party_cookies),
format!("Session Cookies: {}", results.summary.session_cookies),
format!("Persistent Cookies: {}", results.summary.persistent_cookies),
format!("Secure Cookies: {}", results.summary.secure_cookies),
format!("HttpOnly Cookies: {}", results.summary.http_only_cookies),
format!("SameSite Cookies: {}", results.summary.same_site_cookies),
format!("Total Issues: {}", results.summary.total_issues),
];
for item in summary_items {
current_layer.use_text(&item, normal_size, Mm(20.0), Mm(y_pos), &font);
y_pos -= 5.0;
}
y_pos -= 5.0;
if !results.issues.is_empty() {
current_layer.use_text(
format!("Issues ({count})", count = results.issues.len()).as_str(),
14.0,
Mm(20.0),
Mm(y_pos),
&font_bold,
);
y_pos -= 7.0;
for (i, issue) in results.issues.iter().take(10).enumerate() {
if y_pos < 30.0 {
break; }
current_layer.use_text(
format!(
"{num}. [{severity}] {title}",
num = i + 1,
severity = issue.severity,
title = truncate(&issue.title, 60)
)
.as_str(),
normal_size - 1.0,
Mm(20.0),
Mm(y_pos),
&font,
);
y_pos -= 4.0;
}
if results.issues.len() > 10 {
current_layer.use_text(
format!(
"... and {more} more issues",
more = results.issues.len() - 10
)
.as_str(),
normal_size - 1.0,
Mm(20.0),
Mm(y_pos),
&font,
);
}
}
current_layer.use_text(
"Generated by ICOokForms - The World's Reference Cookie Audit Software",
8.0,
Mm(20.0),
Mm(15.0),
&font,
);
let file = File::create(output_path)?;
doc.save(&mut BufWriter::new(file))?;
Ok(())
}
fn truncate(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}...", &s[..max_len - 3])
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn test_truncate() {
assert_eq!(truncate("short", 10), "short");
assert_eq!(truncate("this is a very long string", 10), "this is...");
}
#[test]
fn test_generate_file() {
let results = AnalysisResult::new("test-scan");
let temp_file = NamedTempFile::new().unwrap();
let result = generate(&results, temp_file.path());
assert!(result.is_ok());
let metadata = std::fs::metadata(temp_file.path()).unwrap();
assert!(metadata.len() > 0);
}
}