use http::Request;
pub(crate) fn collect_canonical_header_value<B>(
req: &Request<B>,
lower_name: &str,
) -> Option<String> {
let mut joined = String::new();
let mut seen = false;
for (_, value) in req
.headers()
.iter()
.filter(|(name, _)| name.as_str() == lower_name)
{
let trimmed = value.to_str().unwrap_or("").trim();
if seen {
joined.push_str(", ");
}
seen = true;
joined.push_str(trimmed);
}
seen.then_some(joined)
}
#[cfg(test)]
mod tests {
use http::{Method, Request};
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn returns_none_for_absent_header() {
let req = Request::builder()
.method(Method::GET)
.uri("https://example.com/")
.body(Vec::<u8>::new())
.expect("request");
assert!(collect_canonical_header_value(&req, "x-missing").is_none());
}
#[test]
fn trims_ows_around_single_value() {
let req = Request::builder()
.method(Method::GET)
.uri("https://example.com/")
.header("x-foo", " bar ")
.body(Vec::<u8>::new())
.expect("request");
assert_eq!(
collect_canonical_header_value(&req, "x-foo"),
Some("bar".to_owned())
);
}
#[test]
fn joins_repeated_header_values_with_comma_space() {
let req = Request::builder()
.method(Method::GET)
.uri("https://example.com/")
.header("forwarded", "for=1.1.1.1")
.header("forwarded", "for=2.2.2.2")
.body(Vec::<u8>::new())
.expect("request");
assert_eq!(
collect_canonical_header_value(&req, "forwarded"),
Some("for=1.1.1.1, for=2.2.2.2".to_owned())
);
}
#[test]
fn matches_only_lower_case_header_names() {
let req = Request::builder()
.method(Method::GET)
.uri("https://example.com/")
.header("Content-Type", "application/activity+json")
.body(Vec::<u8>::new())
.expect("request");
assert_eq!(
collect_canonical_header_value(&req, "content-type"),
Some("application/activity+json".to_owned())
);
}
}