cloud_sdk/transport/
delivery.rs1use core::fmt;
4
5#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub enum DeliveryPhase {
8 NotSent,
10 PossiblySent,
12 ResponseStarted,
14}
15
16pub trait DeliveryClassified {
18 fn delivery_phase(&self) -> DeliveryPhase;
20}
21
22#[derive(Clone, Copy)]
27pub struct TransportFailure<E> {
28 phase: DeliveryPhase,
29 error: E,
30}
31
32impl<E> TransportFailure<E> {
33 #[must_use]
35 pub const fn not_sent(error: E) -> Self {
36 Self {
37 phase: DeliveryPhase::NotSent,
38 error,
39 }
40 }
41
42 #[must_use]
44 pub const fn possibly_sent(error: E) -> Self {
45 Self {
46 phase: DeliveryPhase::PossiblySent,
47 error,
48 }
49 }
50
51 #[must_use]
53 pub const fn response_started(error: E) -> Self {
54 Self {
55 phase: DeliveryPhase::ResponseStarted,
56 error,
57 }
58 }
59
60 #[must_use]
62 pub const fn unknown(error: E) -> Self {
63 Self::possibly_sent(error)
64 }
65
66 #[must_use]
68 pub const fn phase(&self) -> DeliveryPhase {
69 self.phase
70 }
71
72 #[must_use]
74 pub const fn error(&self) -> &E {
75 &self.error
76 }
77
78 #[must_use]
80 pub fn into_error(self) -> E {
81 self.error
82 }
83
84 #[must_use]
86 pub fn map<F>(self, transform: impl FnOnce(E) -> F) -> TransportFailure<F> {
87 TransportFailure {
88 phase: self.phase,
89 error: transform(self.error),
90 }
91 }
92}
93
94impl<E> DeliveryClassified for TransportFailure<E> {
95 fn delivery_phase(&self) -> DeliveryPhase {
96 self.phase
97 }
98}
99
100impl<E: PartialEq> PartialEq for TransportFailure<E> {
101 fn eq(&self, other: &Self) -> bool {
102 self.phase == other.phase && self.error == other.error
103 }
104}
105
106impl<E: Eq> Eq for TransportFailure<E> {}
107
108impl<E> fmt::Debug for TransportFailure<E> {
109 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110 formatter
111 .debug_struct("TransportFailure")
112 .field("phase", &self.phase)
113 .field("error", &"[redacted]")
114 .finish()
115 }
116}
117
118impl<E> fmt::Display for TransportFailure<E> {
119 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
120 match self.phase {
121 DeliveryPhase::NotSent => formatter.write_str("transport failed before delivery"),
122 DeliveryPhase::PossiblySent => {
123 formatter.write_str("transport failed with uncertain delivery")
124 }
125 DeliveryPhase::ResponseStarted => {
126 formatter.write_str("transport failed after response start")
127 }
128 }
129 }
130}
131
132impl<E> core::error::Error for TransportFailure<E> {}
133
134#[cfg(test)]
135mod tests {
136 use core::fmt::{self, Write};
137
138 use super::{DeliveryPhase, TransportFailure};
139
140 struct FixedText {
141 bytes: [u8; 96],
142 len: usize,
143 }
144
145 impl FixedText {
146 const fn new() -> Self {
147 Self {
148 bytes: [0_u8; 96],
149 len: 0,
150 }
151 }
152
153 fn as_str(&self) -> &str {
154 core::str::from_utf8(self.bytes.get(..self.len).unwrap_or_default()).unwrap_or_default()
155 }
156 }
157
158 impl Write for FixedText {
159 fn write_str(&mut self, value: &str) -> fmt::Result {
160 let end = self.len.checked_add(value.len()).ok_or(fmt::Error)?;
161 let output = self.bytes.get_mut(self.len..end).ok_or(fmt::Error)?;
162 output.copy_from_slice(value.as_bytes());
163 self.len = end;
164 Ok(())
165 }
166 }
167
168 #[test]
169 fn unknown_delivery_fails_closed_and_debug_redacts_the_error() {
170 let failure = TransportFailure::unknown("secret payload");
171 assert_eq!(failure.phase(), DeliveryPhase::PossiblySent);
172 let mut debug = FixedText::new();
173 assert!(write!(&mut debug, "{failure:?}").is_ok());
174 assert!(debug.as_str().contains("PossiblySent"));
175 assert!(!debug.as_str().contains("secret payload"));
176 }
177}