use http::{HeaderMap, HeaderName, Request};
use crate::{Error, header::extract_single_header_text};
pub const X_API_KEY: HeaderName = HeaderName::from_static("x-api-key");
pub const API_KEY: HeaderName = HeaderName::from_static("api-key");
pub fn extract_header_api_key(headers: &HeaderMap) -> Result<Option<&str>, Error> {
for name in [&X_API_KEY, &API_KEY] {
if let Some(value) = extract_single_header_text(headers, name)? {
return Ok(Some(value));
}
}
Ok(None)
}
pub fn extract_request_api_key<B>(request: &Request<B>) -> Result<Option<&str>, Error> {
extract_header_api_key(request.headers())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uses_x_api_key_before_api_key_fallback() {
let mut headers = HeaderMap::new();
assert_eq!(extract_header_api_key(&headers).unwrap(), None);
headers.insert("api-key", "fallback".parse().unwrap());
assert_eq!(extract_header_api_key(&headers).unwrap(), Some("fallback"));
headers.insert("x-api-key", "preferred".parse().unwrap());
assert_eq!(extract_header_api_key(&headers).unwrap(), Some("preferred"));
}
#[test]
fn preserves_empty_selected_values_without_falling_back() {
let mut headers = HeaderMap::new();
headers.insert("api-key", "fallback".parse().unwrap());
headers.insert("x-api-key", "".parse().unwrap());
assert_eq!(extract_header_api_key(&headers).unwrap(), Some(""));
headers.remove("x-api-key");
headers.insert("api-key", "".parse().unwrap());
assert_eq!(extract_header_api_key(&headers).unwrap(), Some(""));
}
#[test]
fn rejects_duplicate_and_non_text_values_without_echoing_them() {
let mut duplicate = HeaderMap::new();
duplicate.append("x-api-key", "first-secret".parse().unwrap());
duplicate.append("x-api-key", "second-secret".parse().unwrap());
let error = extract_header_api_key(&duplicate).unwrap_err();
assert!(matches!(error, Error::DuplicateHeader { .. }));
assert!(!format!("{error:?}").contains("secret"));
let mut duplicate_fallback = HeaderMap::new();
duplicate_fallback.append("api-key", "first-secret".parse().unwrap());
duplicate_fallback.append("api-key", "second-secret".parse().unwrap());
assert!(matches!(
extract_header_api_key(&duplicate_fallback),
Err(Error::DuplicateHeader { .. })
));
let mut headers = HeaderMap::new();
headers.insert("x-api-key", http::HeaderValue::from_bytes(&[0xff]).unwrap());
assert!(matches!(
extract_header_api_key(&headers),
Err(Error::InvalidHeader { .. })
));
}
#[test]
fn request_entry_point_delegates_to_headers() {
let request = Request::builder()
.header("api-key", "fallback")
.body(())
.unwrap();
assert_eq!(extract_request_api_key(&request).unwrap(), Some("fallback"));
}
}