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