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::HttpResponseMeta;
18use crate::HttpResult;
19
20/// Response interceptor function used to inspect/mutate response metadata
21/// (`status`, `headers`, `url`) before the response is returned to callers.
22///
23/// Returning `Err` short-circuits execution for the current attempt.
24pub type HttpResponseInterceptor = ArcMutatingFunction<HttpResponseMeta, HttpResult<()>>;
25
26/// Ordered response interceptor list with unified application behavior.
27#[derive(Debug, Clone, Default)]
28pub struct HttpResponseInterceptors {
29 interceptors: Vec<HttpResponseInterceptor>,
30}
31
32impl HttpResponseInterceptors {
33 /// Creates an empty response interceptor list.
34 pub fn new() -> Self {
35 Self::default()
36 }
37
38 /// Appends one response interceptor.
39 pub fn push(&mut self, interceptor: HttpResponseInterceptor) {
40 self.interceptors.push(interceptor);
41 }
42
43 /// Removes all response interceptors.
44 pub fn clear(&mut self) {
45 self.interceptors.clear();
46 }
47
48 /// Applies response interceptors in insertion order.
49 ///
50 /// # Parameters
51 /// - `response_meta`: Response metadata.
52 ///
53 /// # Returns
54 /// `Ok(())` when all interceptors accept the response.
55 ///
56 /// # Errors
57 /// Returns the first interceptor error and enriches it with
58 /// status/method/URL context when missing.
59 pub fn apply(&self, response_meta: &mut HttpResponseMeta) -> HttpResult<()> {
60 for interceptor in &self.interceptors {
61 interceptor.apply(response_meta).map_err(|error| {
62 let mut mapped = error;
63 if mapped.status.is_none() {
64 mapped = mapped.with_status(response_meta.status);
65 }
66 if mapped.method.is_none() {
67 mapped = mapped.with_method(&response_meta.method);
68 }
69 if mapped.url.is_none() {
70 mapped = mapped.with_url(&response_meta.url);
71 }
72 mapped
73 })?;
74 }
75 Ok(())
76 }
77}