1use std::error::Error as StdError;
16use std::fmt;
17use std::ops::Deref;
18use std::sync::Arc;
19
20use rocketmq_error::fields;
21use rocketmq_error::Error;
22use rocketmq_error::ErrorContext;
23use rocketmq_error::ErrorDescriptor;
24use rocketmq_error::SharedError;
25use rocketmq_error::ViewValueRef;
26
27#[derive(Clone)]
32pub struct ClientError(SharedError);
33
34pub type ClientResult<T> = std::result::Result<T, ClientError>;
36
37impl ClientError {
38 #[must_use]
40 pub fn from_error(error: Error) -> Self {
41 Self(Arc::new(error))
42 }
43
44 #[must_use]
46 pub const fn from_shared(error: SharedError) -> Self {
47 Self(error)
48 }
49
50 #[must_use]
52 pub fn descriptor(&self) -> &'static ErrorDescriptor {
53 self.0.descriptor()
54 }
55
56 #[must_use]
58 pub fn is(&self, descriptor: &'static ErrorDescriptor) -> bool {
59 self.descriptor().code() == descriptor.code()
60 }
61
62 #[must_use]
64 pub fn broker_response_code(&self) -> Option<i32> {
65 self.diagnostic_i64(fields::BROKER_CODE.schema().name())
66 .and_then(|code| i32::try_from(code).ok())
67 }
68
69 #[must_use]
71 pub fn broker_addr(&self) -> Option<&str> {
72 self.diagnostic_text(fields::BROKER_ADDR.schema().name())
73 }
74
75 #[must_use]
77 pub const fn shared_error(&self) -> &SharedError {
78 &self.0
79 }
80
81 #[must_use]
83 pub fn source_ref<T>(&self) -> Option<&T>
84 where
85 T: StdError + 'static,
86 {
87 let mut current: &(dyn StdError + 'static) = self.0.as_ref();
88 while let Some(source) = current.source() {
89 if let Some(found) = source.downcast_ref::<T>() {
90 return Some(found);
91 }
92 current = source;
93 }
94 None
95 }
96
97 #[must_use]
99 pub fn into_shared_error(self) -> SharedError {
100 self.0
101 }
102
103 #[must_use]
106 pub fn into_error(self) -> Error {
107 match Arc::try_unwrap(self.0) {
108 Ok(error) => error,
109 Err(shared) => {
110 let descriptor = shared.descriptor();
111 let context = shared.context().clone();
112 Error::caused_by(descriptor, ClientError(shared)).with_context(context)
113 }
114 }
115 }
116
117 #[must_use]
119 pub fn illegal_argument(message: impl Into<String>) -> Self {
120 let message = message.into();
121 let mut context = ErrorContext::new();
122 if !message.is_empty() {
123 context = context.with_secret_presence(fields::MESSAGE_PRESENT);
124 }
125 Self::from_error(Error::new(&rocketmq_error::CORE_ARGUMENT_INVALID).with_context(context))
126 }
127
128 #[must_use]
130 pub fn illegal_argument_source(source: impl StdError + Send + Sync + 'static) -> Self {
131 Self::from_error(
132 Error::caused_by(&rocketmq_error::CORE_ARGUMENT_INVALID, source)
133 .with_context(ErrorContext::new().with_secret_presence(fields::MESSAGE_PRESENT)),
134 )
135 }
136
137 #[must_use]
139 pub fn not_started() -> Self {
140 Self::from_error(Error::new(&rocketmq_error::CLIENT_LIFECYCLE_NOT_STARTED))
141 }
142
143 #[must_use]
145 pub fn already_started() -> Self {
146 Self::from_error(Error::new(&rocketmq_error::CLIENT_LIFECYCLE_ALREADY_STARTED))
147 }
148
149 #[must_use]
151 pub fn shutting_down() -> Self {
152 Self::from_error(Error::new(&rocketmq_error::CLIENT_LIFECYCLE_SHUTTING_DOWN))
153 }
154
155 #[must_use]
157 pub fn invalid_state(expected: impl AsRef<str>, actual: impl AsRef<str>) -> Self {
158 let context = ErrorContext::new()
159 .with_text(fields::EXPECTED_STATE, expected)
160 .with_text(fields::ACTUAL_STATE, actual);
161 Self::from_error(Error::new(&rocketmq_error::CLIENT_LIFECYCLE_INVALID_STATE).with_context(context))
162 }
163
164 #[must_use]
166 pub fn component_unavailable(role: impl AsRef<str>) -> Self {
167 let context = ErrorContext::new().with_text(fields::CLIENT_ROLE, role);
168 Self::from_error(Error::new(&rocketmq_error::CLIENT_COMPONENT_UNAVAILABLE).with_context(context))
169 }
170
171 #[must_use]
173 pub fn not_initialized(component: impl AsRef<str>) -> Self {
174 let context = ErrorContext::new().with_text(fields::COMPONENT_NAME, component);
175 Self::from_error(Error::new(&rocketmq_error::CORE_LIFECYCLE_NOT_INITIALIZED).with_context(context))
176 }
177
178 #[must_use]
180 pub fn config_missing(key: impl AsRef<str>) -> Self {
181 let context = ErrorContext::new().with_text(fields::KEY, key);
182 Self::from_error(Error::new(&rocketmq_error::CORE_CONFIGURATION_MISSING).with_context(context))
183 }
184
185 #[must_use]
187 pub fn config_invalid(key: impl AsRef<str>, value: impl AsRef<str>, reason: impl AsRef<str>) -> Self {
188 let mut context = ErrorContext::new().with_text(fields::KEY, key);
189 if !value.as_ref().is_empty() {
190 context = context.with_secret_presence(fields::VALUE_PRESENT);
191 }
192 if !reason.as_ref().is_empty() {
193 context = context.with_secret_presence(fields::REASON_PRESENT);
194 }
195 Self::from_error(Error::new(&rocketmq_error::CORE_CONFIGURATION_INVALID).with_context(context))
196 }
197
198 #[must_use]
200 pub fn config_invalid_source(
201 key: impl AsRef<str>,
202 value_present: bool,
203 source: impl StdError + Send + Sync + 'static,
204 ) -> Self {
205 let mut context = ErrorContext::new().with_text(fields::KEY, key);
206 if value_present {
207 context = context.with_secret_presence(fields::VALUE_PRESENT);
208 }
209 context = context.with_secret_presence(fields::REASON_PRESENT);
210 Self::from_error(Error::caused_by(&rocketmq_error::CORE_CONFIGURATION_INVALID, source).with_context(context))
211 }
212
213 #[must_use]
215 pub fn config_parse_failed(key: impl AsRef<str>, reason: impl AsRef<str>) -> Self {
216 let mut context = ErrorContext::new().with_text(fields::KEY, key);
217 if !reason.as_ref().is_empty() {
218 context = context.with_secret_presence(fields::REASON_PRESENT);
219 }
220 Self::from_error(Error::new(&rocketmq_error::CORE_CONFIGURATION_PARSE_FAILED).with_context(context))
221 }
222
223 #[must_use]
225 pub fn config_parse_source(key: impl AsRef<str>, source: impl StdError + Send + Sync + 'static) -> Self {
226 let context = ErrorContext::new()
227 .with_text(fields::KEY, key)
228 .with_secret_presence(fields::REASON_PRESENT);
229 Self::from_error(
230 Error::caused_by(&rocketmq_error::CORE_CONFIGURATION_PARSE_FAILED, source).with_context(context),
231 )
232 }
233
234 #[must_use]
236 pub fn response_process_failed(operation: impl AsRef<str>, reason: impl AsRef<str>) -> Self {
237 let mut context = ErrorContext::new().with_text(fields::OPERATION_DIAGNOSTIC, operation);
238 if !reason.as_ref().is_empty() {
239 context = context.with_secret_presence(fields::REASON_PRESENT);
240 }
241 Self::from_error(Error::new(&rocketmq_error::PROTOCOL_RESPONSE_FAILED).with_context(context))
242 }
243
244 #[must_use]
246 pub fn response_process_source(operation: impl AsRef<str>, source: impl StdError + Send + Sync + 'static) -> Self {
247 let context = ErrorContext::new()
248 .with_text(fields::OPERATION_DIAGNOSTIC, operation)
249 .with_secret_presence(fields::REASON_PRESENT);
250 Self::from_error(Error::caused_by(&rocketmq_error::PROTOCOL_RESPONSE_FAILED, source).with_context(context))
251 }
252
253 #[must_use]
255 pub fn timeout(operation: impl AsRef<str>, timeout_ms: u64) -> Self {
256 let context = ErrorContext::new()
257 .with_text(fields::OPERATION_DIAGNOSTIC, operation)
258 .with_u64(fields::TIMEOUT_MS, timeout_ms);
259 Self::from_error(Error::new(&rocketmq_error::CORE_OPERATION_TIMED_OUT).with_context(context))
260 }
261
262 #[must_use]
264 pub fn internal(operation: impl AsRef<str>, source: impl StdError + Send + Sync + 'static) -> Self {
265 let context = ErrorContext::new()
266 .with_text(fields::OPERATION_DIAGNOSTIC, operation)
267 .with_secret_presence(fields::SOURCE_PRESENT);
268 Self::from_error(Error::caused_by(&rocketmq_error::CORE_INTERNAL_FAILURE, source).with_context(context))
269 }
270
271 #[must_use]
273 pub fn invariant_violated(invariant: impl AsRef<str>) -> Self {
274 let context = ErrorContext::new().with_text(fields::OPERATION_DIAGNOSTIC, invariant);
275 Self::from_error(Error::new(&rocketmq_error::CORE_INTERNAL_FAILURE).with_context(context))
276 }
277
278 #[must_use]
280 pub fn service_failed(operation: impl AsRef<str>) -> Self {
281 let context = ErrorContext::new().with_text(fields::OPERATION_DIAGNOSTIC, operation);
282 Self::from_error(Error::new(&rocketmq_error::CORE_SERVICE_FAILED).with_context(context))
283 }
284
285 #[must_use]
287 pub fn service_source(operation: impl AsRef<str>, source: impl StdError + Send + Sync + 'static) -> Self {
288 let context = ErrorContext::new().with_text(fields::OPERATION_DIAGNOSTIC, operation);
289 Self::from_error(Error::caused_by(&rocketmq_error::CORE_SERVICE_FAILED, source).with_context(context))
290 }
291
292 #[must_use]
294 pub fn invalid_property(property: impl AsRef<str>) -> Self {
295 let context = ErrorContext::new().with_text(fields::PROPERTY, property);
296 Self::from_error(Error::new(&rocketmq_error::PROTOCOL_MESSAGE_PROPERTY_INVALID).with_context(context))
297 }
298
299 #[must_use]
301 pub fn broker_not_found(broker: impl AsRef<str>) -> Self {
302 let context = ErrorContext::new().with_text(fields::BROKER, broker);
303 Self::from_error(Error::new(&rocketmq_error::BROKER_LOOKUP_NOT_FOUND).with_context(context))
304 }
305
306 #[must_use]
308 pub fn broker_operation_failed(operation: impl AsRef<str>, code: i32, message: impl AsRef<str>) -> Self {
309 let mut context = ErrorContext::new()
310 .with_text(fields::OPERATION_DIAGNOSTIC, operation)
311 .with_i64(fields::BROKER_CODE, i64::from(code));
312 if !message.as_ref().is_empty() {
313 context = context.with_secret_presence(fields::MESSAGE_PRESENT);
314 }
315 Self::from_error(Error::new(&rocketmq_error::BROKER_OPERATION_FAILED).with_context(context))
316 }
317
318 #[must_use]
320 pub fn broker_operation_failed_at(
321 operation: impl AsRef<str>,
322 code: i32,
323 message: impl AsRef<str>,
324 broker_addr: Option<impl AsRef<str>>,
325 ) -> Self {
326 let error = Self::broker_operation_failed(operation, code, message);
327 match broker_addr {
328 Some(broker_addr) => error.with_broker_addr(broker_addr),
329 None => error,
330 }
331 }
332
333 #[must_use]
335 pub fn broker_operation_source(
336 operation: impl AsRef<str>,
337 code: i32,
338 broker_addr: Option<impl AsRef<str>>,
339 source: impl StdError + Send + Sync + 'static,
340 ) -> Self {
341 let mut context = ErrorContext::new()
342 .with_text(fields::OPERATION_DIAGNOSTIC, operation)
343 .with_i64(fields::BROKER_CODE, i64::from(code))
344 .with_secret_presence(fields::MESSAGE_PRESENT);
345 if let Some(broker_addr) = broker_addr {
346 context = context.with_text(fields::BROKER_ADDR, broker_addr);
347 }
348 Self::from_error(Error::caused_by(&rocketmq_error::BROKER_OPERATION_FAILED, source).with_context(context))
349 }
350
351 #[must_use]
353 pub fn with_broker_addr(self, broker_addr: impl AsRef<str>) -> Self {
354 match Arc::try_unwrap(self.0) {
355 Ok(error) => {
356 let context = error.context().clone().with_text(fields::BROKER_ADDR, broker_addr);
357 Self::from_error(error.with_context(context))
358 }
359 Err(shared) => Self(shared),
360 }
361 }
362
363 #[must_use]
365 pub fn route_not_found(topic: impl AsRef<str>) -> Self {
366 let context = ErrorContext::new().with_text(fields::TOPIC, topic);
367 Self::from_error(Error::new(&rocketmq_error::ROUTE_TOPIC_NOT_FOUND).with_context(context))
368 }
369
370 #[must_use]
372 pub fn dns_failed() -> Self {
373 Self::from_error(Error::new(&rocketmq_error::TRANSPORT_DNS_FAILED))
374 }
375
376 #[must_use]
378 pub fn dns_failed_source(source: impl StdError + Send + Sync + 'static) -> Self {
379 let context = ErrorContext::new().with_secret_presence(fields::SOURCE_PRESENT);
380 Self::from_error(Error::caused_by(&rocketmq_error::TRANSPORT_DNS_FAILED, source).with_context(context))
381 }
382
383 fn diagnostic_i64(&self, name: &str) -> Option<i64> {
384 self.0
385 .diagnostic_view()
386 .ok()?
387 .fields()
388 .find_map(|field| match (field.name() == name, field.value()) {
389 (true, ViewValueRef::I64(value)) => Some(value),
390 _ => None,
391 })
392 }
393
394 fn diagnostic_text(&self, name: &str) -> Option<&str> {
395 self.0
396 .diagnostic_view()
397 .ok()?
398 .fields()
399 .find_map(|field| match (field.name() == name, field.value()) {
400 (true, ViewValueRef::Text(value)) => Some(value),
401 _ => None,
402 })
403 }
404}
405
406impl From<Error> for ClientError {
407 fn from(error: Error) -> Self {
408 Self::from_error(error)
409 }
410}
411
412impl From<SharedError> for ClientError {
413 fn from(error: SharedError) -> Self {
414 Self::from_shared(error)
415 }
416}
417
418impl From<ClientError> for SharedError {
419 fn from(error: ClientError) -> Self {
420 error.into_shared_error()
421 }
422}
423
424impl AsRef<Error> for ClientError {
425 fn as_ref(&self) -> &Error {
426 self.0.as_ref()
427 }
428}
429
430impl Deref for ClientError {
431 type Target = Error;
432
433 fn deref(&self) -> &Self::Target {
434 self.0.as_ref()
435 }
436}
437
438impl fmt::Display for ClientError {
439 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
440 self.0.fmt(formatter)
441 }
442}
443
444impl fmt::Debug for ClientError {
445 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
446 self.0.fmt(formatter)
447 }
448}
449
450impl StdError for ClientError {
451 fn source(&self) -> Option<&(dyn StdError + 'static)> {
452 Some(self.0.as_ref())
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn into_error_unwraps_sole_owner_preserving_descriptor_and_typed_cause() {
462 let io_error = std::io::Error::new(std::io::ErrorKind::ConnectionReset, "reset");
463 let client_error = ClientError::response_process_source("some.operation", io_error);
464 let descriptor = client_error.descriptor();
465
466 let error = client_error.into_error();
467
468 assert_eq!(error.descriptor().code(), descriptor.code());
469 let cause = StdError::source(&error).expect("typed cause retained for sole owner");
470 let io_cause = cause.downcast_ref::<std::io::Error>().expect("io error cause retained");
471 assert_eq!(io_cause.kind(), std::io::ErrorKind::ConnectionReset);
472 }
473
474 #[test]
475 fn into_error_wraps_shared_owner_preserving_typed_cause_through_extra_layer() {
476 let io_error = std::io::Error::new(std::io::ErrorKind::ConnectionReset, "reset");
477 let client_error = ClientError::response_process_source("some.operation", io_error);
478 let descriptor = client_error.descriptor();
479 let clone = client_error.clone();
480
481 let error = client_error.into_error();
482
483 assert_eq!(error.descriptor().code(), descriptor.code());
484 let outer_source = StdError::source(&error).expect("wrapper source present for shared owner");
485 let wrapped_client_error = outer_source
486 .downcast_ref::<ClientError>()
487 .expect("wrapped ClientError retained as the extra layer's source");
488 let io_cause = wrapped_client_error
489 .source_ref::<std::io::Error>()
490 .expect("original typed cause reachable through the extra wrapper");
491 assert_eq!(io_cause.kind(), std::io::ErrorKind::ConnectionReset);
492 drop(clone);
493 }
494
495 #[test]
496 fn into_error_leaves_retained_clone_usable_with_unchanged_shared_identity() {
497 let client_error = ClientError::illegal_argument("boom");
498 let clone = client_error.clone();
499 let original_shared_ptr = Arc::as_ptr(clone.shared_error());
500
501 let _ = client_error.into_error();
502
503 assert!(clone.is(&rocketmq_error::CORE_ARGUMENT_INVALID));
504 assert_eq!(Arc::as_ptr(clone.shared_error()), original_shared_ptr);
505 }
506
507 #[test]
508 fn from_shared_and_into_shared_error_round_trip_preserves_arc_identity() {
509 let canonical: SharedError = Arc::new(Error::new(&rocketmq_error::CORE_ARGUMENT_INVALID));
510 let client_error = ClientError::from_shared(Arc::clone(&canonical));
511
512 let shared = client_error.into_shared_error();
513
514 assert!(Arc::ptr_eq(&canonical, &shared));
515 }
516
517 #[test]
518 fn source_ref_returns_none_for_unrelated_source_type() {
519 let client_error = ClientError::illegal_argument("boom");
520 assert!(client_error.source_ref::<std::io::Error>().is_none());
521 }
522}