Skip to main content

rocketmq_error/unified/
tools.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Tools and Admin operation specific errors
16//!
17//! This module contains error types specific to RocketMQ admin tools and CLI operations.
18
19use thiserror::Error;
20
21/// Tools-specific errors for admin operations
22#[derive(Debug, Error)]
23pub enum ToolsError {
24    // ============================================================================
25    // Topic Management Errors
26    // ============================================================================
27    /// Topic not found
28    #[error("Topic '{topic}' not found")]
29    TopicNotFound { topic: String },
30
31    /// Topic already exists
32    #[error("Topic '{topic}' already exists")]
33    TopicAlreadyExists { topic: String },
34
35    /// Invalid topic configuration
36    #[error("Invalid topic configuration: {reason}")]
37    TopicInvalid { reason: String },
38
39    // ============================================================================
40    // Cluster Management Errors
41    // ============================================================================
42    /// Cluster not found
43    #[error("Cluster '{cluster}' not found")]
44    ClusterNotFound { cluster: String },
45
46    /// Invalid cluster configuration
47    #[error("Invalid cluster configuration: {reason}")]
48    ClusterInvalid { reason: String },
49
50    // ============================================================================
51    // Broker Management Errors
52    // ============================================================================
53    /// Broker not found
54    #[error("Broker '{broker}' not found")]
55    BrokerNotFound { broker: String },
56
57    /// Broker offline
58    #[error("Broker '{broker}' is offline")]
59    BrokerOffline { broker: String },
60
61    // ============================================================================
62    // Consumer Management Errors
63    // ============================================================================
64    /// Consumer group not found
65    #[error("Consumer group '{group}' not found")]
66    ConsumerGroupNotFound { group: String },
67
68    /// Consumer offline
69    #[error("Consumer '{consumer}' is offline")]
70    ConsumerOffline { consumer: String },
71
72    // ============================================================================
73    // NameServer Management Errors
74    // ============================================================================
75    /// NameServer unreachable
76    #[error("NameServer '{addr}' is unreachable")]
77    NameServerUnreachable { addr: String },
78
79    /// NameServer configuration invalid
80    #[error("Invalid NameServer configuration: {reason}")]
81    NameServerConfigInvalid { reason: String },
82
83    // ============================================================================
84    // Configuration Errors
85    // ============================================================================
86    /// Invalid configuration field
87    #[error("Invalid configuration for '{field}': {reason}")]
88    InvalidConfiguration { field: String, reason: String },
89
90    /// Missing required field
91    #[error("Missing required field: '{field}'")]
92    MissingRequiredField { field: String },
93
94    // ============================================================================
95    // Validation Errors
96    // ============================================================================
97    /// Input validation failed
98    #[error("Validation failed for '{field}': {reason}")]
99    ValidationError { field: String, reason: String },
100
101    /// Generic validation error
102    #[error("Validation error: {message}")]
103    ValidationFailed { message: String },
104
105    // ============================================================================
106    // Permission Errors
107    // ============================================================================
108    /// Permission denied for operation
109    #[error("Permission denied for operation: {operation}")]
110    PermissionDenied { operation: String },
111
112    /// Invalid permission value
113    #[error("Invalid permission value: {value}, allowed values: {}", .allowed.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(", "))]
114    InvalidPermission { value: i32, allowed: Vec<i32> },
115
116    // ============================================================================
117    // Operation Errors
118    // ============================================================================
119    /// Operation timeout
120    #[error("Operation '{operation}' timed out after {duration_ms}ms")]
121    OperationTimeout { operation: String, duration_ms: u64 },
122
123    /// Generic internal error
124    #[error("Internal error: {message}")]
125    Internal { message: String },
126}
127
128impl ToolsError {
129    // ============================================================================
130    // Convenience Constructors
131    // ============================================================================
132
133    /// Create a topic not found error
134    #[inline]
135    pub fn topic_not_found(topic: impl Into<String>) -> Self {
136        Self::TopicNotFound { topic: topic.into() }
137    }
138
139    /// Create a topic already exists error
140    #[inline]
141    pub fn topic_already_exists(topic: impl Into<String>) -> Self {
142        Self::TopicAlreadyExists { topic: topic.into() }
143    }
144
145    /// Create a cluster not found error
146    #[inline]
147    pub fn cluster_not_found(cluster: impl Into<String>) -> Self {
148        Self::ClusterNotFound {
149            cluster: cluster.into(),
150        }
151    }
152
153    /// Create a broker not found error
154    #[inline]
155    pub fn broker_not_found(broker: impl Into<String>) -> Self {
156        Self::BrokerNotFound { broker: broker.into() }
157    }
158
159    /// Create a validation error
160    #[inline]
161    pub fn validation_error(field: impl Into<String>, reason: impl Into<String>) -> Self {
162        Self::ValidationError {
163            field: field.into(),
164            reason: reason.into(),
165        }
166    }
167
168    /// Create a nameserver unreachable error
169    #[inline]
170    pub fn nameserver_unreachable(addr: impl Into<String>) -> Self {
171        Self::NameServerUnreachable { addr: addr.into() }
172    }
173
174    /// Create a nameserver config invalid error
175    #[inline]
176    pub fn nameserver_config_invalid(reason: impl Into<String>) -> Self {
177        Self::NameServerConfigInvalid { reason: reason.into() }
178    }
179
180    /// Create an internal error
181    #[inline]
182    pub fn internal(message: impl Into<String>) -> Self {
183        Self::Internal {
184            message: message.into(),
185        }
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_topic_management_errors() {
195        let err = ToolsError::topic_not_found("TestTopic");
196        assert_eq!(err.to_string(), "Topic 'TestTopic' not found");
197
198        let err = ToolsError::topic_already_exists("TestTopic");
199        assert_eq!(err.to_string(), "Topic 'TestTopic' already exists");
200
201        let err = ToolsError::TopicInvalid {
202            reason: "invalid partitions".to_string(),
203        };
204        assert_eq!(err.to_string(), "Invalid topic configuration: invalid partitions");
205    }
206
207    #[test]
208    fn test_cluster_management_errors() {
209        let err = ToolsError::cluster_not_found("TestCluster");
210        assert_eq!(err.to_string(), "Cluster 'TestCluster' not found");
211
212        let err = ToolsError::ClusterInvalid {
213            reason: "missing brokers".to_string(),
214        };
215        assert_eq!(err.to_string(), "Invalid cluster configuration: missing brokers");
216    }
217
218    #[test]
219    fn test_broker_management_errors() {
220        let err = ToolsError::broker_not_found("broker-a");
221        assert_eq!(err.to_string(), "Broker 'broker-a' not found");
222
223        let err = ToolsError::BrokerOffline {
224            broker: "broker-a".to_string(),
225        };
226        assert_eq!(err.to_string(), "Broker 'broker-a' is offline");
227    }
228
229    #[test]
230    fn test_consumer_management_errors() {
231        let err = ToolsError::ConsumerGroupNotFound {
232            group: "test-group".to_string(),
233        };
234        assert_eq!(err.to_string(), "Consumer group 'test-group' not found");
235
236        let err = ToolsError::ConsumerOffline {
237            consumer: "consumer-1".to_string(),
238        };
239        assert_eq!(err.to_string(), "Consumer 'consumer-1' is offline");
240    }
241
242    #[test]
243    fn test_nameserver_management_errors() {
244        let err = ToolsError::nameserver_unreachable("127.0.0.1:9876");
245        assert_eq!(err.to_string(), "NameServer '127.0.0.1:9876' is unreachable");
246
247        let err = ToolsError::nameserver_config_invalid("missing nameserver");
248        assert_eq!(err.to_string(), "Invalid NameServer configuration: missing nameserver");
249    }
250
251    #[test]
252    fn test_configuration_errors() {
253        let err = ToolsError::InvalidConfiguration {
254            field: "name_server".to_string(),
255            reason: "missing nameserver".to_string(),
256        };
257        assert_eq!(
258            err.to_string(),
259            "Invalid configuration for 'name_server': missing nameserver"
260        );
261
262        let err = ToolsError::MissingRequiredField {
263            field: "topic".to_string(),
264        };
265        assert_eq!(err.to_string(), "Missing required field: 'topic'");
266    }
267
268    #[test]
269    fn test_validation_errors() {
270        let err = ToolsError::validation_error("topic_name", "name too long");
271        assert_eq!(err.to_string(), "Validation failed for 'topic_name': name too long");
272
273        let err = ToolsError::ValidationFailed {
274            message: "generic validation error".to_string(),
275        };
276        assert_eq!(err.to_string(), "Validation error: generic validation error");
277    }
278
279    #[test]
280    fn test_permission_errors() {
281        let err = ToolsError::PermissionDenied {
282            operation: "createTopic".to_string(),
283        };
284        assert_eq!(err.to_string(), "Permission denied for operation: createTopic");
285
286        let err = ToolsError::InvalidPermission {
287            value: 1,
288            allowed: vec![2, 4, 6],
289        };
290        assert!(err.to_string().contains("Invalid permission value: 1"));
291        assert!(err.to_string().contains("2, 4, 6"));
292    }
293
294    #[test]
295    fn test_operation_errors() {
296        let err = ToolsError::OperationTimeout {
297            operation: "createTopic".to_string(),
298            duration_ms: 5000,
299        };
300        assert!(err
301            .to_string()
302            .contains("Operation 'createTopic' timed out after 5000ms"));
303
304        let err = ToolsError::internal("unexpected error");
305        assert!(err.to_string().contains("Internal error: unexpected error"));
306    }
307}