use http::{HeaderMap, HeaderName, Request};
use crate::{Error, header::extract_single_header_text};
pub const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id");
pub const REQUEST_ID: HeaderName = HeaderName::from_static("request-id");
pub fn extract_header_request_id(headers: &HeaderMap) -> Result<Option<&str>, Error> {
if let Some(value) = extract_single_header_text(headers, &X_REQUEST_ID)? {
return Ok(Some(value));
}
if let Some(value) = extract_single_header_text(headers, &REQUEST_ID)? {
return Ok(Some(value));
}
Ok(None)
}
pub fn extract_request_request_id<B>(request: &Request<B>) -> Result<Option<&str>, Error> {
extract_header_request_id(request.headers())
}
#[cfg(test)]
mod tests {
use http::{HeaderMap, HeaderValue, Request};
use super::*;
#[test]
fn missing_fallback_and_preferred_values_are_distinguished() {
let mut headers = HeaderMap::new();
assert_eq!(extract_header_request_id(&headers), Ok(None));
headers.insert("request-id", "fallback".parse().unwrap());
assert_eq!(extract_header_request_id(&headers), Ok(Some("fallback")));
headers.insert("x-request-id", "preferred".parse().unwrap());
assert_eq!(extract_header_request_id(&headers), Ok(Some("preferred")));
}
#[test]
fn preserves_empty_x_request_id_value_without_falling_back() {
let mut headers = HeaderMap::new();
headers.insert("request-id", "fallback".parse().unwrap());
headers.insert("x-request-id", "".parse().unwrap());
assert_eq!(extract_header_request_id(&headers), Ok(Some("")));
}
#[test]
fn rejects_duplicate_and_non_text_selected_fields_without_echoing_values() {
let mut headers = HeaderMap::new();
headers.append("x-request-id", "first-secret".parse().unwrap());
headers.append("x-request-id", "second-secret".parse().unwrap());
let error = extract_header_request_id(&headers).unwrap_err();
assert!(matches!(error, Error::DuplicateHeader { .. }));
assert!(!error.to_string().contains("secret"));
headers.clear();
headers.insert("x-request-id", HeaderValue::from_bytes(&[0xff]).unwrap());
assert!(matches!(
extract_header_request_id(&headers),
Err(Error::InvalidHeader { .. })
));
}
#[test]
fn request_entry_point_delegates_to_headers() {
let request = Request::builder()
.header("request-id", "fallback")
.body(())
.unwrap();
assert_eq!(extract_request_request_id(&request), Ok(Some("fallback")));
}
}