Skip to main content

rocketmq_model/common/attribute/
attribute_parser.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
15use std::collections::HashMap;
16
17const ATTR_ARRAY_SEPARATOR_COMMA: &str = ",";
18const ATTR_KEY_VALUE_EQUAL_SIGN: &str = "=";
19const ATTR_ADD_PLUS_SIGN: &str = "+";
20const ATTR_DELETE_MINUS_SIGN: &str = "-";
21
22#[derive(Debug)]
23pub struct AttributeParser;
24
25impl AttributeParser {
26    pub fn parse_to_map(attributes_modification: &str) -> Result<HashMap<String, String>, String> {
27        if attributes_modification.is_empty() {
28            return Ok(HashMap::new());
29        }
30
31        let mut attributes = HashMap::new();
32        let kvs: Vec<&str> = attributes_modification.split(ATTR_ARRAY_SEPARATOR_COMMA).collect();
33        for kv in kvs {
34            let mut key = String::new();
35            let mut value = String::new();
36            if kv.contains(ATTR_KEY_VALUE_EQUAL_SIGN) {
37                let splits: Vec<&str> = kv.split(ATTR_KEY_VALUE_EQUAL_SIGN).collect();
38                key.push_str(splits[0]);
39                value.push_str(splits[1]);
40                if !key.contains(ATTR_ADD_PLUS_SIGN) {
41                    return Err(format!("add/alter attribute format is wrong: {key}"));
42                }
43            } else {
44                key.push_str(kv);
45                if !key.contains(ATTR_DELETE_MINUS_SIGN) {
46                    return Err(format!("delete attribute format is wrong: {key}",));
47                }
48            }
49            if attributes.insert(key.clone(), value).is_some() {
50                return Err(format!("key duplication: {key}",));
51            }
52        }
53        Ok(attributes)
54    }
55
56    pub fn parse_to_string(attributes: &HashMap<String, String>) -> String {
57        if attributes.is_empty() {
58            return String::new();
59        }
60
61        let mut kvs: Vec<String> = Vec::new();
62        for (key, value) in attributes {
63            if value.is_empty() {
64                kvs.push(key.clone());
65            } else {
66                kvs.push(format!("{key}{ATTR_KEY_VALUE_EQUAL_SIGN}{value}"));
67            }
68        }
69        kvs.join(ATTR_ARRAY_SEPARATOR_COMMA)
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn test_parse_to_map_empty_string() {
79        let result = AttributeParser::parse_to_map("");
80        assert_eq!(result.unwrap(), HashMap::new());
81    }
82
83    #[test]
84    fn test_parse_to_map_valid_attributes() {
85        let input = "+key1=value1,+key2=value2,-key3";
86        let mut expected = HashMap::new();
87        expected.insert("+key1".to_string(), "value1".to_string());
88        expected.insert("+key2".to_string(), "value2".to_string());
89        expected.insert("-key3".to_string(), "".to_string());
90
91        let result = AttributeParser::parse_to_map(input);
92        assert_eq!(result.unwrap(), expected);
93    }
94
95    #[test]
96    fn test_parse_to_map_add_attribute_format_error() {
97        let input = "key1=value1,+key2=value2";
98        let result = AttributeParser::parse_to_map(input);
99        assert_eq!(
100            result.unwrap_err(),
101            "add/alter attribute format is wrong: key1".to_string()
102        );
103    }
104
105    #[test]
106    fn test_parse_to_map_delete_attribute_format_error() {
107        let input = "+key1=value1,key2";
108        let result = AttributeParser::parse_to_map(input);
109        assert_eq!(
110            result.unwrap_err(),
111            "delete attribute format is wrong: key2".to_string()
112        );
113    }
114
115    #[test]
116    fn test_parse_to_map_key_duplication_error() {
117        let input = "+key1=value1,+key1=value2";
118        let result = AttributeParser::parse_to_map(input);
119        assert_eq!(result.unwrap_err(), "key duplication: +key1".to_string());
120    }
121
122    #[test]
123    fn test_parse_to_string_empty_map() {
124        let attributes = HashMap::new();
125        let result = AttributeParser::parse_to_string(&attributes);
126        assert_eq!(result, "");
127    }
128
129    #[test]
130    fn test_parse_to_string_nonempty_operations_round_trip() {
131        for (operations, mut expected) in [
132            (
133                vec![("+key1", "hello 世界"), ("+key2", "value2"), ("-key3", "")],
134                vec!["+key1=hello 世界", "+key2=value2", "-key3"],
135            ),
136            (vec![("+key1", "hello 世界")], vec!["+key1=hello 世界"]),
137            (vec![("-key3", "")], vec!["-key3"]),
138        ] {
139            let attributes = operations
140                .iter()
141                .map(|(key, value)| (key.to_string(), value.to_string()))
142                .collect();
143            let result = AttributeParser::parse_to_string(&attributes);
144            let mut tokens: Vec<_> = result.split(',').collect();
145            tokens.sort_unstable();
146            expected.sort_unstable();
147            assert_eq!(tokens, expected);
148            assert_eq!(AttributeParser::parse_to_map(&result).unwrap(), attributes);
149        }
150    }
151}