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