1use crate::{HeaderName, Request, utils::HeaderValueGetter};
7use rama_core::error::BoxErrorExt as _;
8use rama_core::{
9 Layer, Service,
10 error::{BoxError, ErrorContext as _, ErrorExt},
11 extensions::{Extension, ExtensionsRef},
12 telemetry::tracing,
13};
14use rama_utils::macros::define_inner_service_accessors;
15use std::{fmt, marker::PhantomData};
16
17pub struct HeaderOptionValueService<T, S> {
21 inner: S,
22 header_name: HeaderName,
23 optional: bool,
24 _marker: PhantomData<fn() -> T>,
25}
26
27impl<T, S> HeaderOptionValueService<T, S> {
28 pub const fn new(inner: S, header_name: HeaderName, optional: bool) -> Self {
33 Self {
34 inner,
35 header_name,
36 optional,
37 _marker: PhantomData,
38 }
39 }
40
41 define_inner_service_accessors!();
42
43 pub const fn required(inner: S, header_name: HeaderName) -> Self {
47 Self::new(inner, header_name, false)
48 }
49
50 pub const fn optional(inner: S, header_name: HeaderName) -> Self {
54 Self::new(inner, header_name, true)
55 }
56}
57
58impl<T, S: fmt::Debug> fmt::Debug for HeaderOptionValueService<T, S> {
59 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 f.debug_struct("HeaderOptionValueService")
61 .field("inner", &self.inner)
62 .field("header_name", &self.header_name)
63 .field("optional", &self.optional)
64 .field(
65 "_marker",
66 &format_args!("{}", std::any::type_name::<fn() -> T>()),
67 )
68 .finish()
69 }
70}
71
72impl<T, S> Clone for HeaderOptionValueService<T, S>
73where
74 S: Clone,
75{
76 fn clone(&self) -> Self {
77 Self {
78 inner: self.inner.clone(),
79 header_name: self.header_name.clone(),
80 optional: self.optional,
81 _marker: PhantomData,
82 }
83 }
84}
85
86impl<T, S, Body, E> Service<Request<Body>> for HeaderOptionValueService<T, S>
87where
88 S: Service<Request<Body>, Error = E>,
89 T: Default + Extension,
90 Body: Send + Sync + 'static,
91 E: Into<BoxError> + Send + Sync + 'static,
92{
93 type Output = S::Output;
94 type Error = BoxError;
95
96 async fn serve(&self, request: Request<Body>) -> Result<Self::Output, Self::Error> {
97 match request.header_str(&self.header_name) {
98 Ok(str_value) => {
99 let str_value = str_value.trim();
100 if str_value == "1" || str_value.eq_ignore_ascii_case("true") {
101 request.extensions().insert(T::default());
102 } else if str_value != "0" && !str_value.eq_ignore_ascii_case("false") {
103 return Err(BoxError::from_static_str("invalid header option")
104 .context_field("header_name", self.header_name.clone())
105 .context_str_field("header_value", str_value));
106 }
107 }
108 Err(err) => {
109 if self.optional && matches!(err, crate::utils::HeaderValueErr::HeaderMissing(_)) {
110 tracing::debug!(
111 http.header.name = %self.header_name,
112 "failed to determine header option: {err:?}",
113 );
114 return self.inner.serve(request).await.into_box_error();
115 } else {
116 return Err(err
117 .context("determine header option")
118 .context_field("header_name", self.header_name.clone()));
119 }
120 }
121 };
122 self.inner.serve(request).await.into_box_error()
123 }
124}
125
126pub struct HeaderOptionValueLayer<T> {
130 header_name: HeaderName,
131 optional: bool,
132 _marker: PhantomData<fn() -> T>,
133}
134
135impl<T> fmt::Debug for HeaderOptionValueLayer<T> {
136 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137 f.debug_struct("HeaderOptionValueLayer")
138 .field("header_name", &self.header_name)
139 .field("optional", &self.optional)
140 .field(
141 "_marker",
142 &format_args!("{}", std::any::type_name::<fn() -> T>()),
143 )
144 .finish()
145 }
146}
147
148impl<T> Clone for HeaderOptionValueLayer<T> {
149 fn clone(&self) -> Self {
150 Self {
151 header_name: self.header_name.clone(),
152 optional: self.optional,
153 _marker: PhantomData,
154 }
155 }
156}
157
158impl<T> HeaderOptionValueLayer<T> {
159 pub fn required(header_name: HeaderName) -> Self {
163 Self {
164 header_name,
165 optional: false,
166 _marker: PhantomData,
167 }
168 }
169
170 pub fn optional(header_name: HeaderName) -> Self {
174 Self {
175 header_name,
176 optional: true,
177 _marker: PhantomData,
178 }
179 }
180}
181
182impl<T, S> Layer<S> for HeaderOptionValueLayer<T> {
183 type Service = HeaderOptionValueService<T, S>;
184
185 fn layer(&self, inner: S) -> Self::Service {
186 HeaderOptionValueService::new(inner, self.header_name.clone(), self.optional)
187 }
188
189 fn into_layer(self, inner: S) -> Self::Service {
190 HeaderOptionValueService::new(inner, self.header_name, self.optional)
191 }
192}
193
194#[cfg(test)]
195mod test {
196 use rama_core::extensions::{Extension, ExtensionsRef};
197
198 use super::*;
199 use crate::Method;
200
201 #[derive(Debug, Clone, Default, Extension)]
202 struct UnitValue;
203
204 #[tokio::test]
205 async fn test_header_option_value_required_happy_path() {
206 let test_cases = [
207 ("1", true),
208 ("true", true),
209 ("True", true),
210 ("TrUE", true),
211 ("TRUE", true),
212 ("0", false),
213 ("false", false),
214 ("False", false),
215 ("FaLsE", false),
216 ("FALSE", false),
217 ];
218 for (str_value, expected_output) in test_cases {
219 let request = Request::builder()
220 .method(Method::GET)
221 .uri("https://www.example.com")
222 .header("x-unit-value", str_value)
223 .body(())
224 .unwrap();
225
226 let inner_service =
227 rama_core::service::service_fn(move |req: Request<()>| async move {
228 assert_eq!(expected_output, req.extensions().contains::<UnitValue>());
229 Ok::<_, std::convert::Infallible>(())
230 });
231
232 let service = HeaderOptionValueService::<UnitValue, _>::required(
233 inner_service,
234 HeaderName::from_static("x-unit-value"),
235 );
236
237 service.serve(request).await.unwrap();
238 }
239 }
240
241 #[tokio::test]
242 async fn test_header_option_value_optional_found() {
243 let test_cases = [
244 ("1", true),
245 ("true", true),
246 ("True", true),
247 ("TrUE", true),
248 ("TRUE", true),
249 ("0", false),
250 ("false", false),
251 ("False", false),
252 ("FaLsE", false),
253 ("FALSE", false),
254 ];
255 for (str_value, expected_output) in test_cases {
256 let request = Request::builder()
257 .method(Method::GET)
258 .uri("https://www.example.com")
259 .header("x-unit-value", str_value)
260 .body(())
261 .unwrap();
262
263 let inner_service =
264 rama_core::service::service_fn(move |req: Request<()>| async move {
265 assert_eq!(expected_output, req.extensions().contains::<UnitValue>());
266 Ok::<_, std::convert::Infallible>(())
267 });
268
269 let service = HeaderOptionValueService::<UnitValue, _>::optional(
270 inner_service,
271 HeaderName::from_static("x-unit-value"),
272 );
273
274 service.serve(request).await.unwrap();
275 }
276 }
277
278 #[tokio::test]
279 async fn test_header_option_value_optional_missing() {
280 let request = Request::builder()
281 .method(Method::GET)
282 .uri("https://www.example.com")
283 .body(())
284 .unwrap();
285
286 let inner_service = rama_core::service::service_fn(async |req: Request<()>| {
287 assert!(!req.extensions().contains::<UnitValue>());
288
289 Ok::<_, std::convert::Infallible>(())
290 });
291
292 let service = HeaderOptionValueService::<UnitValue, _>::optional(
293 inner_service,
294 HeaderName::from_static("x-unit-value"),
295 );
296
297 service.serve(request).await.unwrap();
298 }
299
300 #[tokio::test]
301 async fn test_header_option_value_required_missing_header() {
302 let request = Request::builder()
303 .method(Method::GET)
304 .uri("https://www.example.com")
305 .body(())
306 .unwrap();
307
308 let inner_service = rama_core::service::service_fn(async |_: Request<()>| {
309 Ok::<_, std::convert::Infallible>(())
310 });
311
312 let service = HeaderOptionValueService::<UnitValue, _>::required(
313 inner_service,
314 HeaderName::from_static("x-unit-value"),
315 );
316
317 let result = service.serve(request).await;
318 assert!(result.is_err());
319 }
320
321 #[tokio::test]
322 async fn test_header_option_value_required_invalid_value() {
323 let test_cases = ["", "foo", "yes"];
324
325 for test_case in test_cases {
326 let request = Request::builder()
327 .method(Method::GET)
328 .uri("https://www.example.com")
329 .header("x-unit-value", test_case)
330 .body(())
331 .unwrap();
332
333 let inner_service = rama_core::service::service_fn(async |_: Request<()>| {
334 Ok::<_, std::convert::Infallible>(())
335 });
336
337 let service = HeaderOptionValueService::<UnitValue, _>::required(
338 inner_service,
339 HeaderName::from_static("x-unit-value"),
340 );
341
342 let result = service.serve(request).await;
343 assert!(result.is_err());
344 }
345 }
346
347 #[tokio::test]
348 async fn test_header_option_value_optional_invalid_value() {
349 let test_cases = ["", "foo", "yes"];
350
351 for test_case in test_cases {
352 let request = Request::builder()
353 .method(Method::GET)
354 .uri("https://www.example.com")
355 .header("x-unit-value", test_case)
356 .body(())
357 .unwrap();
358
359 let inner_service = rama_core::service::service_fn(async |_: Request<()>| {
360 Ok::<_, std::convert::Infallible>(())
361 });
362
363 let service = HeaderOptionValueService::<UnitValue, _>::optional(
364 inner_service,
365 HeaderName::from_static("x-unit-value"),
366 );
367
368 let result = service.serve(request).await;
369 assert!(result.is_err());
370 }
371 }
372}