qubit_http/response/http_response_interceptors.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Response interceptor abstraction for successful HTTP responses.
11
12use qubit_function::{
13 ArcMutatingFunction,
14 MutatingFunction,
15};
16
17use super::HttpResponseInterceptorContext;
18use super::HttpResponseMeta;
19use crate::HttpResult;
20
21/// Response interceptor function used to inspect/mutate response metadata
22/// before the response is returned to callers.
23///
24/// The interceptor receives [`HttpResponseInterceptorContext`], where status
25/// and request method are immutable and headers/final URL are mutable.
26///
27/// Returning `Err` short-circuits execution for the current attempt.
28pub type HttpResponseInterceptor =
29 ArcMutatingFunction<HttpResponseInterceptorContext, HttpResult<()>>;
30
31/// Ordered response interceptor list with unified application behavior.
32#[derive(Debug, Clone, Default)]
33pub struct HttpResponseInterceptors {
34 interceptors: Vec<HttpResponseInterceptor>,
35}
36
37impl HttpResponseInterceptors {
38 /// Creates an empty response interceptor list.
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 /// Appends one response interceptor.
44 pub fn push(&mut self, interceptor: HttpResponseInterceptor) {
45 self.interceptors.push(interceptor);
46 }
47
48 /// Removes all response interceptors.
49 pub fn clear(&mut self) {
50 self.interceptors.clear();
51 }
52
53 /// Applies response interceptors in insertion order.
54 ///
55 /// # Parameters
56 /// - `response_meta`: Response metadata to expose and update.
57 ///
58 /// # Returns
59 /// `Ok(())` when all interceptors accept the response.
60 ///
61 /// # Errors
62 /// Returns the first interceptor error and enriches it with
63 /// status/method/URL context when missing.
64 pub fn apply(&self, response_meta: &mut HttpResponseMeta) -> HttpResult<()> {
65 let mut context = HttpResponseInterceptorContext::from_meta(response_meta);
66 for interceptor in &self.interceptors {
67 interceptor.apply(&mut context).map_err(|error| {
68 let mut mapped = error;
69 if mapped.status.is_none() {
70 mapped = mapped.with_status(context.status());
71 }
72 if mapped.method.is_none() {
73 mapped = mapped.with_method(context.method());
74 }
75 if mapped.url.is_none() {
76 mapped = mapped.with_url(context.url());
77 }
78 mapped
79 })?;
80 }
81 context.apply_to_meta(response_meta);
82 Ok(())
83 }
84}