http_extract/
request_id.rs1use http::{HeaderMap, HeaderName, Request};
9
10use crate::{Error, header::extract_single_header_text};
11
12pub const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id");
14
15pub const REQUEST_ID: HeaderName = HeaderName::from_static("request-id");
17
18pub fn extract_header_request_id(headers: &HeaderMap) -> Result<Option<&str>, Error> {
27 if let Some(value) = extract_single_header_text(headers, &X_REQUEST_ID)? {
28 return Ok(Some(value));
29 }
30 if let Some(value) = extract_single_header_text(headers, &REQUEST_ID)? {
31 return Ok(Some(value));
32 }
33 Ok(None)
34}
35
36pub fn extract_request_request_id<B>(request: &Request<B>) -> Result<Option<&str>, Error> {
43 extract_header_request_id(request.headers())
44}
45
46#[cfg(test)]
47mod tests {
48 use http::{HeaderMap, HeaderValue, Request};
49
50 use super::*;
51
52 #[test]
53 fn missing_fallback_and_preferred_values_are_distinguished() {
54 let mut headers = HeaderMap::new();
55 assert_eq!(extract_header_request_id(&headers), Ok(None));
56
57 headers.insert("request-id", "fallback".parse().unwrap());
58 assert_eq!(extract_header_request_id(&headers), Ok(Some("fallback")));
59
60 headers.insert("x-request-id", "preferred".parse().unwrap());
61 assert_eq!(extract_header_request_id(&headers), Ok(Some("preferred")));
62 }
63
64 #[test]
65 fn preserves_empty_x_request_id_value_without_falling_back() {
66 let mut headers = HeaderMap::new();
67 headers.insert("request-id", "fallback".parse().unwrap());
68 headers.insert("x-request-id", "".parse().unwrap());
69 assert_eq!(extract_header_request_id(&headers), Ok(Some("")));
70 }
71
72 #[test]
73 fn rejects_duplicate_and_non_text_selected_fields_without_echoing_values() {
74 let mut headers = HeaderMap::new();
75 headers.append("x-request-id", "first-secret".parse().unwrap());
76 headers.append("x-request-id", "second-secret".parse().unwrap());
77 let error = extract_header_request_id(&headers).unwrap_err();
78 assert!(matches!(error, Error::DuplicateHeader { .. }));
79 assert!(!error.to_string().contains("secret"));
80
81 headers.clear();
82 headers.insert("x-request-id", HeaderValue::from_bytes(&[0xff]).unwrap());
83 assert!(matches!(
84 extract_header_request_id(&headers),
85 Err(Error::InvalidHeader { .. })
86 ));
87 }
88
89 #[test]
90 fn request_entry_point_delegates_to_headers() {
91 let request = Request::builder()
92 .header("request-id", "fallback")
93 .body(())
94 .unwrap();
95 assert_eq!(extract_request_request_id(&request), Ok(Some("fallback")));
96 }
97}