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 = ArcMutatingFunction<HttpResponseInterceptorContext, HttpResult<()>>;
29
30/// Ordered response interceptor list with unified application behavior.
31#[derive(Debug, Clone, Default)]
32pub struct HttpResponseInterceptors {
33 interceptors: Vec<HttpResponseInterceptor>,
34}
35
36impl HttpResponseInterceptors {
37 /// Creates an empty response interceptor list.
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 /// Appends one response interceptor.
43 pub fn push(&mut self, interceptor: HttpResponseInterceptor) {
44 self.interceptors.push(interceptor);
45 }
46
47 /// Removes all response interceptors.
48 pub fn clear(&mut self) {
49 self.interceptors.clear();
50 }
51
52 /// Applies response interceptors in insertion order.
53 ///
54 /// # Parameters
55 /// - `response_meta`: Response metadata to expose and update.
56 ///
57 /// # Returns
58 /// `Ok(())` when all interceptors accept the response.
59 ///
60 /// # Errors
61 /// Returns the first interceptor error and enriches it with
62 /// status/method/URL context when missing.
63 pub fn apply(&self, response_meta: &mut HttpResponseMeta) -> HttpResult<()> {
64 let mut context = HttpResponseInterceptorContext::from_meta(response_meta);
65 for interceptor in &self.interceptors {
66 interceptor.apply(&mut context).map_err(|error| {
67 let mut mapped = error;
68 if mapped.status.is_none() {
69 mapped = mapped.with_status(context.status());
70 }
71 if mapped.method.is_none() {
72 mapped = mapped.with_method(context.method());
73 }
74 if mapped.url.is_none() {
75 mapped = mapped.with_url(context.url());
76 }
77 mapped
78 })?;
79 }
80 context.apply_to_meta(response_meta);
81 Ok(())
82 }
83}