#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProvenanceShape {
Minimal,
Verbose,
}
impl ProvenanceShape {
#[must_use]
pub const fn is_verbose(self) -> bool {
matches!(self, Self::Verbose)
}
}
#[must_use]
pub fn resolve_from_headers(headers: &axum::http::HeaderMap) -> ProvenanceShape {
headers
.get("accept-provenance")
.and_then(|v| v.to_str().ok())
.map(parse_value)
.unwrap_or(ProvenanceShape::Minimal)
}
#[must_use]
pub fn parse_value(raw: &str) -> ProvenanceShape {
let trimmed = raw.trim();
if trimmed.eq_ignore_ascii_case("verbose") {
ProvenanceShape::Verbose
} else {
ProvenanceShape::Minimal
}
}
#[cfg(test)]
mod tests {
use super::*;
use axum::http::{HeaderMap, HeaderValue};
fn hm(name: &str, value: &str) -> HeaderMap {
let mut h = HeaderMap::new();
h.insert(
name.parse::<axum::http::HeaderName>().unwrap(),
HeaderValue::from_str(value).unwrap(),
);
h
}
#[test]
fn absent_header_yields_minimal() {
let h = HeaderMap::new();
assert_eq!(resolve_from_headers(&h), ProvenanceShape::Minimal);
assert!(!resolve_from_headers(&h).is_verbose());
}
#[test]
fn verbose_header_yields_verbose() {
let h = hm("accept-provenance", "verbose");
assert_eq!(resolve_from_headers(&h), ProvenanceShape::Verbose);
assert!(resolve_from_headers(&h).is_verbose());
}
#[test]
fn verbose_header_is_case_insensitive() {
for value in ["VERBOSE", "Verbose", "vErBoSe", "verbose"] {
let h = hm("accept-provenance", value);
assert_eq!(
resolve_from_headers(&h),
ProvenanceShape::Verbose,
"Accept-Provenance: {value} must parse as Verbose"
);
}
}
#[test]
fn minimal_header_explicit_value_yields_minimal() {
let h = hm("accept-provenance", "minimal");
assert_eq!(resolve_from_headers(&h), ProvenanceShape::Minimal);
}
#[test]
fn unrecognised_value_falls_through_to_minimal() {
for value in [
"experimental-v2",
"extended",
"richer",
"true",
"1",
"verbose-plus",
] {
let h = hm("accept-provenance", value);
assert_eq!(
resolve_from_headers(&h),
ProvenanceShape::Minimal,
"Accept-Provenance: {value} must fall through to Minimal"
);
}
}
#[test]
fn whitespace_tolerated() {
for value in [
"verbose",
" verbose",
"verbose ",
" verbose ",
"\tverbose",
] {
let h = hm("accept-provenance", value);
assert_eq!(
resolve_from_headers(&h),
ProvenanceShape::Verbose,
"Accept-Provenance: {value:?} must parse as Verbose after trim"
);
}
}
#[test]
fn empty_header_value_yields_minimal() {
let h = hm("accept-provenance", "");
assert_eq!(resolve_from_headers(&h), ProvenanceShape::Minimal);
}
#[test]
fn header_name_case_insensitive() {
for name in [
"Accept-Provenance",
"accept-provenance",
"ACCEPT-PROVENANCE",
] {
let h = hm(name, "verbose");
assert_eq!(
resolve_from_headers(&h),
ProvenanceShape::Verbose,
"header name `{name}` must resolve case-insensitively"
);
}
}
#[test]
fn parse_value_directly() {
assert_eq!(parse_value("verbose"), ProvenanceShape::Verbose);
assert_eq!(parse_value("minimal"), ProvenanceShape::Minimal);
assert_eq!(parse_value(""), ProvenanceShape::Minimal);
assert_eq!(parse_value("garbage"), ProvenanceShape::Minimal);
assert_eq!(parse_value("Verbose"), ProvenanceShape::Verbose);
}
#[test]
fn shape_copy_clone_equality() {
let a = ProvenanceShape::Verbose;
let b = a;
assert_eq!(a, b);
assert_eq!(a, a.clone());
assert!(a.is_verbose());
assert!(!ProvenanceShape::Minimal.is_verbose());
}
}