use aws_smithy_types::config_bag::{Storable, StoreAppend};
use std::fmt;
use std::sync::Arc;
pub trait ObserveResponseChecksum: Send + Sync + fmt::Debug {
fn on_event(&self, event: &ChecksumEvent<'_>);
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChecksumEvent<'a> {
attempt: u32,
kind: ChecksumEventKind<'a>,
}
impl<'a> ChecksumEvent<'a> {
pub fn new(attempt: u32, kind: ChecksumEventKind<'a>) -> Self {
Self { attempt, kind }
}
pub fn attempt(&self) -> u32 {
self.attempt
}
pub fn kind(&self) -> &ChecksumEventKind<'a> {
&self.kind
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChecksumEventKind<'a> {
NotValidated(NotValidated),
Validating(Validating),
Matched(Match<'a>),
Mismatched(Mismatch<'a>),
Errored,
Dropped,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NotValidated {
reason: ChecksumSkip,
}
impl NotValidated {
pub fn new(reason: ChecksumSkip) -> Self {
Self { reason }
}
pub fn reason(&self) -> ChecksumSkip {
self.reason
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Validating {
algorithm: &'static str,
}
impl Validating {
pub fn new(algorithm: &'static str) -> Self {
Self { algorithm }
}
pub fn algorithm(&self) -> &'static str {
self.algorithm
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Match<'a> {
computed: &'a str,
}
impl<'a> Match<'a> {
pub fn new(computed: &'a str) -> Self {
Self { computed }
}
pub fn computed(&self) -> &'a str {
self.computed
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Mismatch<'a> {
expected: &'a str,
computed: &'a str,
}
impl<'a> Mismatch<'a> {
pub fn new(expected: &'a str, computed: &'a str) -> Self {
Self { expected, computed }
}
pub fn expected(&self) -> &'a str {
self.expected
}
pub fn computed(&self) -> &'a str {
self.computed
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChecksumSkip {
Disabled,
NoChecksum,
PartLevelChecksum,
InvalidChecksumHeader,
UnsupportedAlgorithm,
ErrorResponse,
}
#[derive(Clone, Debug)]
pub struct SharedResponseChecksumObserver(Arc<dyn ObserveResponseChecksum>);
impl SharedResponseChecksumObserver {
pub fn new(observer: impl ObserveResponseChecksum + 'static) -> Self {
Self(Arc::new(observer))
}
}
impl ObserveResponseChecksum for SharedResponseChecksumObserver {
fn on_event(&self, event: &ChecksumEvent<'_>) {
self.0.on_event(event)
}
}
impl Storable for SharedResponseChecksumObserver {
type Storer = StoreAppend<Self>;
}
#[cfg(test)]
mod tests {
use super::{
ChecksumEvent, ChecksumEventKind, ChecksumSkip, Match, Mismatch, NotValidated,
ObserveResponseChecksum, SharedResponseChecksumObserver, Validating,
};
use aws_smithy_types::config_bag::{ConfigBag, Layer};
use std::sync::{Arc, Mutex};
#[derive(Clone, Debug, Default)]
struct Recorder(Arc<Mutex<Vec<String>>>);
impl Recorder {
fn record(&self, event: impl Into<String>) {
self.0.lock().unwrap().push(event.into());
}
fn events(&self) -> Vec<String> {
self.0.lock().unwrap().clone()
}
}
impl ObserveResponseChecksum for Recorder {
fn on_event(&self, event: &ChecksumEvent<'_>) {
let kind = match event.kind() {
ChecksumEventKind::NotValidated(data) => {
format!("not_validated {:?}", data.reason())
}
ChecksumEventKind::Validating(data) => format!("validating {}", data.algorithm()),
ChecksumEventKind::Matched(data) => format!("matched {}", data.computed()),
ChecksumEventKind::Mismatched(data) => {
format!("mismatched {} {}", data.expected(), data.computed())
}
ChecksumEventKind::Errored => "errored".to_string(),
ChecksumEventKind::Dropped => "dropped".to_string(),
};
self.record(format!("{} {kind}", event.attempt()));
}
}
#[test]
fn every_appended_observer_receives_every_event() {
let (first, second) = (Recorder::default(), Recorder::default());
let mut layer = Layer::new("test");
layer.store_append(SharedResponseChecksumObserver::new(first.clone()));
layer.store_append(SharedResponseChecksumObserver::new(second.clone()));
let cfg = ConfigBag::of_layers(vec![layer]);
let kinds = [
ChecksumEventKind::Validating(Validating::new("crc32")),
ChecksumEventKind::NotValidated(NotValidated::new(ChecksumSkip::NoChecksum)),
ChecksumEventKind::Matched(Match::new("computed")),
ChecksumEventKind::Mismatched(Mismatch::new("expected", "computed")),
ChecksumEventKind::Errored,
ChecksumEventKind::Dropped,
];
for observer in cfg.load::<SharedResponseChecksumObserver>() {
for kind in &kinds {
observer.on_event(&ChecksumEvent::new(2, kind.clone()));
}
}
let expected = [
"2 validating crc32",
"2 not_validated NoChecksum",
"2 matched computed",
"2 mismatched expected computed",
"2 errored",
"2 dropped",
];
assert_eq!(first.events(), expected);
assert_eq!(second.events(), expected);
}
}