use std::io;
use std::path::Path;
use reqwest::header::HeaderMap;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct RenderResult {
pub bytes: Vec<u8>,
pub content_type: String,
pub page_count: u32,
pub file_page_counts: Vec<u32>,
pub labels_billed: Option<u32>,
pub rate_limit: Option<RateLimit>,
pub server_timing: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct RateLimit {
pub limit: u32,
pub remaining: u32,
}
impl RenderResult {
pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
std::fs::write(path, &self.bytes)
}
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
pub(crate) fn from_response(headers: &HeaderMap, bytes: Vec<u8>) -> Self {
let text = |name: &str| {
headers
.get(name)
.and_then(|v| v.to_str().ok())
.map(str::trim)
};
let number = |name: &str| text(name).and_then(|v| v.parse::<u32>().ok());
let rate_limit = match (number("ratelimit-limit"), number("ratelimit-remaining")) {
(Some(limit), Some(remaining)) => Some(RateLimit { limit, remaining }),
_ => None,
};
RenderResult {
bytes,
content_type: text("content-type").unwrap_or_default().to_owned(),
page_count: number("x-page-count").unwrap_or(0),
file_page_counts: text("x-file-page-counts")
.map(|v| v.split(',').filter_map(|n| n.trim().parse().ok()).collect())
.unwrap_or_default(),
labels_billed: number("x-labels-billed"),
rate_limit,
server_timing: text("server-timing").map(str::to_owned),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use reqwest::header::HeaderValue;
#[test]
fn parses_headers() {
let mut headers = HeaderMap::new();
headers.insert("content-type", HeaderValue::from_static("application/pdf"));
headers.insert("x-page-count", HeaderValue::from_static("3"));
headers.insert("x-file-page-counts", HeaderValue::from_static("2, 1"));
headers.insert("x-labels-billed", HeaderValue::from_static("3"));
headers.insert("ratelimit-limit", HeaderValue::from_static("200"));
headers.insert("ratelimit-remaining", HeaderValue::from_static("199"));
headers.insert("server-timing", HeaderValue::from_static("read;dur=0.1"));
let result = RenderResult::from_response(&headers, b"%PDF".to_vec());
assert_eq!(result.page_count, 3);
assert_eq!(result.file_page_counts, vec![2, 1]);
assert_eq!(result.labels_billed, Some(3));
assert_eq!(
result.rate_limit,
Some(RateLimit {
limit: 200,
remaining: 199
})
);
assert_eq!(result.server_timing.as_deref(), Some("read;dur=0.1"));
}
#[test]
fn optional_headers_absent() {
let result = RenderResult::from_response(&HeaderMap::new(), Vec::new());
assert_eq!(result.page_count, 0);
assert!(result.file_page_counts.is_empty());
assert_eq!(result.labels_billed, None);
assert_eq!(result.rate_limit, None);
assert_eq!(result.server_timing, None);
}
}