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