rocketmq_auth/authorization/model/
resource.rs1use std::fmt;
16
17use rocketmq_common::common::mix_all;
18use rocketmq_common::common::resource::resource_pattern::ResourcePattern;
19use rocketmq_common::common::resource::resource_type::ResourceType;
20use rocketmq_remoting::protocol::namespace_util::NamespaceUtil;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct Resource {
24 pub resource_type: ResourceType,
25 pub resource_name: Option<String>,
26 pub resource_pattern: ResourcePattern,
27}
28
29impl Resource {
30 pub fn of_cluster(cluster_name: &str) -> Self {
31 Self::of(
32 ResourceType::Cluster,
33 Some(cluster_name.to_string()),
34 ResourcePattern::Literal,
35 )
36 }
37
38 pub fn of_topic(topic_name: &str) -> Self {
39 Self::of(
40 ResourceType::Topic,
41 Some(topic_name.to_string()),
42 ResourcePattern::Literal,
43 )
44 }
45
46 pub fn of_group(mut group_name: String) -> Self {
47 if NamespaceUtil::is_retry_topic(&group_name) {
48 let prefix = mix_all::RETRY_GROUP_TOPIC_PREFIX;
50 if group_name.starts_with(prefix) {
51 group_name = group_name[prefix.len()..].to_string();
52 }
53 }
54 Self::of(ResourceType::Group, Some(group_name), ResourcePattern::Literal)
55 }
56
57 pub fn of(resource_type: ResourceType, resource_name: Option<String>, resource_pattern: ResourcePattern) -> Self {
58 Resource {
59 resource_type,
60 resource_name,
61 resource_pattern,
62 }
63 }
64
65 pub fn of_list<I, S>(resource_keys: I) -> Option<Vec<Self>>
66 where
67 I: IntoIterator<Item = S>,
68 S: AsRef<str>,
69 {
70 let mut out = Vec::new();
71 for s in resource_keys.into_iter() {
72 let sref = s.as_ref();
73 if let Some(r) = Resource::of_str(sref) {
74 out.push(r);
75 }
76 }
77 if out.is_empty() {
78 None
79 } else {
80 Some(out)
81 }
82 }
83
84 pub fn of_str(resource_key: &str) -> Option<Self> {
85 let key = resource_key.trim();
86 if key.is_empty() {
87 return None;
88 }
89 if key == "*" {
90 return Some(Self::of(ResourceType::Any, None, ResourcePattern::Any));
91 }
92 let (t, name) = match key.split_once(':') {
93 Some((a, b)) => (a, b),
94 None => (key, ""),
95 };
96 let resource_type = ResourceType::get_by_name(t)?;
97 let mut resource_name = if name.is_empty() { None } else { Some(name.to_string()) };
98 let mut resource_pattern = ResourcePattern::Literal;
99
100 if let Some(ref rn) = resource_name {
101 if rn == "*" {
102 resource_name = None;
103 resource_pattern = ResourcePattern::Any;
104 } else if rn.ends_with('*') {
105 let trimmed = rn.trim_end_matches('*').to_string();
106 resource_name = Some(trimmed);
107 resource_pattern = ResourcePattern::Prefixed;
108 }
109 }
110
111 Some(Self::of(resource_type, resource_name, resource_pattern))
112 }
113
114 pub fn resource_key(&self) -> Option<String> {
116 if self.resource_type == ResourceType::Any {
117 return Some("*".to_string());
118 }
119 let type_name = self.resource_type.name();
120 match self.resource_pattern {
121 ResourcePattern::Any => Some(format!("{}:{}", type_name, "*")),
122 ResourcePattern::Literal => Some(format!("{}:{}", type_name, self.resource_name.as_deref().unwrap_or(""))),
123 ResourcePattern::Prefixed => Some(format!(
124 "{}:{}*",
125 type_name,
126 self.resource_name.as_deref().unwrap_or("")
127 )),
128 }
129 }
130
131 pub fn is_match(&self, resource: &Resource) -> bool {
133 if self.resource_type == ResourceType::Any {
134 return true;
135 }
136 if self.resource_type != resource.resource_type {
137 return false;
138 }
139 match self.resource_pattern {
140 ResourcePattern::Any => true,
141 ResourcePattern::Literal => resource.resource_name.as_deref() == self.resource_name.as_deref(),
142 ResourcePattern::Prefixed => match (&resource.resource_name, &self.resource_name) {
143 (Some(target), Some(prefix)) => target.starts_with(prefix),
144 _ => false,
145 },
146 }
147 }
148
149 pub fn resource_type(&self) -> ResourceType {
151 self.resource_type
152 }
153
154 pub fn resource_name(&self) -> Option<&str> {
155 self.resource_name.as_deref()
156 }
157
158 pub fn resource_pattern(&self) -> ResourcePattern {
159 self.resource_pattern
160 }
161
162 pub fn set_resource_type(&mut self, t: ResourceType) {
163 self.resource_type = t;
164 }
165
166 pub fn set_resource_name<S: Into<String>>(&mut self, name: Option<S>) {
167 self.resource_name = name.map(Into::into);
168 }
169
170 pub fn set_resource_pattern(&mut self, p: ResourcePattern) {
171 self.resource_pattern = p;
172 }
173}
174
175impl fmt::Display for Resource {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 match self.resource_key() {
178 Some(s) => write!(f, "{}", s),
179 None => write!(f, ""),
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_of_and_serialization() {
190 let r = Resource::of(ResourceType::Topic, Some("foo".to_string()), ResourcePattern::Literal);
191 assert_eq!(r.resource_key(), Some("Topic:foo".to_string()));
192
193 let any = Resource::of_str("*").unwrap();
194 assert_eq!(any.resource_key(), Some("*".to_string()));
195 }
196
197 #[test]
198 fn test_of_parsing_patterns() {
199 assert_eq!(
200 Resource::of_str("Topic:*").unwrap().resource_pattern,
201 ResourcePattern::Any
202 );
203 assert_eq!(
204 Resource::of_str("Topic:pref*").unwrap().resource_pattern,
205 ResourcePattern::Prefixed
206 );
207 assert_eq!(
208 Resource::of_str("Topic:foo").unwrap().resource_pattern,
209 ResourcePattern::Literal
210 );
211 }
212
213 #[test]
214 fn test_group_retry_strip() {
215 let g = format!("{}{}", mix_all::RETRY_GROUP_TOPIC_PREFIX, "mygroup");
216 let r = Resource::of_group(g);
217 assert_eq!(r.resource_name(), Some("mygroup"));
218 }
219
220 #[test]
221 fn test_is_match() {
222 let rule_any = Resource::of(ResourceType::Any, None, ResourcePattern::Any);
223 let res = Resource::of(ResourceType::Topic, Some("a".to_string()), ResourcePattern::Literal);
224 assert!(rule_any.is_match(&res));
225
226 let rule_pref = Resource::of(ResourceType::Topic, Some("pre".to_string()), ResourcePattern::Prefixed);
227 let res2 = Resource::of(
228 ResourceType::Topic,
229 Some("prefix_val".to_string()),
230 ResourcePattern::Literal,
231 );
232 assert!(rule_pref.is_match(&res2));
233
234 let rule_lit = Resource::of(ResourceType::Topic, Some("t1".to_string()), ResourcePattern::Literal);
235 assert!(rule_lit.is_match(&Resource::of(
236 ResourceType::Topic,
237 Some("t1".to_string()),
238 ResourcePattern::Literal
239 )));
240 assert!(!rule_lit.is_match(&Resource::of(
241 ResourceType::Topic,
242 Some("t2".to_string()),
243 ResourcePattern::Literal
244 )));
245 }
246
247 #[test]
248 fn test_of_list() {
249 let keys = vec!["Topic:foo".to_string(), "Topic:bar".to_string()];
250 let v = Resource::of_list(keys).unwrap();
251 assert_eq!(v.len(), 2);
252
253 let empty: Vec<String> = vec![];
254 assert!(Resource::of_list(empty).is_none());
255 }
256}