rocketmq_protocol/
contract.rs1use rocketmq_error::CanonicalCondition;
16
17use crate::protocol::header_codec::HeaderValueKind;
18use crate::protocol::remoting_command_defaults::RemotingCommandDefaultsConflict;
19use crate::protocol::remoting_command_facade::InvalidRemotingSerializeType;
20
21#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
28#[non_exhaustive]
29pub enum ProtocolContractViolation {
30 #[error("checkpoint schema version {actual} does not match {expected}")]
32 CheckpointSchemaVersion {
33 expected: u16,
35 actual: u16,
37 },
38 #[error("invalid checkpoint field {field}: {reason}")]
40 InvalidCheckpointField {
41 field: &'static str,
43 reason: &'static str,
45 },
46 #[error("invalid checkpoint offsets: {reason}")]
48 InvalidCheckpointOffsets {
49 reason: &'static str,
51 },
52 #[error("checkpoint set has no Store member")]
54 MissingStoreMembers,
55 #[error("checkpoint set repeats a Store member")]
57 DuplicateStoreMember,
58 #[error("checkpoint member does not match the set barrier")]
60 CheckpointSetBindingMismatch {
61 member: &'static str,
63 },
64 #[error("checkpoint permits destructive WAL or persistent-volume replacement")]
66 DestructiveRollback,
67 #[error("checkpoint restore verification is incomplete")]
69 RestoreVerificationIncomplete,
70 #[error("missing required header field {header}.{key}")]
72 Missing {
73 header: &'static str,
75 key: &'static str,
77 },
78 #[error("invalid header field {header}.{key}: expected {expected:?}")]
80 InvalidValue {
81 header: &'static str,
83 key: &'static str,
85 expected: HeaderValueKind,
87 },
88 #[error("conflicting canonical and alias values for {header}.{key}")]
90 Conflict {
91 header: &'static str,
93 key: &'static str,
95 },
96 #[error("typed header and dynamic field conflict for {header}.{key}")]
98 DynamicFieldConflict {
99 header: &'static str,
101 key: &'static str,
103 },
104 #[error("legacy header validation failed for {header}")]
106 LegacyValidation {
107 header: &'static str,
109 },
110 #[error("legacy header map conversion failed for {header}")]
112 LegacyMapConversionFailed {
113 header: &'static str,
115 },
116 #[error("header validation failed for {header}: {rule}")]
118 Validation {
119 header: &'static str,
121 rule: &'static str,
123 },
124 #[error("header value is outside Java range for {header}.{key}")]
126 JavaRange {
127 header: &'static str,
129 key: &'static str,
131 },
132 #[error("header key length exceeds the ROCKETMQ limit for {header}.{key}")]
134 KeyLengthOverflow {
135 header: &'static str,
137 key: &'static str,
139 },
140 #[error("header value length exceeds the ROCKETMQ limit for {header}.{key}")]
142 ValueLengthOverflow {
143 header: &'static str,
145 key: &'static str,
147 },
148 #[error("ROCKETMQ extension-field payload exceeds the signed 32-bit wire limit")]
150 ExtensionFieldsLengthOverflow,
151 #[error("dynamic header key length exceeds the ROCKETMQ wire limit")]
153 DynamicKeyLengthOverflow,
154 #[error("dynamic header value length exceeds the ROCKETMQ wire limit")]
156 DynamicValueLengthOverflow,
157 #[error("direct binary codec is unavailable for {header}")]
159 FastCodecUnavailable {
160 header: &'static str,
162 },
163 #[error("invalid remoting serialization type")]
165 InvalidSerializeType(
166 #[from]
167 #[source]
168 InvalidRemotingSerializeType,
169 ),
170 #[error("remoting command defaults conflict with the initialized value")]
172 RemotingDefaultsConflict(
173 #[from]
174 #[source]
175 RemotingCommandDefaultsConflict,
176 ),
177 #[error("The specified group is blank.")]
179 BlankSubscriptionGroup,
180 #[error("The specified group is longer than group max length: {max_length}")]
182 SubscriptionGroupTooLong {
183 max_length: usize,
185 },
186 #[error("The specified group contains illegal characters, allowing only ^[%|a-zA-Z0-9_-]+$")]
188 SubscriptionGroupIllegalCharacters,
189 #[error("The specified group list contains a duplicate group.")]
191 DuplicateSubscriptionGroup,
192}
193
194impl ProtocolContractViolation {
195 #[must_use]
197 pub const fn condition(&self) -> CanonicalCondition {
198 match self {
199 Self::DestructiveRollback | Self::RestoreVerificationIncomplete | Self::RemotingDefaultsConflict(_) => {
200 CanonicalCondition::FailedPrecondition
201 }
202 Self::CheckpointSchemaVersion { .. }
203 | Self::InvalidCheckpointField { .. }
204 | Self::InvalidCheckpointOffsets { .. }
205 | Self::MissingStoreMembers
206 | Self::DuplicateStoreMember
207 | Self::CheckpointSetBindingMismatch { .. }
208 | Self::Missing { .. }
209 | Self::InvalidValue { .. }
210 | Self::Conflict { .. }
211 | Self::DynamicFieldConflict { .. }
212 | Self::LegacyValidation { .. }
213 | Self::LegacyMapConversionFailed { .. }
214 | Self::Validation { .. }
215 | Self::JavaRange { .. }
216 | Self::KeyLengthOverflow { .. }
217 | Self::ValueLengthOverflow { .. }
218 | Self::ExtensionFieldsLengthOverflow
219 | Self::DynamicKeyLengthOverflow
220 | Self::DynamicValueLengthOverflow
221 | Self::FastCodecUnavailable { .. }
222 | Self::InvalidSerializeType(_)
223 | Self::BlankSubscriptionGroup
224 | Self::SubscriptionGroupTooLong { .. }
225 | Self::SubscriptionGroupIllegalCharacters
226 | Self::DuplicateSubscriptionGroup => CanonicalCondition::InvalidArgument,
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use std::error::Error as _;
234
235 use super::*;
236 use crate::protocol::remoting_command::SERIALIZE_TYPE_PROPERTY;
237 use crate::protocol::remoting_command_facade::resolve_remoting_serialize_type;
238
239 #[test]
240 fn invalid_configuration_keeps_a_typed_source_behind_fixed_text() {
241 const SENTINEL: &str = "unsupported-serialize-type-secret";
242
243 let error = resolve_remoting_serialize_type(Some(SENTINEL), None).unwrap_err();
244
245 assert_eq!(error.condition(), CanonicalCondition::InvalidArgument);
246 assert_eq!(error.to_string(), "invalid remoting serialization type");
247 assert!(!format!("{error:?}").contains(SENTINEL));
248 let source = error
249 .source()
250 .and_then(|source| source.downcast_ref::<InvalidRemotingSerializeType>())
251 .expect("typed remoting serialization source");
252 assert_eq!(source.key(), SERIALIZE_TYPE_PROPERTY);
253 assert_eq!(source.value(), SENTINEL);
254 }
255
256 #[test]
257 fn rollback_contracts_are_failed_preconditions() {
258 assert_eq!(
259 ProtocolContractViolation::DestructiveRollback.condition(),
260 CanonicalCondition::FailedPrecondition
261 );
262 assert_eq!(
263 ProtocolContractViolation::RestoreVerificationIncomplete.condition(),
264 CanonicalCondition::FailedPrecondition
265 );
266 }
267}