use syn::{Attribute, Lit, Result};
pub(crate) fn parse_http_header(attrs: &[Attribute]) -> Result<Option<String>> {
let mut result: Option<String> = None;
for attr in attrs {
if attr.path().is_ident("http_header") {
if result.is_some() {
return Err(syn::Error::new_spanned(
attr,
"duplicate #[http_header] attribute",
));
}
let lit: Lit = attr.parse_args()?;
if let Lit::Str(s) = lit {
let header_name = s.value();
if let Err(e) = http::HeaderName::from_bytes(header_name.as_bytes()) {
return Err(syn::Error::new_spanned(
attr,
format!("invalid HTTP header name: {e}"),
));
}
result = Some(header_name);
} else {
return Err(syn::Error::new_spanned(
attr,
"expected string literal for http_header name",
));
}
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn parse_single_http_header() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[http_header("X-Request-Id")])];
let result = parse_http_header(&attrs).unwrap();
assert_eq!(result, Some("X-Request-Id".to_string()));
}
#[test]
fn parse_no_http_header() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[extension])];
let result = parse_http_header(&attrs).unwrap();
assert_eq!(result, None);
}
#[test]
fn reject_duplicate_http_header() {
let attrs: Vec<Attribute> = vec![
parse_quote!(#[http_header("X-First")]),
parse_quote!(#[http_header("X-Second")]),
];
let result = parse_http_header(&attrs);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("duplicate"));
}
#[test]
fn reject_invalid_header_name() {
let attrs: Vec<Attribute> = vec![parse_quote!(#[http_header("Invalid Header")])];
let result = parse_http_header(&attrs);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("invalid"));
}
}