rocketmq_remoting/protocol/
namespace_util.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17use rocketmq_common::common::mix_all;
18use rocketmq_common::common::topic::TopicValidator;
19
20pub struct NamespaceUtil;
21
22impl NamespaceUtil {
23    pub const DLQ_PREFIX_LENGTH: usize = mix_all::DLQ_GROUP_TOPIC_PREFIX.len();
24    pub const NAMESPACE_SEPARATOR: char = '%';
25    pub const RETRY_PREFIX_LENGTH: usize = mix_all::RETRY_GROUP_TOPIC_PREFIX.len();
26    pub const STRING_BLANK: &'static str = "";
27
28    pub fn without_namespace(resource_with_namespace: &str) -> String {
29        if resource_with_namespace.is_empty()
30            || NamespaceUtil::is_system_resource(resource_with_namespace)
31        {
32            return resource_with_namespace.to_string();
33        }
34
35        let mut string_builder = String::new();
36        if NamespaceUtil::is_retry_topic(resource_with_namespace) {
37            string_builder.push_str(mix_all::RETRY_GROUP_TOPIC_PREFIX);
38        }
39        if NamespaceUtil::is_dlq_topic(resource_with_namespace) {
40            string_builder.push_str(mix_all::DLQ_GROUP_TOPIC_PREFIX);
41        }
42
43        if let Some(index) = NamespaceUtil::without_retry_and_dlq(resource_with_namespace)
44            .find(NamespaceUtil::NAMESPACE_SEPARATOR)
45        {
46            let resource_without_namespace =
47                &NamespaceUtil::without_retry_and_dlq(resource_with_namespace)[index + 1..];
48            return string_builder + resource_without_namespace;
49        }
50
51        resource_with_namespace.to_string()
52    }
53
54    pub fn without_namespace_with_namespace(
55        resource_with_namespace: &str,
56        namespace: &str,
57    ) -> String {
58        if resource_with_namespace.is_empty() || namespace.is_empty() {
59            return resource_with_namespace.to_string();
60        }
61
62        let resource_without_retry_and_dlq =
63            NamespaceUtil::without_retry_and_dlq(resource_with_namespace);
64        if resource_without_retry_and_dlq.starts_with(&format!(
65            "{}{}",
66            namespace,
67            NamespaceUtil::NAMESPACE_SEPARATOR
68        )) {
69            return NamespaceUtil::without_namespace(resource_with_namespace);
70        }
71
72        resource_with_namespace.to_string()
73    }
74
75    pub fn wrap_namespace(namespace: &str, resource_without_namespace: &str) -> String {
76        if namespace.is_empty() || resource_without_namespace.is_empty() {
77            return resource_without_namespace.to_string();
78        }
79
80        if NamespaceUtil::is_system_resource(resource_without_namespace)
81            || NamespaceUtil::is_already_with_namespace(resource_without_namespace, namespace)
82        {
83            return resource_without_namespace.to_string();
84        }
85
86        let mut string_builder = String::new();
87
88        if NamespaceUtil::is_retry_topic(resource_without_namespace) {
89            string_builder.push_str(mix_all::RETRY_GROUP_TOPIC_PREFIX);
90        }
91
92        if NamespaceUtil::is_dlq_topic(resource_without_namespace) {
93            string_builder.push_str(mix_all::DLQ_GROUP_TOPIC_PREFIX);
94        }
95        let resource_without_retry_and_dlq =
96            NamespaceUtil::without_retry_and_dlq(resource_without_namespace);
97        string_builder
98            + namespace
99            + &NamespaceUtil::NAMESPACE_SEPARATOR.to_string()
100            + resource_without_retry_and_dlq
101    }
102
103    pub fn is_already_with_namespace(resource: &str, namespace: &str) -> bool {
104        if namespace.is_empty()
105            || resource.is_empty()
106            || NamespaceUtil::is_system_resource(resource)
107        {
108            return false;
109        }
110
111        let resource_without_retry_and_dlq = NamespaceUtil::without_retry_and_dlq(resource);
112
113        resource_without_retry_and_dlq.starts_with(&format!(
114            "{}{}",
115            namespace,
116            NamespaceUtil::NAMESPACE_SEPARATOR
117        ))
118    }
119
120    pub fn wrap_namespace_and_retry(namespace: &str, consumer_group: &str) -> Option<String> {
121        if consumer_group.is_empty() {
122            return None;
123        }
124
125        Some(
126            mix_all::RETRY_GROUP_TOPIC_PREFIX.to_string()
127                + &NamespaceUtil::wrap_namespace(namespace, consumer_group),
128        )
129    }
130
131    pub fn get_namespace_from_resource(resource: &str) -> String {
132        if resource.is_empty() || NamespaceUtil::is_system_resource(resource) {
133            return NamespaceUtil::STRING_BLANK.to_string();
134        }
135        let resource_without_retry_and_dlq = NamespaceUtil::without_retry_and_dlq(resource);
136        if let Some(index) = resource_without_retry_and_dlq.find(NamespaceUtil::NAMESPACE_SEPARATOR)
137        {
138            return resource_without_retry_and_dlq[..index].to_string();
139        }
140
141        NamespaceUtil::STRING_BLANK.to_string()
142    }
143
144    fn without_retry_and_dlq(original_resource: &str) -> &str {
145        if original_resource.is_empty() {
146            return NamespaceUtil::STRING_BLANK;
147        }
148        if NamespaceUtil::is_retry_topic(original_resource) {
149            return &original_resource[NamespaceUtil::RETRY_PREFIX_LENGTH..];
150        }
151
152        if NamespaceUtil::is_dlq_topic(original_resource) {
153            return &original_resource[NamespaceUtil::DLQ_PREFIX_LENGTH..];
154        }
155
156        original_resource
157    }
158
159    fn is_system_resource(resource: &str) -> bool {
160        if resource.is_empty() {
161            return false;
162        }
163        TopicValidator::is_system_topic(resource) || mix_all::is_sys_consumer_group(resource)
164    }
165
166    #[inline]
167    pub fn is_retry_topic(resource: &str) -> bool {
168        !resource.is_empty() && resource.starts_with(mix_all::RETRY_GROUP_TOPIC_PREFIX)
169    }
170
171    fn is_dlq_topic(resource: &str) -> bool {
172        !resource.is_empty() && resource.starts_with(mix_all::DLQ_GROUP_TOPIC_PREFIX)
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn without_namespace_returns_original_when_empty() {
182        assert_eq!(NamespaceUtil::without_namespace(""), "");
183    }
184
185    #[test]
186    fn without_namespace_returns_original_when_system_resource() {
187        assert_eq!(NamespaceUtil::without_namespace("SYS_TOPIC"), "SYS_TOPIC");
188    }
189
190    #[test]
191    fn without_namespace_removes_namespace() {
192        assert_eq!(
193            NamespaceUtil::without_namespace("my_namespace%my_resource"),
194            "my_resource"
195        );
196    }
197
198    #[test]
199    fn without_namespace_with_namespace_returns_original_when_empty() {
200        assert_eq!(
201            NamespaceUtil::without_namespace_with_namespace("", "my_namespace"),
202            ""
203        );
204    }
205
206    #[test]
207    fn without_namespace_with_namespace_removes_namespace() {
208        assert_eq!(
209            NamespaceUtil::without_namespace_with_namespace(
210                "my_namespace%my_resource",
211                "my_namespace"
212            ),
213            "my_resource"
214        );
215    }
216
217    #[test]
218    fn wrap_namespace_returns_original_when_empty() {
219        assert_eq!(NamespaceUtil::wrap_namespace("my_namespace", ""), "");
220    }
221
222    #[test]
223    fn wrap_namespace_adds_namespace() {
224        assert_eq!(
225            NamespaceUtil::wrap_namespace("my_namespace", "my_resource"),
226            "my_namespace%my_resource"
227        );
228    }
229
230    #[test]
231    fn is_already_with_namespace_returns_false_when_empty() {
232        assert!(!NamespaceUtil::is_already_with_namespace(
233            "",
234            "my_namespace"
235        ));
236    }
237
238    #[test]
239    fn is_already_with_namespace_returns_true_when_with_namespace() {
240        assert!(NamespaceUtil::is_already_with_namespace(
241            "my_namespace%my_resource",
242            "my_namespace"
243        ));
244    }
245
246    #[test]
247    fn wrap_namespace_and_retry_returns_none_when_empty() {
248        assert_eq!(
249            NamespaceUtil::wrap_namespace_and_retry("my_namespace", ""),
250            None
251        );
252    }
253
254    #[test]
255    fn wrap_namespace_and_retry_adds_namespace_and_retry() {
256        assert_eq!(
257            NamespaceUtil::wrap_namespace_and_retry("my_namespace", "my_group"),
258            Some("%RETRY%my_namespace%my_group".to_string())
259        );
260    }
261
262    #[test]
263    fn get_namespace_from_resource_returns_blank_when_empty() {
264        assert_eq!(NamespaceUtil::get_namespace_from_resource(""), "");
265    }
266
267    #[test]
268    fn get_namespace_from_resource_returns_namespace() {
269        assert_eq!(
270            NamespaceUtil::get_namespace_from_resource("my_namespace%my_resource"),
271            "my_namespace"
272        );
273    }
274
275    #[test]
276    fn without_retry_and_dlq_returns_original_when_empty() {
277        assert_eq!(NamespaceUtil::without_retry_and_dlq(""), "");
278    }
279
280    #[test]
281    fn without_retry_and_dlq_removes_retry_and_dlq() {
282        assert_eq!(
283            NamespaceUtil::without_retry_and_dlq("RETRY_GROUP_TOPIC_PREFIXmy_resource"),
284            "RETRY_GROUP_TOPIC_PREFIXmy_resource"
285        );
286        assert_eq!(
287            NamespaceUtil::without_retry_and_dlq("DLQ_GROUP_TOPIC_PREFIXmy_resource"),
288            "DLQ_GROUP_TOPIC_PREFIXmy_resource"
289        );
290    }
291
292    #[test]
293    fn is_system_resource_returns_false_when_empty() {
294        assert!(!NamespaceUtil::is_system_resource(""));
295    }
296
297    #[test]
298    fn is_system_resource_returns_true_when_system_resource() {
299        assert!(NamespaceUtil::is_system_resource("CID_RMQ_SYS_"));
300        assert!(NamespaceUtil::is_system_resource("TBW102"));
301    }
302
303    #[test]
304    fn is_retry_topic_returns_false_when_empty() {
305        assert!(!NamespaceUtil::is_retry_topic(""));
306    }
307
308    #[test]
309    fn is_retry_topic_returns_true_when_retry_topic() {
310        assert!(!NamespaceUtil::is_retry_topic(
311            "RETRY_GROUP_TOPIC_PREFIXmy_topic"
312        ));
313        assert!(NamespaceUtil::is_retry_topic(
314            "%RETRY%RETRY_GROUP_TOPIC_PREFIXmy_topic"
315        ));
316    }
317
318    #[test]
319    fn is_dlq_topic_returns_false_when_empty() {
320        assert!(!NamespaceUtil::is_dlq_topic(""));
321    }
322
323    #[test]
324    fn is_dlq_topic_returns_true_when_dlq_topic() {
325        assert!(!NamespaceUtil::is_dlq_topic(
326            "DLQ_GROUP_TOPIC_PREFIXmy_topic"
327        ));
328        assert!(NamespaceUtil::is_dlq_topic(
329            "%DLQ%DLQ_GROUP_TOPIC_PREFIXmy_topic"
330        ));
331    }
332}