Skip to main content

base_http/
interim.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! Per-exchange interim-response hooks, independent of message heads and bodies.
5//!
6//! [`InterimResponses`] supplies per-exchange send and receive hooks using the same [`ResponseMetadata`] as final responses. Its handler can be a protocol writer or an async observer. It validates interim status codes, excludes final `101`, and stops delivery after `finish` or a failed or abandoned started send. Drivers call `finish` when committing or receiving the final metadata.
7//!
8//! ```rust
9//! use base_http::{InterimResponses, ResponseMetadata, Status};
10//! use base_scheduler::Cancellation;
11//! use std::convert::Infallible;
12//!
13//! let mut interim = InterimResponses::new(|head: ResponseMetadata| async move {
14//!     assert_eq!(head.status, Status::EARLY_HINTS);
15//!     Ok::<_, Infallible>(())
16//! });
17//! futures_lite::future::block_on(interim.send_interim_response(
18//!     ResponseMetadata::new(Status::EARLY_HINTS), &Cancellation::never(),
19//! ))?;
20//! interim.finish();
21//! # Ok::<(), base_http::InterimError<Infallible>>(())
22//! ```
23use crate::{ResponseMetadata, Status, future::cancellable};
24use base_scheduler::Cancellation;
25use std::{error::Error, fmt, future::Future};
26
27/// A protocol writer or client observer for an interim response head.
28pub trait InterimHandler: Send {
29    type Error: Error + Send + Sync + 'static;
30    fn on_interim_response(
31        &mut self,
32        response: ResponseMetadata,
33        cancellation: &Cancellation,
34    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
35}
36
37impl<F, Fut, E> InterimHandler for F
38where
39    F: FnMut(ResponseMetadata) -> Fut + Send,
40    Fut: Future<Output = Result<(), E>> + Send,
41    E: Error + Send + Sync + 'static,
42{
43    type Error = E;
44    async fn on_interim_response(
45        &mut self,
46        response: ResponseMetadata,
47        _: &Cancellation,
48    ) -> Result<(), E> {
49        self(response).await
50    }
51}
52
53/// An interim response failure. `Handler` is transparent: it displays the handler's error and reports that error's source.
54#[derive(Debug)]
55pub enum InterimError<E> {
56    InvalidStatus(Status),
57    Finished,
58    Cancelled,
59    Handler(E),
60}
61
62impl<E: fmt::Display> fmt::Display for InterimError<E> {
63    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            Self::InvalidStatus(status) => write!(formatter, "{status} is not an interim response"),
66            Self::Finished => formatter.write_str("interim responses have finished"),
67            Self::Cancelled => formatter.write_str("interim response cancelled"),
68            Self::Handler(error) => error.fmt(formatter),
69        }
70    }
71}
72impl<E: Error + 'static> Error for InterimError<E> {
73    fn source(&self) -> Option<&(dyn Error + 'static)> {
74        match self {
75            Self::Handler(error) => error.source(),
76            _ => None,
77        }
78    }
79}
80
81/// Explicit server responder/client observer state for one exchange.
82///
83/// Accepted interim heads are delivered in order without accumulating a log.
84/// `101` is a final protocol-switch response and is never sent to the hook.
85/// The driver calls `finish` when committing/receiving its final head. Failure
86/// or abandonment of a started send also ends this capability, since a handler
87/// may have made partial wire progress. Invalid heads and pre-cancellation do
88/// not consume it. The handler itself is statically dispatched.
89pub struct InterimResponses<H> {
90    handler: H,
91    finished: bool,
92}
93
94impl<H> InterimResponses<H> {
95    pub fn new(handler: H) -> Self {
96        Self {
97            handler,
98            finished: false,
99        }
100    }
101    pub fn finish(&mut self) {
102        self.finished = true;
103    }
104    pub fn is_finished(&self) -> bool {
105        self.finished
106    }
107    pub fn into_inner(self) -> H {
108        self.handler
109    }
110}
111
112impl<H: InterimHandler> InterimResponses<H> {
113    pub async fn send_interim_response(
114        &mut self,
115        response: ResponseMetadata,
116        cancellation: &Cancellation,
117    ) -> Result<(), InterimError<H::Error>> {
118        cancellation.check().map_err(|_| InterimError::Cancelled)?;
119        if self.finished {
120            return Err(InterimError::Finished);
121        }
122        if !response.status.is_informational() || response.status.is_final() {
123            return Err(InterimError::InvalidStatus(response.status));
124        }
125        self.finished = true;
126        cancellable(
127            self.handler.on_interim_response(response, cancellation),
128            cancellation,
129        )
130        .await
131        .map_err(|_| InterimError::Cancelled)?
132        .map_err(InterimError::Handler)?;
133        self.finished = false;
134        Ok(())
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use std::io;
142
143    #[test]
144    fn handler_errors_are_transparent() {
145        let error = InterimError::Handler(crate::body::BodyError::new(io::Error::other(
146            "observer failed",
147        )));
148        assert_eq!(error.to_string(), "body failed");
149        assert_eq!(error.source().unwrap().to_string(), "observer failed");
150    }
151}