use axum::http::HeaderValue;
use crate::error::HttpError;
use crate::format::{OUTPUT_MIME_ALIAS, OUTPUT_MIME_PRIMARY};
#[inline]
pub fn negotiate_accept(header_value: Option<&HeaderValue>) -> Result<&'static str, HttpError> {
let Some(header_val) = header_value else {
return Ok(OUTPUT_MIME_PRIMARY);
};
let header_str = header_val
.to_str()
.map_err(|_err| HttpError::NotAcceptable)?;
for part in header_str.split(',') {
let type_part = part.trim().split(';').next().map_or("", str::trim);
if type_part.eq_ignore_ascii_case("*/*")
|| type_part.eq_ignore_ascii_case("application/*")
|| type_part.eq_ignore_ascii_case(OUTPUT_MIME_PRIMARY)
|| type_part.eq_ignore_ascii_case(OUTPUT_MIME_ALIAS)
{
return Ok(OUTPUT_MIME_PRIMARY);
}
}
Err(HttpError::NotAcceptable)
}
#[inline]
pub fn validate_content_type(header_value: Option<&HeaderValue>) -> Result<(), HttpError> {
let Some(header_val) = header_value else {
return Err(HttpError::UnsupportedMediaType { received: None });
};
let header_str = header_val
.to_str()
.ok()
.ok_or_else(|| HttpError::UnsupportedMediaType {
received: Some("<invalid header value>".to_owned()),
})?;
let parsed: mime::Mime =
header_str
.parse()
.ok()
.ok_or_else(|| HttpError::UnsupportedMediaType {
received: Some(header_str.to_owned()),
})?;
if parsed.type_() != mime::TEXT || parsed.subtype().as_str() != "markdown" {
return Err(HttpError::UnsupportedMediaType {
received: Some(header_str.to_owned()),
});
}
if let Some(charset) = parsed.get_param(mime::CHARSET) {
if !charset.as_str().eq_ignore_ascii_case("utf-8") {
return Err(HttpError::UnsupportedMediaType {
received: Some(header_str.to_owned()),
});
}
}
for (name, _) in parsed.params() {
if name != mime::CHARSET {
return Err(HttpError::UnsupportedMediaType {
received: Some(header_str.to_owned()),
});
}
}
Ok(())
}
#[must_use]
#[inline]
pub fn bucket_input_mime(header_value: Option<&HeaderValue>) -> &'static str {
let Some(header_val) = header_value else {
return crate::metrics::INPUT_MIME_NONE;
};
let Ok(header_str) = header_val.to_str() else {
return crate::metrics::INPUT_MIME_UNSUPPORTED;
};
let Ok(parsed) = header_str.parse::<mime::Mime>() else {
return crate::metrics::INPUT_MIME_UNSUPPORTED;
};
if parsed.type_() == mime::TEXT && parsed.subtype().as_str() == "markdown" {
crate::metrics::INPUT_MIME_MARKDOWN
} else {
crate::metrics::INPUT_MIME_UNSUPPORTED
}
}
#[inline]
#[must_use]
pub fn bucket_output_mime(conversion_ok: bool) -> &'static str {
if conversion_ok {
crate::metrics::OUTPUT_MIME_BLOCKNOTE
} else {
crate::metrics::OUTPUT_MIME_NONE
}
}
#[cfg(test)]
mod bucket_tests {
#![allow(
clippy::tests_outside_test_module,
clippy::unwrap_used,
clippy::expect_used
)]
use super::*;
use axum::http::HeaderValue;
#[test]
fn bucket_input_mime_none_when_header_absent() {
assert_eq!(bucket_input_mime(None), crate::metrics::INPUT_MIME_NONE);
}
#[test]
fn bucket_input_mime_markdown_when_text_markdown() {
let val = HeaderValue::from_static("text/markdown");
assert_eq!(
bucket_input_mime(Some(&val)),
crate::metrics::INPUT_MIME_MARKDOWN
);
}
#[test]
fn bucket_input_mime_markdown_when_text_markdown_with_charset() {
let val = HeaderValue::from_static("text/markdown; charset=utf-8");
assert_eq!(
bucket_input_mime(Some(&val)),
crate::metrics::INPUT_MIME_MARKDOWN
);
}
#[test]
fn bucket_input_mime_markdown_case_insensitive() {
let val = HeaderValue::from_static("TEXT/MARKDOWN");
assert_eq!(
bucket_input_mime(Some(&val)),
crate::metrics::INPUT_MIME_MARKDOWN
);
}
#[test]
fn bucket_input_mime_unsupported_when_other_format() {
let val = HeaderValue::from_static("application/pdf");
assert_eq!(
bucket_input_mime(Some(&val)),
crate::metrics::INPUT_MIME_UNSUPPORTED
);
}
#[test]
fn bucket_input_mime_unsupported_when_malformed() {
let val = HeaderValue::from_static("not a mime type at all");
assert_eq!(
bucket_input_mime(Some(&val)),
crate::metrics::INPUT_MIME_UNSUPPORTED
);
}
#[test]
fn bucket_input_mime_unsupported_when_non_ascii() {
let val = HeaderValue::from_bytes(&[0xFF, 0xFE]).unwrap();
assert_eq!(
bucket_input_mime(Some(&val)),
crate::metrics::INPUT_MIME_UNSUPPORTED
);
}
#[test]
fn bucket_output_mime_blocknote_when_success() {
assert_eq!(
bucket_output_mime(true),
crate::metrics::OUTPUT_MIME_BLOCKNOTE
);
}
#[test]
fn bucket_output_mime_none_when_failure() {
assert_eq!(bucket_output_mime(false), crate::metrics::OUTPUT_MIME_NONE);
}
}