Skip to main content

rocketmq_model/
lite.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
15pub const SEPARATOR: char = '$';
16pub const LITE_TOPIC_PREFIX: &str = "%LMQ%$";
17
18pub fn to_lmq_name(parent_topic: &str, lite_topic: &str) -> Option<String> {
19    if parent_topic.is_empty() || lite_topic.is_empty() {
20        return None;
21    }
22    Some(format!("{}{}${}", LITE_TOPIC_PREFIX, parent_topic, lite_topic))
23}
24
25pub fn is_lite_topic_queue(lmq_name: &str) -> bool {
26    !lmq_name.is_empty() && lmq_name.starts_with(LITE_TOPIC_PREFIX)
27}
28
29pub fn get_parent_topic(lmq_name: &str) -> Option<String> {
30    if !is_lite_topic_queue(lmq_name) {
31        return None;
32    }
33
34    let remaining = &lmq_name[LITE_TOPIC_PREFIX.len()..];
35    let index = remaining.find(SEPARATOR)?;
36
37    if index == 0 || index == remaining.len() - 1 {
38        return None;
39    }
40
41    // Check that there's only one separator after the prefix
42    if remaining[index + 1..].contains(SEPARATOR) {
43        return None;
44    }
45
46    Some(remaining[..index].to_string())
47}
48
49pub fn get_lite_topic(lmq_name: &str) -> Option<String> {
50    if !is_lite_topic_queue(lmq_name) {
51        return None;
52    }
53
54    let remaining = &lmq_name[LITE_TOPIC_PREFIX.len()..];
55    let index = remaining.find(SEPARATOR)?;
56
57    if index == 0 || index == remaining.len() - 1 {
58        return None;
59    }
60
61    // Check that there's only one separator after the prefix
62    if remaining[index + 1..].contains(SEPARATOR) {
63        return None;
64    }
65
66    Some(remaining[index + 1..].to_string())
67}
68
69pub fn get_parent_and_lite_topic(lmq_name: &str) -> Option<(String, String)> {
70    if !lmq_name.starts_with(LITE_TOPIC_PREFIX) {
71        return None;
72    }
73
74    let remaining = &lmq_name[LITE_TOPIC_PREFIX.len()..];
75    let parts: Vec<&str> = remaining.split(SEPARATOR).collect();
76
77    if parts.len() != 2 {
78        return None;
79    }
80
81    if parts[0].is_empty() || parts[1].is_empty() {
82        return None;
83    }
84
85    Some((parts[0].to_string(), parts[1].to_string()))
86}
87
88pub fn belongs_to(lmq_name: &str, parent_topic: &str) -> bool {
89    let prefix = format!("{}{}{}", LITE_TOPIC_PREFIX, parent_topic, SEPARATOR);
90    lmq_name.starts_with(&prefix)
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_to_lmq_name() {
99        let result = to_lmq_name("parent", "child");
100        assert!(result.is_some());
101        let lmq_name = result.unwrap();
102        assert!(lmq_name.contains("parent"));
103        assert!(lmq_name.contains("child"));
104        assert!(lmq_name.contains("$"));
105    }
106
107    #[test]
108    fn test_to_lmq_name_empty_parent() {
109        let result = to_lmq_name("", "child");
110        assert!(result.is_none());
111    }
112
113    #[test]
114    fn test_to_lmq_name_empty_lite_topic() {
115        let result = to_lmq_name("parent", "");
116        assert!(result.is_none());
117    }
118
119    #[test]
120    fn test_is_lite_topic_queue_valid() {
121        let lmq_name = to_lmq_name("parent", "child").unwrap();
122        assert!(is_lite_topic_queue(&lmq_name));
123    }
124
125    #[test]
126    fn test_is_lite_topic_queue_invalid() {
127        assert!(!is_lite_topic_queue("regular_topic"));
128        assert!(!is_lite_topic_queue(""));
129    }
130
131    #[test]
132    fn test_get_parent_topic() {
133        let lmq_name = to_lmq_name("my_parent", "my_child").unwrap();
134        let parent = get_parent_topic(&lmq_name);
135        assert_eq!(parent, Some("my_parent".to_string()));
136    }
137
138    #[test]
139    fn test_get_parent_topic_not_lite() {
140        let parent = get_parent_topic("regular_topic");
141        assert!(parent.is_none());
142    }
143
144    #[test]
145    fn test_get_lite_topic() {
146        let lmq_name = to_lmq_name("my_parent", "my_child").unwrap();
147        let lite = get_lite_topic(&lmq_name);
148        assert_eq!(lite, Some("my_child".to_string()));
149    }
150
151    #[test]
152    fn test_get_lite_topic_not_lite() {
153        let lite = get_lite_topic("regular_topic");
154        assert!(lite.is_none());
155    }
156
157    #[test]
158    fn test_get_parent_and_lite_topic() {
159        let lmq_name = to_lmq_name("parent_test", "child_test").unwrap();
160        let pair = get_parent_and_lite_topic(&lmq_name);
161        assert!(pair.is_some());
162        let (parent, child) = pair.unwrap();
163        assert_eq!(parent, "parent_test");
164        assert_eq!(child, "child_test");
165    }
166
167    #[test]
168    fn test_get_parent_and_lite_topic_invalid() {
169        let pair = get_parent_and_lite_topic("not_a_lite_topic");
170        assert!(pair.is_none());
171    }
172
173    #[test]
174    fn test_belongs_to_true() {
175        let lmq_name = to_lmq_name("my_parent", "my_child").unwrap();
176        assert!(belongs_to(&lmq_name, "my_parent"));
177    }
178
179    #[test]
180    fn test_belongs_to_false() {
181        let lmq_name = to_lmq_name("parent1", "child1").unwrap();
182        assert!(!belongs_to(&lmq_name, "parent2"));
183    }
184
185    #[test]
186    fn test_belongs_to_invalid() {
187        assert!(!belongs_to("regular_topic", "parent"));
188    }
189
190    #[test]
191    fn test_roundtrip() {
192        let original_parent = "test_parent";
193        let original_child = "test_child";
194
195        let lmq_name = to_lmq_name(original_parent, original_child).unwrap();
196
197        let extracted_parent = get_parent_topic(&lmq_name).unwrap();
198        let extracted_child = get_lite_topic(&lmq_name).unwrap();
199
200        assert_eq!(extracted_parent, original_parent);
201        assert_eq!(extracted_child, original_child);
202    }
203
204    #[test]
205    fn test_special_characters_in_names() {
206        let parent = "parent-123";
207        let child = "child_456";
208
209        let lmq_name = to_lmq_name(parent, child).unwrap();
210
211        assert_eq!(get_parent_topic(&lmq_name), Some(parent.to_string()));
212        assert_eq!(get_lite_topic(&lmq_name), Some(child.to_string()));
213    }
214
215    #[test]
216    fn test_multiple_separators_in_name() {
217        // Create a malformed lmq_name with extra separators
218        let malformed = format!("{}parent{}child{}", LITE_TOPIC_PREFIX, SEPARATOR, SEPARATOR);
219
220        assert!(is_lite_topic_queue(&malformed));
221        // But parsing should fail due to multiple separators
222        assert!(get_parent_topic(&malformed).is_none());
223        assert!(get_lite_topic(&malformed).is_none());
224    }
225}