Skip to main content

cloud_sdk/transport/
delivery.rs

1//! Conservative transport-delivery classification.
2
3use core::fmt;
4
5/// Furthest delivery point known for one failed HTTP attempt.
6#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub enum DeliveryPhase {
8    /// Validation or connection establishment failed before request delivery.
9    NotSent,
10    /// Some request bytes may have reached the peer.
11    PossiblySent,
12    /// Any informational or final response head was observed from the peer.
13    ResponseStarted,
14}
15
16/// Payload-redacting transport failure with an explicit delivery phase.
17///
18/// Unknown send state must use [`Self::unknown`], which conservatively maps to
19/// [`DeliveryPhase::PossiblySent`].
20#[derive(Clone, Copy)]
21pub struct TransportFailure<E> {
22    phase: DeliveryPhase,
23    error: E,
24}
25
26impl<E> TransportFailure<E> {
27    /// Records a failure proven to have happened before request delivery.
28    #[must_use]
29    pub const fn not_sent(error: E) -> Self {
30        Self {
31            phase: DeliveryPhase::NotSent,
32            error,
33        }
34    }
35
36    /// Records a failure after request delivery may have begun.
37    #[must_use]
38    pub const fn possibly_sent(error: E) -> Self {
39        Self {
40            phase: DeliveryPhase::PossiblySent,
41            error,
42        }
43    }
44
45    /// Records a failure after any response head was observed.
46    #[must_use]
47    pub const fn response_started(error: E) -> Self {
48        Self {
49            phase: DeliveryPhase::ResponseStarted,
50            error,
51        }
52    }
53
54    /// Conservatively records an error whose delivery state is unknown.
55    #[must_use]
56    pub const fn unknown(error: E) -> Self {
57        Self::possibly_sent(error)
58    }
59
60    /// Returns the conservative delivery phase.
61    #[must_use]
62    pub const fn phase(&self) -> DeliveryPhase {
63        self.phase
64    }
65
66    /// Returns a shared view of the payload-free adapter error.
67    #[must_use]
68    pub const fn error(&self) -> &E {
69        &self.error
70    }
71
72    /// Consumes the failure and returns its adapter error.
73    #[must_use]
74    pub fn into_error(self) -> E {
75        self.error
76    }
77}
78
79impl<E: PartialEq> PartialEq for TransportFailure<E> {
80    fn eq(&self, other: &Self) -> bool {
81        self.phase == other.phase && self.error == other.error
82    }
83}
84
85impl<E: Eq> Eq for TransportFailure<E> {}
86
87impl<E> fmt::Debug for TransportFailure<E> {
88    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
89        formatter
90            .debug_struct("TransportFailure")
91            .field("phase", &self.phase)
92            .field("error", &"[redacted]")
93            .finish()
94    }
95}
96
97impl<E> fmt::Display for TransportFailure<E> {
98    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self.phase {
100            DeliveryPhase::NotSent => formatter.write_str("transport failed before delivery"),
101            DeliveryPhase::PossiblySent => {
102                formatter.write_str("transport failed with uncertain delivery")
103            }
104            DeliveryPhase::ResponseStarted => {
105                formatter.write_str("transport failed after response start")
106            }
107        }
108    }
109}
110
111impl<E> core::error::Error for TransportFailure<E> {}
112
113#[cfg(test)]
114mod tests {
115    use core::fmt::{self, Write};
116
117    use super::{DeliveryPhase, TransportFailure};
118
119    struct FixedText {
120        bytes: [u8; 96],
121        len: usize,
122    }
123
124    impl FixedText {
125        const fn new() -> Self {
126            Self {
127                bytes: [0_u8; 96],
128                len: 0,
129            }
130        }
131
132        fn as_str(&self) -> &str {
133            core::str::from_utf8(self.bytes.get(..self.len).unwrap_or_default()).unwrap_or_default()
134        }
135    }
136
137    impl Write for FixedText {
138        fn write_str(&mut self, value: &str) -> fmt::Result {
139            let end = self.len.checked_add(value.len()).ok_or(fmt::Error)?;
140            let output = self.bytes.get_mut(self.len..end).ok_or(fmt::Error)?;
141            output.copy_from_slice(value.as_bytes());
142            self.len = end;
143            Ok(())
144        }
145    }
146
147    #[test]
148    fn unknown_delivery_fails_closed_and_debug_redacts_the_error() {
149        let failure = TransportFailure::unknown("secret payload");
150        assert_eq!(failure.phase(), DeliveryPhase::PossiblySent);
151        let mut debug = FixedText::new();
152        assert!(write!(&mut debug, "{failure:?}").is_ok());
153        assert!(debug.as_str().contains("PossiblySent"));
154        assert!(!debug.as_str().contains("secret payload"));
155    }
156}