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