rig_http/http_client/middleware.rs
1//! Transport-boundary middleware for [`DynHttpClient`](super::DynHttpClient).
2//! Attach hooks with [`with_middleware`](super::DynHttpClient::with_middleware).
3//!
4//! ```
5//! use rig_http::http_client::middleware::HttpMiddleware;
6//!
7//! struct PassThrough;
8//! impl HttpMiddleware for PassThrough {}
9//! ```
10
11use bytes::Bytes;
12use http::{Extensions, HeaderMap, Method, StatusCode, Uri};
13
14use super::Result;
15use crate::wasm_compat::{WasmBoxedFuture, WasmCompatSend, WasmCompatSync};
16
17/// The request a hook runs for: its method, URI and extensions, which carry
18/// what the caller attached to it, such as the model call a request serves
19/// (rig-core's `serve::Serving`). Built by
20/// [`DynHttpClient`](super::DynHttpClient); a field added later breaks no
21/// hook.
22#[derive(Clone, Copy, Debug)]
23#[non_exhaustive]
24pub struct Exchange<'a> {
25 /// The request's method.
26 pub method: &'a Method,
27 /// The request's URI.
28 pub uri: &'a Uri,
29 /// The request's extensions, as the transport receives them.
30 pub extensions: &'a Extensions,
31}
32
33/// Transport-boundary hooks applied by
34/// [`DynHttpClient`](super::DynHttpClient) around every request.
35///
36/// Methods default to no-ops. All header hooks run in attachment order, then
37/// all body hooks run in that order with the final headers and preceding body
38/// replacements. Response hooks run in attachment order on every reply,
39/// before streaming-body consumption: a response, or a non-success reply a
40/// transport turned into an error that keeps its status and headers, as
41/// `rig-reqwest` does. Multipart requests skip body hooks.
42///
43/// Any hook error fails the request; request-side errors abort before sending.
44/// An error reply fails with its own error whatever its hooks return.
45/// Response hooks cannot modify responses. Hooks run on the request future and
46/// must not block.
47pub trait HttpMiddleware: WasmCompatSend + WasmCompatSync {
48 /// Mutate the outgoing request headers in place.
49 ///
50 /// Runs before [`before_request_body`](Self::before_request_body). An
51 /// error aborts the request before it is sent.
52 fn before_request_headers<'a>(
53 &'a self,
54 _exchange: Exchange<'a>,
55 _headers: &'a mut HeaderMap,
56 ) -> WasmBoxedFuture<'a, Result<()>> {
57 Box::pin(async { Ok(()) })
58 }
59
60 /// Observe the serialized request body, returning the body to send.
61 ///
62 /// Return `body` unchanged to pass through, or a replacement to rewrite
63 /// the payload. `headers` reflects every middleware's header mutations.
64 /// Not invoked for multipart requests. An error aborts the request before
65 /// it is sent.
66 fn before_request_body<'a>(
67 &'a self,
68 _exchange: Exchange<'a>,
69 _headers: &'a HeaderMap,
70 body: Bytes,
71 ) -> WasmBoxedFuture<'a, Result<Bytes>> {
72 Box::pin(async move { Ok(body) })
73 }
74
75 /// Observe the response status and headers as soon as they arrive,
76 /// including a non-success reply's (such as a 429's rate-limit headers)
77 /// on a transport that reports it as an error. Not invoked when the
78 /// request failed with no reply.
79 ///
80 /// For streaming responses this runs before any of the body stream is
81 /// consumed. Observe-only: the response cannot be modified, but returning
82 /// an error fails the request with that error.
83 fn after_response<'a>(
84 &'a self,
85 _exchange: Exchange<'a>,
86 _status: StatusCode,
87 _headers: &'a HeaderMap,
88 ) -> WasmBoxedFuture<'a, Result<()>> {
89 Box::pin(async { Ok(()) })
90 }
91}