1use http::{HeaderMap, HeaderName, Request};
10
11use crate::{Error, header::extract_single_header_text};
12
13const X_API_KEY: HeaderName = HeaderName::from_static("x-api-key");
14const API_KEY: HeaderName = HeaderName::from_static("api-key");
15
16pub fn extract_header_api_key(headers: &HeaderMap) -> Result<Option<&str>, Error> {
26 for name in [&X_API_KEY, &API_KEY] {
27 if let Some(value) = extract_single_header_text(headers, name)? {
28 return Ok(Some(value));
29 }
30 }
31 Ok(None)
32}
33
34pub fn extract_request_api_key<B>(request: &Request<B>) -> Result<Option<&str>, Error> {
42 extract_header_api_key(request.headers())
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn uses_x_api_key_before_api_key_fallback() {
51 let mut headers = HeaderMap::new();
52 assert_eq!(extract_header_api_key(&headers).unwrap(), None);
53
54 headers.insert("api-key", "fallback".parse().unwrap());
55 assert_eq!(extract_header_api_key(&headers).unwrap(), Some("fallback"));
56
57 headers.insert("x-api-key", "preferred".parse().unwrap());
58 assert_eq!(extract_header_api_key(&headers).unwrap(), Some("preferred"));
59 }
60
61 #[test]
62 fn preserves_empty_selected_values_without_falling_back() {
63 let mut headers = HeaderMap::new();
64 headers.insert("api-key", "fallback".parse().unwrap());
65 headers.insert("x-api-key", "".parse().unwrap());
66 assert_eq!(extract_header_api_key(&headers).unwrap(), Some(""));
67
68 headers.remove("x-api-key");
69 headers.insert("api-key", "".parse().unwrap());
70 assert_eq!(extract_header_api_key(&headers).unwrap(), Some(""));
71 }
72
73 #[test]
74 fn rejects_duplicate_and_non_text_values_without_echoing_them() {
75 let mut duplicate = HeaderMap::new();
76 duplicate.append("x-api-key", "first-secret".parse().unwrap());
77 duplicate.append("x-api-key", "second-secret".parse().unwrap());
78 let error = extract_header_api_key(&duplicate).unwrap_err();
79 assert!(matches!(error, Error::DuplicateHeader { .. }));
80 assert!(!format!("{error:?}").contains("secret"));
81
82 let mut duplicate_fallback = HeaderMap::new();
83 duplicate_fallback.append("api-key", "first-secret".parse().unwrap());
84 duplicate_fallback.append("api-key", "second-secret".parse().unwrap());
85 assert!(matches!(
86 extract_header_api_key(&duplicate_fallback),
87 Err(Error::DuplicateHeader { .. })
88 ));
89
90 let mut headers = HeaderMap::new();
91 headers.insert("x-api-key", http::HeaderValue::from_bytes(&[0xff]).unwrap());
92 assert!(matches!(
93 extract_header_api_key(&headers),
94 Err(Error::InvalidHeader { .. })
95 ));
96 }
97
98 #[test]
99 fn request_entry_point_delegates_to_headers() {
100 let request = Request::builder()
101 .header("api-key", "fallback")
102 .body(())
103 .unwrap();
104
105 assert_eq!(extract_request_api_key(&request).unwrap(), Some("fallback"));
106 }
107}