apollo-errors-derive 0.5.0

Proc macro for deriving apollo-errors::Error trait
Documentation
//! HTTP header attribute parsing

use syn::{Attribute, Lit, Result};

/// Parse #[http_header("Header-Name")] attribute from a field
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"));
    }
}