use crate::csp::{CspNonce, PagePolicy};
use axum::http::{header, HeaderName, HeaderValue};
use axum::response::{IntoResponse, Response};
use std::time::Duration;
#[derive(Debug)]
pub struct PageResponse {
html: String,
nonce: CspNonce,
policy: PagePolicy,
counts: RenderCounts,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct RenderCounts {
pub nodes: usize,
pub bytes: usize,
pub list_iterations: usize,
pub walk_time: Duration,
}
impl RenderCounts {
pub(crate) fn from_walk(counts: forma_ir::WalkCounts, walk_time: Duration) -> Self {
RenderCounts {
nodes: counts.nodes,
bytes: counts.bytes,
list_iterations: counts.list_iterations,
walk_time,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct PageParts {
pub html: String,
pub nonce: CspNonce,
pub headers: Vec<(HeaderName, HeaderValue)>,
pub counts: RenderCounts,
}
impl PageResponse {
pub(crate) fn with_policy(
html: String,
nonce: CspNonce,
policy: PagePolicy,
counts: RenderCounts,
) -> Self {
PageResponse {
html,
nonce,
policy,
counts,
}
}
pub fn counts(&self) -> RenderCounts {
self.counts
}
pub fn into_parts_unchecked(self) -> PageParts {
let headers = self.headers();
PageParts {
html: self.html,
nonce: self.nonce,
headers,
counts: self.counts,
}
}
fn headers(&self) -> Vec<(HeaderName, HeaderValue)> {
let mut headers = Vec::with_capacity(9);
headers.push((
header::CONTENT_TYPE,
HeaderValue::from_static("text/html; charset=utf-8"),
));
headers.push((header::CACHE_CONTROL, HeaderValue::from_static("no-store")));
headers.push((header::CONTENT_SECURITY_POLICY, self.policy.csp().clone()));
if let Some(report_only) = self.policy.report_only() {
headers.push((
header::CONTENT_SECURITY_POLICY_REPORT_ONLY,
report_only.clone(),
));
}
if let Some(endpoints) = self.policy.reporting_endpoints() {
headers.push((
HeaderName::from_static("reporting-endpoints"),
endpoints.clone(),
));
}
for (name, value) in crate::types::document_security_headers() {
if name.as_str() != "cross-origin-opener-policy" {
headers.push((name.clone(), value.clone()));
}
}
headers.push((
HeaderName::from_static("cross-origin-opener-policy"),
self.policy.opener().clone(),
));
headers
}
}
impl IntoResponse for PageResponse {
fn into_response(self) -> Response {
let headers = self.headers();
let mut response = Response::new(axum::body::Body::from(self.html));
let map = response.headers_mut();
for (name, value) in headers {
map.append(name, value);
}
response
}
}