aws_smithy_runtime_api/client/unit_of_work/
checksums.rs1use aws_smithy_types::config_bag::{Storable, StoreAppend};
13use std::fmt;
14use std::sync::Arc;
15
16pub trait ObserveResponseChecksum: Send + Sync + fmt::Debug {
26 fn on_event(&self, event: &ChecksumEvent<'_>);
28}
29
30#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct ChecksumEvent<'a> {
33 attempt: u32,
34 kind: ChecksumEventKind<'a>,
35}
36
37impl<'a> ChecksumEvent<'a> {
38 pub fn new(attempt: u32, kind: ChecksumEventKind<'a>) -> Self {
40 Self { attempt, kind }
41 }
42
43 pub fn attempt(&self) -> u32 {
45 self.attempt
46 }
47
48 pub fn kind(&self) -> &ChecksumEventKind<'a> {
50 &self.kind
51 }
52}
53
54#[derive(Clone, Debug, PartialEq, Eq)]
56#[non_exhaustive]
57pub enum ChecksumEventKind<'a> {
58 NotValidated(NotValidated),
60 Validating(Validating),
62 Matched(Match<'a>),
64 Mismatched(Mismatch<'a>),
67 Errored,
69 Dropped,
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct NotValidated {
76 reason: ChecksumSkip,
77}
78
79impl NotValidated {
80 pub fn new(reason: ChecksumSkip) -> Self {
82 Self { reason }
83 }
84
85 pub fn reason(&self) -> ChecksumSkip {
87 self.reason
88 }
89}
90
91#[derive(Clone, Debug, PartialEq, Eq)]
93pub struct Validating {
94 algorithm: &'static str,
95}
96
97impl Validating {
98 pub fn new(algorithm: &'static str) -> Self {
100 Self { algorithm }
101 }
102
103 pub fn algorithm(&self) -> &'static str {
106 self.algorithm
107 }
108}
109
110#[derive(Clone, Debug, PartialEq, Eq)]
112pub struct Match<'a> {
113 computed: &'a str,
114}
115
116impl<'a> Match<'a> {
117 pub fn new(computed: &'a str) -> Self {
119 Self { computed }
120 }
121
122 pub fn computed(&self) -> &'a str {
124 self.computed
125 }
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
130pub struct Mismatch<'a> {
131 expected: &'a str,
132 computed: &'a str,
133}
134
135impl<'a> Mismatch<'a> {
136 pub fn new(expected: &'a str, computed: &'a str) -> Self {
138 Self { expected, computed }
139 }
140
141 pub fn expected(&self) -> &'a str {
143 self.expected
144 }
145
146 pub fn computed(&self) -> &'a str {
148 self.computed
149 }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
154#[non_exhaustive]
155pub enum ChecksumSkip {
156 Disabled,
159 NoChecksum,
161 PartLevelChecksum,
163 InvalidChecksumHeader,
165 UnsupportedAlgorithm,
167 ErrorResponse,
169}
170
171#[derive(Clone, Debug)]
176pub struct SharedResponseChecksumObserver(Arc<dyn ObserveResponseChecksum>);
177
178impl SharedResponseChecksumObserver {
179 pub fn new(observer: impl ObserveResponseChecksum + 'static) -> Self {
181 Self(Arc::new(observer))
182 }
183}
184
185impl ObserveResponseChecksum for SharedResponseChecksumObserver {
186 fn on_event(&self, event: &ChecksumEvent<'_>) {
187 self.0.on_event(event)
188 }
189}
190
191impl Storable for SharedResponseChecksumObserver {
192 type Storer = StoreAppend<Self>;
193}
194
195#[cfg(test)]
196mod tests {
197 use super::{
198 ChecksumEvent, ChecksumEventKind, ChecksumSkip, Match, Mismatch, NotValidated,
199 ObserveResponseChecksum, SharedResponseChecksumObserver, Validating,
200 };
201 use aws_smithy_types::config_bag::{ConfigBag, Layer};
202 use std::sync::{Arc, Mutex};
203
204 #[derive(Clone, Debug, Default)]
206 struct Recorder(Arc<Mutex<Vec<String>>>);
207
208 impl Recorder {
209 fn record(&self, event: impl Into<String>) {
210 self.0.lock().unwrap().push(event.into());
211 }
212
213 fn events(&self) -> Vec<String> {
214 self.0.lock().unwrap().clone()
215 }
216 }
217
218 impl ObserveResponseChecksum for Recorder {
219 fn on_event(&self, event: &ChecksumEvent<'_>) {
220 let kind = match event.kind() {
221 ChecksumEventKind::NotValidated(data) => {
222 format!("not_validated {:?}", data.reason())
223 }
224 ChecksumEventKind::Validating(data) => format!("validating {}", data.algorithm()),
225 ChecksumEventKind::Matched(data) => format!("matched {}", data.computed()),
226 ChecksumEventKind::Mismatched(data) => {
227 format!("mismatched {} {}", data.expected(), data.computed())
228 }
229 ChecksumEventKind::Errored => "errored".to_string(),
230 ChecksumEventKind::Dropped => "dropped".to_string(),
231 };
232 self.record(format!("{} {kind}", event.attempt()));
233 }
234 }
235
236 #[test]
237 fn every_appended_observer_receives_every_event() {
238 let (first, second) = (Recorder::default(), Recorder::default());
239 let mut layer = Layer::new("test");
240 layer.store_append(SharedResponseChecksumObserver::new(first.clone()));
241 layer.store_append(SharedResponseChecksumObserver::new(second.clone()));
242 let cfg = ConfigBag::of_layers(vec![layer]);
243
244 let kinds = [
245 ChecksumEventKind::Validating(Validating::new("crc32")),
246 ChecksumEventKind::NotValidated(NotValidated::new(ChecksumSkip::NoChecksum)),
247 ChecksumEventKind::Matched(Match::new("computed")),
248 ChecksumEventKind::Mismatched(Mismatch::new("expected", "computed")),
249 ChecksumEventKind::Errored,
250 ChecksumEventKind::Dropped,
251 ];
252 for observer in cfg.load::<SharedResponseChecksumObserver>() {
253 for kind in &kinds {
254 observer.on_event(&ChecksumEvent::new(2, kind.clone()));
255 }
256 }
257
258 let expected = [
259 "2 validating crc32",
260 "2 not_validated NoChecksum",
261 "2 matched computed",
262 "2 mismatched expected computed",
263 "2 errored",
264 "2 dropped",
265 ];
266 assert_eq!(first.events(), expected);
267 assert_eq!(second.events(), expected);
268 }
269}