use crate::model::{Object, ObjectChecksums};
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ChecksumMismatch {
Crc32c { got: u32, want: u32 },
Md5 {
got: bytes::Bytes,
want: bytes::Bytes,
},
Both {
got: Box<ObjectChecksums>,
want: Box<ObjectChecksums>,
},
}
impl std::fmt::Display for ChecksumMismatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Crc32c { got, want } => write!(
f,
"the CRC32C checksums do not match: got=0x{got:08x}, want=0x{want:08x}"
),
Self::Md5 { got, want } => write!(
f,
"the MD5 hashes do not match: got={:0x?}, want={:0x?}",
&got, &want
),
Self::Both { got, want } => {
write!(
f,
"both the CRC32C checksums and MD5 hashes do not match: got.crc32c=0x{:08x}, want.crc32c=0x{:08x}, got.md5={:x?}, want.md5={:x?}",
got.crc32c.unwrap_or_default(),
want.crc32c.unwrap_or_default(),
got.md5_hash,
want.md5_hash
)
}
}
}
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum KeyAes256Error {
#[error("Key has an invalid length: expected 32 bytes.")]
InvalidLength,
}
type BoxedSource = Box<dyn std::error::Error + Send + Sync + 'static>;
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum ReadError {
#[error("checksum mismatch {0}")]
ChecksumMismatch(ChecksumMismatch),
#[error("missing {0} bytes at the end of the stream")]
ShortRead(u64),
#[error("too many bytes received: expected {expected}, stopped read at {got}")]
LongRead { got: u64, expected: u64 },
#[error("unexpected success code {0} in read request, only 200 and 206 are expected")]
UnexpectedSuccessCode(u16),
#[error("the response is missing '{0}', a required header")]
MissingHeader(&'static str),
#[error("the format for header '{0}' is incorrect")]
BadHeaderFormat(&'static str, #[source] BoxedSource),
#[error("cannot recover from an underlying read error: {0}")]
UnrecoverableBidiReadInterrupt(#[source] std::sync::Arc<crate::Error>),
#[error("the bidi streaming read response is invalid: {0}")]
InvalidBidiStreamingReadResponse(#[source] BoxedSource),
}
impl ReadError {
pub(crate) fn bidi_out_of_order(expected: i64, got: i64) -> Self {
Self::InvalidBidiStreamingReadResponse(
format!("message offset mismatch, expected={expected}, got={got}").into(),
)
}
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum WriteError {
#[error(
"the service previously persisted {offset} bytes, but now reports only {persisted} as persisted"
)]
UnexpectedRewind { offset: u64, persisted: u64 },
#[error("the service reports {persisted} bytes as persisted, but we only sent {sent} bytes")]
TooMuchProgress { sent: u64, persisted: u64 },
#[error("checksum mismatch {mismatch} when uploading {} to {}", object.name, object.bucket)]
ChecksumMismatch {
mismatch: ChecksumMismatch,
object: Box<Object>,
},
}
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct SigningError(SigningErrorKind);
impl SigningError {
pub fn is_signing(&self) -> bool {
matches!(self.0, SigningErrorKind::Signing(_))
}
pub fn is_invalid_parameter(&self) -> bool {
matches!(self.0, SigningErrorKind::InvalidParameter(_, _))
}
pub(crate) fn signing<T>(source: T) -> SigningError
where
T: Into<BoxError>,
{
SigningError(SigningErrorKind::Signing(source.into()))
}
pub(crate) fn invalid_parameter<S: Into<String>, T>(field: S, source: T) -> SigningError
where
T: Into<BoxError>,
{
SigningError(SigningErrorKind::InvalidParameter(
field.into(),
source.into(),
))
}
}
#[derive(thiserror::Error, Debug)]
#[allow(dead_code)]
enum SigningErrorKind {
#[error("signing failed: {0}")]
Signing(#[source] BoxError),
#[error("invalid `{0}` parameter: {1}")]
InvalidParameter(String, #[source] BoxError),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mismatch_crc32c() {
let value = ChecksumMismatch::Crc32c {
got: 0x01020304_u32,
want: 0x02030405_u32,
};
let fmt = value.to_string();
assert!(fmt.contains("got=0x01020304"), "{value:?} => {fmt}");
assert!(fmt.contains("want=0x02030405"), "{value:?} => {fmt}");
}
#[test]
fn mismatch_md5() {
let value = ChecksumMismatch::Md5 {
got: bytes::Bytes::from_owner([0x01_u8, 0x02, 0x03, 0x04]),
want: bytes::Bytes::from_owner([0x02_u8, 0x03, 0x04, 0x05]),
};
let fmt = value.to_string();
assert!(
fmt.contains(r#"got=b"\x01\x02\x03\x04""#),
"{value:?} => {fmt}"
);
assert!(
fmt.contains(r#"want=b"\x02\x03\x04\x05""#),
"{value:?} => {fmt}"
);
}
#[test]
fn mismatch_both() {
let got = ObjectChecksums::new()
.set_crc32c(0x01020304_u32)
.set_md5_hash(bytes::Bytes::from_owner([0x01_u8, 0x02, 0x03, 0x04]));
let want = ObjectChecksums::new()
.set_crc32c(0x02030405_u32)
.set_md5_hash(bytes::Bytes::from_owner([0x02_u8, 0x03, 0x04, 0x05]));
let value = ChecksumMismatch::Both {
got: Box::new(got),
want: Box::new(want),
};
let fmt = value.to_string();
assert!(fmt.contains("got.crc32c=0x01020304"), "{value:?} => {fmt}");
assert!(fmt.contains("want.crc32c=0x02030405"), "{value:?} => {fmt}");
assert!(
fmt.contains(r#"got.md5=b"\x01\x02\x03\x04""#),
"{value:?} => {fmt}"
);
assert!(
fmt.contains(r#"want.md5=b"\x02\x03\x04\x05""#),
"{value:?} => {fmt}"
);
}
#[test]
fn signing_errors() {
let value = SigningError::signing("sign error".to_string());
let fmt = value.to_string();
assert!(
fmt.contains("signing failed: sign error"),
"{value:?} => {fmt}"
);
let value = SigningError::invalid_parameter("endpoint", "missing scheme".to_string());
let fmt = value.to_string();
assert!(
fmt.contains("invalid `endpoint` parameter: missing scheme"),
"{value:?} => {fmt}"
);
}
}