rocketmq_client_rust/admin/
mq_admin_infrastructure_observation_read_ext.rs1use std::collections::HashMap;
18use std::time::Duration;
19
20use crate::ClientError;
21use cheetah_string::CheetahString;
22use rocketmq_protocol::protocol::body::broker_replicas_info::BrokerReplicasInfo;
23use rocketmq_protocol::protocol::body::ha_runtime_info::HARuntimeInfo;
24use rocketmq_protocol::protocol::header::get_meta_data_response_header::GetMetaDataResponseHeader;
25
26use super::default_mq_admin_ext::DefaultMQAdminExt;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum InfrastructureObservationReadErrorCode {
31 SourceUnavailable,
32 Timeout,
33 PermissionDenied,
34 NotFound,
35 RateLimited,
36 InvalidResponse,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct InfrastructureObservationReadError {
42 code: InfrastructureObservationReadErrorCode,
43 retryable: bool,
44}
45
46impl InfrastructureObservationReadError {
47 pub const fn new(code: InfrastructureObservationReadErrorCode, retryable: bool) -> Self {
48 Self { code, retryable }
49 }
50
51 pub const fn code(self) -> InfrastructureObservationReadErrorCode {
52 self.code
53 }
54
55 pub const fn retryable(self) -> bool {
56 self.retryable
57 }
58}
59
60impl std::fmt::Display for InfrastructureObservationReadError {
61 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(formatter, "infrastructure observation failed: {:?}", self.code)
63 }
64}
65
66impl std::error::Error for InfrastructureObservationReadError {}
67
68#[allow(async_fn_in_trait)]
73pub trait MQAdminInfrastructureObservationReadExt: Send {
74 async fn broker_ha_runtime_at(
75 &self,
76 broker_endpoint: CheetahString,
77 ) -> Result<HARuntimeInfo, InfrastructureObservationReadError>;
78
79 async fn controller_sync_state_at(
80 &self,
81 controller_endpoint: CheetahString,
82 broker_names: Vec<CheetahString>,
83 ) -> Result<BrokerReplicasInfo, InfrastructureObservationReadError>;
84
85 async fn controller_metadata_at(
86 &self,
87 controller_endpoint: CheetahString,
88 ) -> Result<GetMetaDataResponseHeader, InfrastructureObservationReadError>;
89
90 async fn nameserver_config_at(
91 &self,
92 nameserver_endpoint: CheetahString,
93 ) -> Result<HashMap<CheetahString, CheetahString>, InfrastructureObservationReadError>;
94}
95
96impl MQAdminInfrastructureObservationReadExt for DefaultMQAdminExt {
97 async fn broker_ha_runtime_at(
98 &self,
99 broker_endpoint: CheetahString,
100 ) -> Result<HARuntimeInfo, InfrastructureObservationReadError> {
101 self.inner()
102 .mq_client_api()
103 .map_err(sanitized_error)?
104 .get_broker_ha_status(
105 broker_endpoint,
106 self.inner().remoting_timeout_millis().map_err(sanitized_error)?,
107 )
108 .await
109 .map_err(sanitized_error)
110 }
111
112 async fn controller_sync_state_at(
113 &self,
114 controller_endpoint: CheetahString,
115 broker_names: Vec<CheetahString>,
116 ) -> Result<BrokerReplicasInfo, InfrastructureObservationReadError> {
117 self.inner()
118 .mq_client_api()
119 .map_err(sanitized_error)?
120 .get_in_sync_state_data_at(
121 controller_endpoint,
122 broker_names,
123 self.inner().remoting_timeout_millis().map_err(sanitized_error)?,
124 )
125 .await
126 .map_err(sanitized_error)
127 }
128
129 async fn controller_metadata_at(
130 &self,
131 controller_endpoint: CheetahString,
132 ) -> Result<GetMetaDataResponseHeader, InfrastructureObservationReadError> {
133 self.inner()
134 .mq_client_api()
135 .map_err(sanitized_error)?
136 .get_controller_metadata(
137 controller_endpoint,
138 self.inner().remoting_timeout_millis().map_err(sanitized_error)?,
139 )
140 .await
141 .map_err(sanitized_error)
142 }
143
144 async fn nameserver_config_at(
145 &self,
146 nameserver_endpoint: CheetahString,
147 ) -> Result<HashMap<CheetahString, CheetahString>, InfrastructureObservationReadError> {
148 let mut responses = self
149 .inner()
150 .mq_client_api()
151 .map_err(sanitized_error)?
152 .get_name_server_config(
153 Some(vec![nameserver_endpoint.clone()]),
154 Duration::from_millis(self.inner().remoting_timeout_millis().map_err(sanitized_error)?),
155 )
156 .await
157 .map_err(sanitized_error)?
158 .ok_or_else(invalid_response)?;
159 if responses.len() != 1 {
160 return Err(invalid_response());
161 }
162 responses.remove(&nameserver_endpoint).ok_or_else(invalid_response)
163 }
164}
165
166fn sanitized_error(error: ClientError) -> InfrastructureObservationReadError {
167 let descriptor = error.descriptor();
168 let code = match descriptor.projection().http().status.as_u16() {
169 401 | 403 => InfrastructureObservationReadErrorCode::PermissionDenied,
170 404 => InfrastructureObservationReadErrorCode::NotFound,
171 408 | 504 => InfrastructureObservationReadErrorCode::Timeout,
172 429 => InfrastructureObservationReadErrorCode::RateLimited,
173 400 | 413 | 422 => InfrastructureObservationReadErrorCode::InvalidResponse,
174 _ => InfrastructureObservationReadErrorCode::SourceUnavailable,
175 };
176 let retryable = matches!(
177 descriptor.recovery_hint(),
178 rocketmq_error::RecoveryHint::Backoff
179 | rocketmq_error::RecoveryHint::RefreshRoute
180 | rocketmq_error::RecoveryHint::RefreshLeader
181 | rocketmq_error::RecoveryHint::SwitchBroker
182 );
183 InfrastructureObservationReadError::new(code, retryable)
184}
185
186const fn invalid_response() -> InfrastructureObservationReadError {
187 InfrastructureObservationReadError::new(InfrastructureObservationReadErrorCode::InvalidResponse, false)
188}
189
190#[cfg(test)]
191mod tests {
192 use std::sync::Arc;
193
194 use super::*;
195 use rocketmq_error::Error;
196 use rocketmq_error::TRANSPORT_CONNECTION_FAILED;
197
198 #[test]
199 fn public_error_is_typed_and_never_retains_endpoint_detail() {
200 let error = sanitized_error(ClientError::from_shared(Arc::new(Error::caused_by(
201 &TRANSPORT_CONNECTION_FAILED,
202 std::io::Error::other("private-controller.internal:9878 at 10.23.45.67:10911"),
203 ))));
204 assert_eq!(error.code(), InfrastructureObservationReadErrorCode::SourceUnavailable);
205 let rendered = format!("{error:?} {error}");
206 assert!(!rendered.contains("10.23.45.67"));
207 assert!(!rendered.contains("private-controller"));
208 }
209}