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
16#[derive(Clone, Copy)]
21pub struct TransportFailure<E> {
22 phase: DeliveryPhase,
23 error: E,
24}
25
26impl<E> TransportFailure<E> {
27 #[must_use]
29 pub const fn not_sent(error: E) -> Self {
30 Self {
31 phase: DeliveryPhase::NotSent,
32 error,
33 }
34 }
35
36 #[must_use]
38 pub const fn possibly_sent(error: E) -> Self {
39 Self {
40 phase: DeliveryPhase::PossiblySent,
41 error,
42 }
43 }
44
45 #[must_use]
47 pub const fn response_started(error: E) -> Self {
48 Self {
49 phase: DeliveryPhase::ResponseStarted,
50 error,
51 }
52 }
53
54 #[must_use]
56 pub const fn unknown(error: E) -> Self {
57 Self::possibly_sent(error)
58 }
59
60 #[must_use]
62 pub const fn phase(&self) -> DeliveryPhase {
63 self.phase
64 }
65
66 #[must_use]
68 pub const fn error(&self) -> &E {
69 &self.error
70 }
71
72 #[must_use]
74 pub fn into_error(self) -> E {
75 self.error
76 }
77
78 #[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}