refactor(replication): name the resync state error and keep io failures typed (#6628)

Backlog#1845 step 7. The replication crate's hand-rolled, crate-generic Error type actually describes one thing: failures of the persisted resync/MRF state files. Rename it to ResyncStateError so the name says so, and stop collapsing io::Error into Other(String): a new Io(std::io::Error) variant keeps the kind and source chain, Display renders identically, and the ecstore boundary maps it to StorageError::Io so the kind survives into store-layer classification instead of degrading into a stringified other().

No thiserror introduced - the crate keeps its zero-internal-deps posture and hand-written impls.

Ref rustfs/backlog#1845
This commit is contained in:
Zhengchao An
2026-08-26 12:32:53 +08:00
committed by GitHub
parent aa56d4b847
commit 65a7cc9cd4
4 changed files with 92 additions and 62 deletions
@@ -53,10 +53,13 @@ pub(crate) const MRF_META_FORMAT: u16 = rustfs_replication::mrf::MRF_META_FORMAT
)] )]
pub(crate) const MRF_META_VERSION: u16 = rustfs_replication::mrf::MRF_META_VERSION; pub(crate) const MRF_META_VERSION: u16 = rustfs_replication::mrf::MRF_META_VERSION;
fn map_replication_error(err: rustfs_replication::Error) -> Error { fn map_replication_error(err: rustfs_replication::ResyncStateError) -> Error {
match err { match err {
rustfs_replication::Error::CorruptedFormat => Error::CorruptedFormat, rustfs_replication::ResyncStateError::CorruptedFormat => Error::CorruptedFormat,
rustfs_replication::Error::Other(err) => Error::other(err), // Keep the io error typed so its kind survives into StorageError::Io
// instead of degrading to a stringified other() (backlog#1845).
rustfs_replication::ResyncStateError::Io(err) => Error::Io(err),
rustfs_replication::ResyncStateError::Other(err) => Error::other(err),
} }
} }
+1 -1
View File
@@ -78,7 +78,7 @@ pub use queue::{
replication_heal_queue_action, worker_queue_for_replication_type, replication_heal_queue_action, worker_queue_for_replication_type,
}; };
pub use resync::{ pub use resync::{
BucketReplicationResyncStatus, Error, RESYNC_FILE_MAX_BYTES, Result, ResyncOpts, ResyncStatusType, BucketReplicationResyncStatus, RESYNC_FILE_MAX_BYTES, Result, ResyncOpts, ResyncStateError, ResyncStatusType,
TargetReplicationResyncStatus, decode_resync_file, encode_resync_file, is_version_id_mismatch, resync_state_accepts_update, TargetReplicationResyncStatus, decode_resync_file, encode_resync_file, is_version_id_mismatch, resync_state_accepts_update,
resync_status_duration, sanitize_resync_error_detail, should_auto_resume_resync, should_count_head_proxy_failure, resync_status_duration, sanitize_resync_error_detail, should_auto_resume_resync, should_count_head_proxy_failure,
}; };
+7 -7
View File
@@ -15,7 +15,7 @@
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use std::fmt; use std::fmt;
use crate::{Error, Result}; use crate::{Result, ResyncStateError};
pub use crate::filemeta::{MrfOpKind, MrfReplicateEntry}; pub use crate::filemeta::{MrfOpKind, MrfReplicateEntry};
@@ -569,7 +569,7 @@ impl MrfV2Envelope {
} }
} }
pub fn encode_mrf_file(entries: &[MrfReplicateEntry]) -> Result<Vec<u8>> { pub fn encode_mrf_file(entries: &[MrfReplicateEntry]) -> Result<Vec<u8>> {
let payload = rmp_serde::to_vec_named(entries).map_err(|e| Error::Other(e.to_string()))?; let payload = rmp_serde::to_vec_named(entries).map_err(|e| ResyncStateError::Other(e.to_string()))?;
let mut data = Vec::with_capacity(4 + payload.len()); let mut data = Vec::with_capacity(4 + payload.len());
let mut fmt = [0u8; 2]; let mut fmt = [0u8; 2];
LittleEndian::write_u16(&mut fmt, MRF_META_FORMAT); LittleEndian::write_u16(&mut fmt, MRF_META_FORMAT);
@@ -583,19 +583,19 @@ pub fn encode_mrf_file(entries: &[MrfReplicateEntry]) -> Result<Vec<u8>> {
pub fn decode_mrf_file(data: &[u8]) -> Result<Vec<MrfReplicateEntry>> { pub fn decode_mrf_file(data: &[u8]) -> Result<Vec<MrfReplicateEntry>> {
if data.len() <= 4 { if data.len() <= 4 {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
let mut fmt = [0u8; 2]; let mut fmt = [0u8; 2];
fmt.copy_from_slice(&data[0..2]); fmt.copy_from_slice(&data[0..2]);
if LittleEndian::read_u16(&fmt) != MRF_META_FORMAT { if LittleEndian::read_u16(&fmt) != MRF_META_FORMAT {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
let mut ver = [0u8; 2]; let mut ver = [0u8; 2];
ver.copy_from_slice(&data[2..4]); ver.copy_from_slice(&data[2..4]);
if LittleEndian::read_u16(&ver) != MRF_META_VERSION { if LittleEndian::read_u16(&ver) != MRF_META_VERSION {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
rmp_serde::from_slice(&data[4..]).map_err(|e| Error::Other(e.to_string())) rmp_serde::from_slice(&data[4..]).map_err(|e| ResyncStateError::Other(e.to_string()))
} }
#[cfg(test)] #[cfg(test)]
@@ -755,7 +755,7 @@ mod tests {
data.extend_from_slice(&MRF_META_VERSION.to_le_bytes()); data.extend_from_slice(&MRF_META_VERSION.to_le_bytes());
data.push(0x90); data.push(0x90);
assert!(matches!(decode_mrf_file(&data), Err(Error::CorruptedFormat))); assert!(matches!(decode_mrf_file(&data), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
+78 -51
View File
@@ -49,62 +49,74 @@ const RESYNC_ERROR_SENSITIVE_MARKERS: &[&str] = &[
"password", "password",
]; ];
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, ResyncStateError>;
/// Error type for the persisted resync/MRF state files (backlog#1845 step 7:
/// renamed from the crate-generic `Error`, with `Io` kept typed instead of
/// collapsing into a string so callers can still classify the io failure).
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum ResyncStateError {
CorruptedFormat, CorruptedFormat,
Io(std::io::Error),
Other(String), Other(String),
} }
impl Error { impl ResyncStateError {
fn other(err: impl Into<String>) -> Self { fn other(err: impl Into<String>) -> Self {
Self::Other(err.into()) Self::Other(err.into())
} }
} }
impl fmt::Display for Error { impl fmt::Display for ResyncStateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::CorruptedFormat => write!(f, "corrupted format"), Self::CorruptedFormat => write!(f, "corrupted format"),
Self::Io(err) => write!(f, "{err}"),
Self::Other(err) => write!(f, "{err}"), Self::Other(err) => write!(f, "{err}"),
} }
} }
} }
impl std::error::Error for Error {} impl std::error::Error for ResyncStateError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
impl From<std::io::Error> for Error { match self {
fn from(err: std::io::Error) -> Self { Self::Io(err) => Some(err),
Self::other(err.to_string()) _ => None,
}
} }
} }
impl From<std::string::FromUtf8Error> for Error { impl From<std::io::Error> for ResyncStateError {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl From<std::string::FromUtf8Error> for ResyncStateError {
fn from(err: std::string::FromUtf8Error) -> Self { fn from(err: std::string::FromUtf8Error) -> Self {
Self::other(err.to_string()) Self::other(err.to_string())
} }
} }
impl From<rmp::encode::ValueWriteError> for Error { impl From<rmp::encode::ValueWriteError> for ResyncStateError {
fn from(err: rmp::encode::ValueWriteError) -> Self { fn from(err: rmp::encode::ValueWriteError) -> Self {
Self::other(err.to_string()) Self::other(err.to_string())
} }
} }
impl From<rmp::decode::ValueReadError> for Error { impl From<rmp::decode::ValueReadError> for ResyncStateError {
fn from(err: rmp::decode::ValueReadError) -> Self { fn from(err: rmp::decode::ValueReadError) -> Self {
Self::other(err.to_string()) Self::other(err.to_string())
} }
} }
impl From<rmp::decode::NumValueReadError> for Error { impl From<rmp::decode::NumValueReadError> for ResyncStateError {
fn from(err: rmp::decode::NumValueReadError) -> Self { fn from(err: rmp::decode::NumValueReadError) -> Self {
Self::other(err.to_string()) Self::other(err.to_string())
} }
} }
impl From<rmp_serde::decode::Error> for Error { impl From<rmp_serde::decode::Error> for ResyncStateError {
fn from(err: rmp_serde::decode::Error) -> Self { fn from(err: rmp_serde::decode::Error) -> Self {
Self::other(err.to_string()) Self::other(err.to_string())
} }
@@ -360,7 +372,7 @@ impl BucketReplicationResyncStatus {
rmp::encode::write_str(&mut wr, "v")?; rmp::encode::write_str(&mut wr, "v")?;
rmp::encode::write_i32(&mut wr, i32::from(self.version))?; rmp::encode::write_i32(&mut wr, i32::from(self.version))?;
rmp::encode::write_str(&mut wr, "brs")?; rmp::encode::write_str(&mut wr, "brs")?;
let target_count = u32::try_from(self.targets_map.len()).map_err(|_| Error::CorruptedFormat)?; let target_count = u32::try_from(self.targets_map.len()).map_err(|_| ResyncStateError::CorruptedFormat)?;
rmp::encode::write_map_len(&mut wr, target_count)?; rmp::encode::write_map_len(&mut wr, target_count)?;
for (arn, status) in &self.targets_map { for (arn, status) in &self.targets_map {
rmp::encode::write_str(&mut wr, arn)?; rmp::encode::write_str(&mut wr, arn)?;
@@ -386,11 +398,11 @@ impl BucketReplicationResyncStatus {
match key.as_str() { match key.as_str() {
"v" => { "v" => {
let v: i32 = rmp::decode::read_int(&mut rd)?; let v: i32 = rmp::decode::read_int(&mut rd)?;
out.version = u16::try_from(v).map_err(|_| Error::other("invalid resync version"))?; out.version = u16::try_from(v).map_err(|_| ResyncStateError::other("invalid resync version"))?;
} }
"brs" => { "brs" => {
let map_len = rmp::decode::read_map_len(&mut rd)?; let map_len = rmp::decode::read_map_len(&mut rd)?;
let target_count = usize::try_from(map_len).map_err(|_| Error::CorruptedFormat)?; let target_count = usize::try_from(map_len).map_err(|_| ResyncStateError::CorruptedFormat)?;
let mut targets = HashMap::with_capacity(target_count); let mut targets = HashMap::with_capacity(target_count);
for _ in 0..map_len { for _ in 0..map_len {
let arn = read_msgp_str(&mut rd)?; let arn = read_msgp_str(&mut rd)?;
@@ -436,19 +448,19 @@ pub fn encode_resync_file(status: &BucketReplicationResyncStatus) -> Result<Vec<
pub fn decode_resync_file(data: &[u8]) -> Result<BucketReplicationResyncStatus> { pub fn decode_resync_file(data: &[u8]) -> Result<BucketReplicationResyncStatus> {
if data.len() <= RESYNC_FILE_HEADER_LEN || data.len() > RESYNC_FILE_MAX_BYTES { if data.len() <= RESYNC_FILE_HEADER_LEN || data.len() > RESYNC_FILE_MAX_BYTES {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
let mut major = [0u8; 2]; let mut major = [0u8; 2];
major.copy_from_slice(&data[0..2]); major.copy_from_slice(&data[0..2]);
if LittleEndian::read_u16(&major) != RESYNC_META_FORMAT { if LittleEndian::read_u16(&major) != RESYNC_META_FORMAT {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
let mut minor = [0u8; 2]; let mut minor = [0u8; 2];
minor.copy_from_slice(&data[2..4]); minor.copy_from_slice(&data[2..4]);
if LittleEndian::read_u16(&minor) != RESYNC_META_VERSION { if LittleEndian::read_u16(&minor) != RESYNC_META_VERSION {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
let status = match BucketReplicationResyncStatus::unmarshal_msg(&data[4..]) { let status = match BucketReplicationResyncStatus::unmarshal_msg(&data[4..]) {
@@ -456,7 +468,7 @@ pub fn decode_resync_file(data: &[u8]) -> Result<BucketReplicationResyncStatus>
Err(_) => BucketReplicationResyncStatus::unmarshal_legacy_msg(&data[4..])?, Err(_) => BucketReplicationResyncStatus::unmarshal_legacy_msg(&data[4..])?,
}; };
if status.version != RESYNC_META_VERSION { if status.version != RESYNC_META_VERSION {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
Ok(status) Ok(status)
} }
@@ -478,28 +490,28 @@ fn wire_zero_time() -> OffsetDateTime {
fn validate_msgp_payload(data: &[u8]) -> Result<()> { fn validate_msgp_payload(data: &[u8]) -> Result<()> {
if data.len() > RESYNC_MSGP_MAX_BYTES { if data.len() > RESYNC_MSGP_MAX_BYTES {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
let mut rd = Cursor::new(data); let mut rd = Cursor::new(data);
let mut values = 0usize; let mut values = 0usize;
validate_msgp_value(&mut rd, 0, &mut values)?; validate_msgp_value(&mut rd, 0, &mut values)?;
if usize::try_from(rd.position()).ok() != Some(data.len()) { if usize::try_from(rd.position()).ok() != Some(data.len()) {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
Ok(()) Ok(())
} }
fn validate_msgp_value<R: Read>(rd: &mut R, depth: usize, values: &mut usize) -> Result<()> { fn validate_msgp_value<R: Read>(rd: &mut R, depth: usize, values: &mut usize) -> Result<()> {
if depth > RESYNC_MSGP_MAX_DEPTH { if depth > RESYNC_MSGP_MAX_DEPTH {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
*values = values.checked_add(1).ok_or(Error::CorruptedFormat)?; *values = values.checked_add(1).ok_or(ResyncStateError::CorruptedFormat)?;
if *values > RESYNC_MSGP_MAX_VALUES { if *values > RESYNC_MSGP_MAX_VALUES {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
let marker = rmp::decode::read_marker(rd).map_err(|e| Error::other(format!("{e:?}")))?; let marker = rmp::decode::read_marker(rd).map_err(|e| ResyncStateError::other(format!("{e:?}")))?;
let skip_len = match marker { let skip_len = match marker {
Marker::Null | Marker::False | Marker::True | Marker::FixPos(_) | Marker::FixNeg(_) => 0, Marker::Null | Marker::False | Marker::True | Marker::FixPos(_) | Marker::FixNeg(_) => 0,
Marker::U8 | Marker::I8 => 1, Marker::U8 | Marker::I8 => 1,
@@ -558,7 +570,7 @@ fn validate_msgp_value<R: Read>(rd: &mut R, depth: usize, values: &mut usize) ->
skip_exact(rd, 1)?; skip_exact(rd, 1)?;
return skip_exact(rd, len); return skip_exact(rd, len);
} }
Marker::Reserved => return Err(Error::CorruptedFormat), Marker::Reserved => return Err(ResyncStateError::CorruptedFormat),
}; };
let skip_len = validate_msgp_element_len(skip_len)?; let skip_len = validate_msgp_element_len(skip_len)?;
skip_exact(rd, skip_len) skip_exact(rd, skip_len)
@@ -566,10 +578,10 @@ fn validate_msgp_value<R: Read>(rd: &mut R, depth: usize, values: &mut usize) ->
fn validate_msgp_collection<R: Read>(rd: &mut R, len: usize, depth: usize, values: &mut usize, is_map: bool) -> Result<()> { fn validate_msgp_collection<R: Read>(rd: &mut R, len: usize, depth: usize, values: &mut usize, is_map: bool) -> Result<()> {
if len > RESYNC_MSGP_MAX_COLLECTION_ITEMS { if len > RESYNC_MSGP_MAX_COLLECTION_ITEMS {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
let values_per_item = if is_map { 2 } else { 1 }; let values_per_item = if is_map { 2 } else { 1 };
let child_count = len.checked_mul(values_per_item).ok_or(Error::CorruptedFormat)?; let child_count = len.checked_mul(values_per_item).ok_or(ResyncStateError::CorruptedFormat)?;
for _ in 0..child_count { for _ in 0..child_count {
validate_msgp_value(rd, depth + 1, values)?; validate_msgp_value(rd, depth + 1, values)?;
} }
@@ -578,7 +590,7 @@ fn validate_msgp_collection<R: Read>(rd: &mut R, len: usize, depth: usize, value
fn validate_msgp_element_len(len: usize) -> Result<usize> { fn validate_msgp_element_len(len: usize) -> Result<usize> {
if len > RESYNC_MSGP_MAX_ELEMENT_BYTES { if len > RESYNC_MSGP_MAX_ELEMENT_BYTES {
return Err(Error::CorruptedFormat); return Err(ResyncStateError::CorruptedFormat);
} }
Ok(len) Ok(len)
} }
@@ -591,11 +603,11 @@ fn read_msgp_str<R: Read>(rd: &mut R) -> Result<String> {
} }
fn read_msgp_time_or_nil<R: Read>(rd: &mut R) -> Result<Option<OffsetDateTime>> { fn read_msgp_time_or_nil<R: Read>(rd: &mut R) -> Result<Option<OffsetDateTime>> {
let marker = rmp::decode::read_marker(rd).map_err(|e| Error::other(format!("{e:?}")))?; let marker = rmp::decode::read_marker(rd).map_err(|e| ResyncStateError::other(format!("{e:?}")))?;
match marker { match marker {
Marker::Null => Ok(None), Marker::Null => Ok(None),
Marker::Ext8 => Ok(Some(read_msgp_ext8_time(rd)?)), Marker::Ext8 => Ok(Some(read_msgp_ext8_time(rd)?)),
other => Err(Error::other(format!("expected time ext or nil, got marker: {other:?}"))), other => Err(ResyncStateError::other(format!("expected time ext or nil, got marker: {other:?}"))),
} }
} }
@@ -604,21 +616,21 @@ fn read_msgp_ext8_time<R: Read>(rd: &mut R) -> Result<OffsetDateTime> {
rd.read_exact(&mut len_buf)?; rd.read_exact(&mut len_buf)?;
let len = len_buf[0] as usize; let len = len_buf[0] as usize;
if len != MSGP_TIME_LEN as usize { if len != MSGP_TIME_LEN as usize {
return Err(Error::other(format!("invalid msgp time len: {len}"))); return Err(ResyncStateError::other(format!("invalid msgp time len: {len}")));
} }
let mut type_buf = [0u8; 1]; let mut type_buf = [0u8; 1];
rd.read_exact(&mut type_buf)?; rd.read_exact(&mut type_buf)?;
if type_buf[0] != MSGP_TIME_EXT_TYPE as u8 { if type_buf[0] != MSGP_TIME_EXT_TYPE as u8 {
return Err(Error::other(format!("invalid msgp time type: {}", type_buf[0]))); return Err(ResyncStateError::other(format!("invalid msgp time type: {}", type_buf[0])));
} }
let mut buf = [0u8; 12]; let mut buf = [0u8; 12];
rd.read_exact(&mut buf)?; rd.read_exact(&mut buf)?;
let sec = BigEndian::read_i64(&buf[0..8]); let sec = BigEndian::read_i64(&buf[0..8]);
let nsec = BigEndian::read_u32(&buf[8..12]); let nsec = BigEndian::read_u32(&buf[8..12]);
OffsetDateTime::from_unix_timestamp(sec) OffsetDateTime::from_unix_timestamp(sec)
.map_err(|_| Error::other("invalid timestamp"))? .map_err(|_| ResyncStateError::other("invalid timestamp"))?
.replace_nanosecond(nsec) .replace_nanosecond(nsec)
.map_err(|_| Error::other("invalid nanosecond")) .map_err(|_| ResyncStateError::other("invalid nanosecond"))
} }
fn write_msgp_time<W: Write>(wr: &mut W, time: OffsetDateTime) -> Result<()> { fn write_msgp_time<W: Write>(wr: &mut W, time: OffsetDateTime) -> Result<()> {
@@ -660,9 +672,9 @@ fn read_skip_len<R: Read>(rd: &mut R, bytes: usize) -> Result<usize> {
4 => { 4 => {
let mut buf = [0u8; 4]; let mut buf = [0u8; 4];
rd.read_exact(&mut buf)?; rd.read_exact(&mut buf)?;
usize::try_from(u32::from_be_bytes(buf)).map_err(|_| Error::CorruptedFormat) usize::try_from(u32::from_be_bytes(buf)).map_err(|_| ResyncStateError::CorruptedFormat)
} }
_ => Err(Error::other("invalid MessagePack length width")), _ => Err(ResyncStateError::other("invalid MessagePack length width")),
} }
} }
@@ -685,7 +697,7 @@ fn resync_status_from_i32(code: i32) -> Result<ResyncStatusType> {
3 => Ok(ResyncStatusType::ResyncStarted), 3 => Ok(ResyncStatusType::ResyncStarted),
4 => Ok(ResyncStatusType::ResyncCompleted), 4 => Ok(ResyncStatusType::ResyncCompleted),
5 => Ok(ResyncStatusType::ResyncFailed), 5 => Ok(ResyncStatusType::ResyncFailed),
_ => Err(Error::other(format!("invalid resync status code: {code}"))), _ => Err(ResyncStateError::other(format!("invalid resync status code: {code}"))),
} }
} }
@@ -847,14 +859,14 @@ mod tests {
let mut data = encode_resync_file(&status).expect("resync status should encode"); let mut data = encode_resync_file(&status).expect("resync status should encode");
data.push(0xc0); data.push(0xc0);
assert!(matches!(decode_resync_file(&data), Err(Error::CorruptedFormat))); assert!(matches!(decode_resync_file(&data), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
fn resync_file_rejects_reserved_messagepack_marker() { fn resync_file_rejects_reserved_messagepack_marker() {
let data = resync_file_with_unknown_value(&[0xc1]); let data = resync_file_with_unknown_value(&[0xc1]);
assert!(matches!(decode_resync_file(&data), Err(Error::CorruptedFormat))); assert!(matches!(decode_resync_file(&data), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
@@ -866,7 +878,7 @@ mod tests {
let value = msgp_str32(RESYNC_MSGP_MAX_ELEMENT_BYTES + 1); let value = msgp_str32(RESYNC_MSGP_MAX_ELEMENT_BYTES + 1);
let data = resync_file_with_unknown_value(&value); let data = resync_file_with_unknown_value(&value);
assert!(matches!(decode_resync_file(&data), Err(Error::CorruptedFormat))); assert!(matches!(decode_resync_file(&data), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
@@ -899,7 +911,7 @@ mod tests {
for value in [array, map] { for value in [array, map] {
let data = resync_file_with_unknown_value(&value); let data = resync_file_with_unknown_value(&value);
assert!(matches!(decode_resync_file(&data), Err(Error::CorruptedFormat))); assert!(matches!(decode_resync_file(&data), Err(ResyncStateError::CorruptedFormat)));
} }
} }
@@ -921,7 +933,7 @@ mod tests {
let data = resync_file_with_unknown_value(&extension(RESYNC_MSGP_MAX_ELEMENT_BYTES + 1)); let data = resync_file_with_unknown_value(&extension(RESYNC_MSGP_MAX_ELEMENT_BYTES + 1));
assert!(matches!(decode_resync_file(&data), Err(Error::CorruptedFormat))); assert!(matches!(decode_resync_file(&data), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
@@ -935,7 +947,7 @@ mod tests {
let mut rejected = vec![0x91; RESYNC_MSGP_MAX_DEPTH]; let mut rejected = vec![0x91; RESYNC_MSGP_MAX_DEPTH];
rejected.push(0xc0); rejected.push(0xc0);
let data = resync_file_with_unknown_value(&rejected); let data = resync_file_with_unknown_value(&rejected);
assert!(matches!(decode_resync_file(&data), Err(Error::CorruptedFormat))); assert!(matches!(decode_resync_file(&data), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
@@ -973,7 +985,7 @@ mod tests {
let data = resync_file_with_unknown_value(&nested_arrays(accepted_leaf_count + 1)); let data = resync_file_with_unknown_value(&nested_arrays(accepted_leaf_count + 1));
assert!(matches!(decode_resync_file(&data), Err(Error::CorruptedFormat))); assert!(matches!(decode_resync_file(&data), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
@@ -1004,7 +1016,7 @@ mod tests {
data.push(0xc0); data.push(0xc0);
assert_eq!(data.len(), RESYNC_FILE_MAX_BYTES + 1); assert_eq!(data.len(), RESYNC_FILE_MAX_BYTES + 1);
assert!(matches!(decode_resync_file(&data), Err(Error::CorruptedFormat))); assert!(matches!(decode_resync_file(&data), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
@@ -1030,7 +1042,7 @@ mod tests {
); );
} }
assert!(matches!(encode_resync_file(&status), Err(Error::CorruptedFormat))); assert!(matches!(encode_resync_file(&status), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
@@ -1044,7 +1056,7 @@ mod tests {
}, },
); );
assert!(matches!(encode_resync_file(&status), Err(Error::CorruptedFormat))); assert!(matches!(encode_resync_file(&status), Err(ResyncStateError::CorruptedFormat)));
} }
#[test] #[test]
@@ -1115,7 +1127,7 @@ mod tests {
assert!(matches!( assert!(matches!(
BucketReplicationResyncStatus::unmarshal_legacy_msg(&payload), BucketReplicationResyncStatus::unmarshal_legacy_msg(&payload),
Err(Error::CorruptedFormat) Err(ResyncStateError::CorruptedFormat)
)); ));
} }
@@ -1173,4 +1185,19 @@ mod tests {
assert!(matches!(rmp::decode::read_marker(&mut cursor), Ok(Marker::FixPos(1)))); assert!(matches!(rmp::decode::read_marker(&mut cursor), Ok(Marker::FixPos(1))));
} }
} }
#[test]
fn io_errors_stay_typed_in_resync_state_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "state file locked");
let err: ResyncStateError = io_err.into();
match &err {
ResyncStateError::Io(inner) => {
assert_eq!(inner.kind(), std::io::ErrorKind::PermissionDenied);
assert_eq!(inner.to_string(), "state file locked");
}
other => panic!("io::Error must stay typed, got {other:?}"),
}
assert_eq!(err.to_string(), "state file locked");
assert!(std::error::Error::source(&err).is_some(), "Io must expose its source");
}
} }