use std::borrow::Cow;
use std::path::Path;
#[derive(Clone, Debug)]
pub struct ConversionResult {
pub bytes: Vec<u8>,
pub content_type: Option<String>,
pub status: u16,
pub request_id: Option<String>,
}
impl ConversionResult {
pub fn text(&self) -> Cow<'_, str> {
decode(&self.bytes, self.content_type.as_deref())
}
pub fn save(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::write(path, &self.bytes)
}
}
fn decode<'a>(body: &'a [u8], content_type: Option<&str>) -> Cow<'a, str> {
let encoding = charset_of(content_type)
.and_then(|charset| encoding_rs::Encoding::for_label(charset.as_bytes()))
.unwrap_or(encoding_rs::UTF_8);
let (decoded, _, _) = encoding.decode(body);
decoded
}
fn charset_of(content_type: Option<&str>) -> Option<String> {
let header = content_type?;
header.split(';').skip(1).find_map(|parameter| {
let (name, value) = parameter.split_once('=')?;
if !name.trim().eq_ignore_ascii_case("charset") {
return None;
}
Some(value.trim().trim_matches('"').to_owned())
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decodes_latin1_by_the_response_charset() {
let body = b"Hello \xE9";
assert_eq!(
decode(body, Some("text/plain;charset=ISO-8859-1")),
"Hello é"
);
assert_eq!(decode(body, Some("text/plain")), "Hello \u{FFFD}");
}
#[test]
fn ignores_an_unrecognized_charset() {
assert_eq!(
decode(b"hi", Some("text/plain; charset=\"not-a-charset\"")),
"hi"
);
assert_eq!(decode(b"hi", None), "hi");
}
#[test]
fn strips_quotes_around_the_charset() {
assert_eq!(
charset_of(Some("text/plain; charset=\"utf-8\"")).as_deref(),
Some("utf-8")
);
}
}