mockforge_http/middleware/
production_headers.rs1use axum::{
7 body::Body,
8 extract::State,
9 http::{HeaderName, HeaderValue, Request},
10 middleware::Next,
11 response::Response,
12};
13use tracing::debug;
14use uuid::Uuid;
15
16use crate::HttpServerState;
17
18pub async fn production_headers_middleware(
26 State(state): State<HttpServerState>,
27 req: Request<Body>,
28 next: Next,
29) -> Response<Body> {
30 let mut response = next.run(req).await;
32
33 if let Some(headers) = &state.production_headers {
35 for (key, value) in headers.iter() {
36 let expanded_value = expand_templates(value);
38
39 if let (Ok(header_name), Ok(header_value)) =
41 (key.parse::<HeaderName>(), expanded_value.parse::<HeaderValue>())
42 {
43 if !response.headers().contains_key(&header_name) {
45 response.headers_mut().insert(header_name, header_value);
46 debug!("Added production header: {} = {}", key, expanded_value);
47 }
48 } else {
49 tracing::warn!("Failed to parse production header: {} = {}", key, expanded_value);
50 }
51 }
52 }
53
54 response
55}
56
57fn expand_templates(value: &str) -> String {
64 let mut result = value.to_string();
65
66 if result.contains("{{uuid}}") {
68 let uuid = Uuid::new_v4().to_string();
69 result = result.replace("{{uuid}}", &uuid);
70 }
71
72 if result.contains("{{now}}") {
74 let now = chrono::Utc::now().to_rfc3339();
75 result = result.replace("{{now}}", &now);
76 }
77
78 if result.contains("{{timestamp}}") {
80 let timestamp = chrono::Utc::now().timestamp().to_string();
81 result = result.replace("{{timestamp}}", ×tamp);
82 }
83
84 result
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn test_expand_uuid_template() {
93 let value = "{{uuid}}";
94 let expanded = expand_templates(value);
95 assert_eq!(expanded.len(), 36);
97 assert!(!expanded.contains("{{uuid}}"));
98 }
99
100 #[test]
101 fn test_expand_now_template() {
102 let value = "{{now}}";
103 let expanded = expand_templates(value);
104 assert!(expanded.len() > 15);
106 assert!(!expanded.contains("{{now}}"));
107 assert!(expanded.contains('T'));
109 }
110
111 #[test]
112 fn test_expand_timestamp_template() {
113 let value = "{{timestamp}}";
114 let expanded = expand_templates(value);
115 assert!(expanded.parse::<i64>().is_ok());
117 assert!(!expanded.contains("{{timestamp}}"));
118 }
119
120 #[test]
121 fn test_expand_multiple_templates() {
122 let value = "Request-{{uuid}} at {{timestamp}}";
123 let expanded = expand_templates(value);
124 assert!(!expanded.contains("{{uuid}}"));
125 assert!(!expanded.contains("{{timestamp}}"));
126 assert!(expanded.starts_with("Request-"));
127 }
128
129 #[test]
130 fn test_no_templates() {
131 let value = "Static header value";
132 let expanded = expand_templates(value);
133 assert_eq!(expanded, value);
134 }
135}