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