Skip to main content

rocketmq_protocol/protocol/heartbeat/
subscription_data.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::BTreeSet;
16use std::hash::Hash;
17use std::hash::Hasher;
18
19use cheetah_string::CheetahString;
20use rocketmq_model::common::filter::expression_type::ExpressionType;
21use serde::Deserialize;
22use serde::Serialize;
23
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
25#[serde(rename_all = "camelCase")]
26pub struct SubscriptionData {
27    pub class_filter_mode: bool,
28    pub topic: CheetahString,
29    pub sub_string: CheetahString,
30    pub tags_set: BTreeSet<CheetahString>,
31    pub code_set: BTreeSet<i32>,
32    pub sub_version: i64,
33    pub expression_type: CheetahString,
34    // In Rust, attributes like `@JSONField(serialize = false)` are typically handled through
35    // documentation or external crates.
36    #[serde(skip)]
37    pub filter_class_source: CheetahString, // This field is not used in this example.
38}
39
40impl Default for SubscriptionData {
41    fn default() -> Self {
42        SubscriptionData {
43            class_filter_mode: false,
44            topic: CheetahString::new(),
45            sub_string: CheetahString::new(),
46            tags_set: BTreeSet::new(),
47            code_set: BTreeSet::new(),
48            sub_version: 0,
49            expression_type: CheetahString::from_static_str(ExpressionType::TAG),
50            filter_class_source: CheetahString::new(),
51        }
52    }
53}
54
55impl SubscriptionData {
56    pub const SUB_ALL: &'static str = "*";
57}
58
59impl Hash for SubscriptionData {
60    fn hash<H: Hasher>(&self, state: &mut H) {
61        self.class_filter_mode.hash(state);
62        self.topic.hash(state);
63        self.sub_string.hash(state);
64        self.tags_set.iter().for_each(|tag| tag.hash(state));
65        self.code_set.iter().for_each(|code| code.hash(state));
66        self.sub_version.hash(state);
67        self.expression_type.hash(state);
68        self.filter_class_source.hash(state);
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use std::collections::hash_map::DefaultHasher;
76
77    #[test]
78    fn subscription_data_default() {
79        let data = SubscriptionData::default();
80
81        assert!(!data.class_filter_mode);
82        assert!(data.topic.is_empty());
83        assert!(data.sub_string.is_empty());
84        assert!(data.tags_set.is_empty());
85        assert!(data.code_set.is_empty());
86        assert_eq!(data.sub_version, 0);
87        assert_eq!(data.expression_type, ExpressionType::TAG);
88        assert!(data.filter_class_source.is_empty());
89    }
90
91    #[test]
92    fn subscription_data_sub_all_constant() {
93        assert_eq!(SubscriptionData::SUB_ALL, "*");
94    }
95
96    #[test]
97    fn subscription_data_hash_and_eq() {
98        let data1 = SubscriptionData {
99            topic: CheetahString::from("topic"),
100            sub_version: 12345,
101            ..Default::default()
102        };
103
104        let mut data2 = data1.clone();
105        assert_eq!(data1, data2);
106
107        let mut hasher1 = DefaultHasher::new();
108        data1.hash(&mut hasher1);
109        let hash1 = hasher1.finish();
110
111        let mut hasher2 = DefaultHasher::new();
112        data2.hash(&mut hasher2);
113        let hash2 = hasher2.finish();
114
115        assert_eq!(hash1, hash2);
116
117        data2.sub_version = 54321;
118        assert_ne!(data1, data2);
119    }
120
121    #[test]
122    fn subscription_data_serde() {
123        let mut data = SubscriptionData {
124            topic: CheetahString::from("test_topic"),
125            filter_class_source: CheetahString::from("source"),
126            ..Default::default()
127        };
128        data.tags_set.insert(CheetahString::from("tag1"));
129        data.code_set.insert(1);
130
131        let json = serde_json::to_string(&data).unwrap();
132        assert!(json.contains("\"topic\":\"test_topic\""));
133        assert!(json.contains("\"tagsSet\":[\"tag1\"]"));
134        assert!(json.contains("\"codeSet\":[1]"));
135        assert!(!json.contains("filterClassSource"));
136        assert!(!json.contains("source"));
137
138        let deserialized: SubscriptionData = serde_json::from_str(&json).unwrap();
139        assert_eq!(deserialized.topic, data.topic);
140        assert_eq!(deserialized.tags_set, data.tags_set);
141        assert_eq!(deserialized.code_set, data.code_set);
142        assert_eq!(deserialized.sub_version, data.sub_version);
143        assert!(deserialized.filter_class_source.is_empty());
144    }
145}