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    /// Transforms the payload-free adapter error while preserving delivery state.
79    #[must_use]
80    pub fn map<F>(self, transform: impl FnOnce(E) -> F) -> TransportFailure<F> {
81        TransportFailure {
82            phase: self.phase,
83            error: transform(self.error),
84        }
85    }
86}
87
88impl<E: PartialEq> PartialEq for TransportFailure<E> {
89    fn eq(&self, other: &Self) -> bool {
90        self.phase == other.phase && self.error == other.error
91    }
92}
93
94impl<E: Eq> Eq for TransportFailure<E> {}
95
96impl<E> fmt::Debug for TransportFailure<E> {
97    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
98        formatter
99            .debug_struct("TransportFailure")
100            .field("phase", &self.phase)
101            .field("error", &"[redacted]")
102            .finish()
103    }
104}
105
106impl<E> fmt::Display for TransportFailure<E> {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self.phase {
109            DeliveryPhase::NotSent => formatter.write_str("transport failed before delivery"),
110            DeliveryPhase::PossiblySent => {
111                formatter.write_str("transport failed with uncertain delivery")
112            }
113            DeliveryPhase::ResponseStarted => {
114                formatter.write_str("transport failed after response start")
115            }
116        }
117    }
118}
119
120impl<E> core::error::Error for TransportFailure<E> {}
121
122#[cfg(test)]
123mod tests {
124    use core::fmt::{self, Write};
125
126    use super::{DeliveryPhase, TransportFailure};
127
128    struct FixedText {
129        bytes: [u8; 96],
130        len: usize,
131    }
132
133    impl FixedText {
134        const fn new() -> Self {
135            Self {
136                bytes: [0_u8; 96],
137                len: 0,
138            }
139        }
140
141        fn as_str(&self) -> &str {
142            core::str::from_utf8(self.bytes.get(..self.len).unwrap_or_default()).unwrap_or_default()
143        }
144    }
145
146    impl Write for FixedText {
147        fn write_str(&mut self, value: &str) -> fmt::Result {
148            let end = self.len.checked_add(value.len()).ok_or(fmt::Error)?;
149            let output = self.bytes.get_mut(self.len..end).ok_or(fmt::Error)?;
150            output.copy_from_slice(value.as_bytes());
151            self.len = end;
152            Ok(())
153        }
154    }
155
156    #[test]
157    fn unknown_delivery_fails_closed_and_debug_redacts_the_error() {
158        let failure = TransportFailure::unknown("secret payload");
159        assert_eq!(failure.phase(), DeliveryPhase::PossiblySent);
160        let mut debug = FixedText::new();
161        assert!(write!(&mut debug, "{failure:?}").is_ok());
162        assert!(debug.as_str().contains("PossiblySent"));
163        assert!(!debug.as_str().contains("secret payload"));
164    }
165}