mockforge_core/protocol_abstraction/
matcher.rs1use super::{Protocol, ProtocolRequest, RequestMatcher};
4use std::collections::hash_map::DefaultHasher;
5use std::hash::{Hash, Hasher};
6pub struct SimpleRequestMatcher;
8
9impl RequestMatcher for SimpleRequestMatcher {
10 fn match_score(&self, _request: &ProtocolRequest) -> f64 {
11 1.0
13 }
14
15 fn protocol(&self) -> Protocol {
16 Protocol::Http }
19}
20
21pub struct FuzzyRequestMatcher {
23 pub operation_weight: f64,
25 pub path_weight: f64,
27 pub metadata_weight: f64,
29 pub body_weight: f64,
31}
32
33impl Default for FuzzyRequestMatcher {
34 fn default() -> Self {
35 Self {
36 operation_weight: 0.4,
37 path_weight: 0.4,
38 metadata_weight: 0.1,
39 body_weight: 0.1,
40 }
41 }
42}
43
44impl RequestMatcher for FuzzyRequestMatcher {
45 fn match_score(&self, request: &ProtocolRequest) -> f64 {
46 let mut score = 0.0;
48
49 if !request.operation.is_empty() {
51 score += self.operation_weight;
52 }
53
54 if !request.path.is_empty() {
56 score += self.path_weight;
57 }
58
59 if !request.metadata.is_empty() {
61 score += self.metadata_weight;
62 }
63
64 if request.body.is_some() {
66 score += self.body_weight;
67 }
68
69 score
70 }
71
72 fn protocol(&self) -> Protocol {
73 Protocol::Http }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Hash)]
79pub struct RequestFingerprint {
80 pub protocol: Protocol,
82 pub operation_hash: u64,
84 pub path_hash: u64,
86 pub metadata_hash: Option<u64>,
88 pub body_hash: Option<u64>,
90}
91
92impl RequestFingerprint {
93 pub fn from_request(request: &ProtocolRequest) -> Self {
95 Self {
96 protocol: request.protocol,
97 operation_hash: Self::hash_string(&request.operation),
98 path_hash: Self::hash_string(&request.path),
99 metadata_hash: if !request.metadata.is_empty() {
100 Some(Self::hash_metadata(&request.metadata))
101 } else {
102 None
103 },
104 body_hash: request.body.as_ref().map(|b| Self::hash_bytes(b)),
105 }
106 }
107
108 pub fn simple(request: &ProtocolRequest) -> Self {
110 Self {
111 protocol: request.protocol,
112 operation_hash: Self::hash_string(&request.operation),
113 path_hash: Self::hash_string(&request.path),
114 metadata_hash: None,
115 body_hash: None,
116 }
117 }
118
119 fn hash_string(s: &str) -> u64 {
121 let mut hasher = DefaultHasher::new();
122 s.hash(&mut hasher);
123 hasher.finish()
124 }
125
126 fn hash_bytes(bytes: &[u8]) -> u64 {
128 let mut hasher = DefaultHasher::new();
129 bytes.hash(&mut hasher);
130 hasher.finish()
131 }
132
133 fn hash_metadata(metadata: &std::collections::HashMap<String, String>) -> u64 {
135 let mut hasher = DefaultHasher::new();
136 let mut keys: Vec<&String> = metadata.keys().collect();
138 keys.sort();
139 for key in keys {
140 key.hash(&mut hasher);
141 if let Some(value) = metadata.get(key) {
142 value.hash(&mut hasher);
143 }
144 }
145 hasher.finish()
146 }
147
148 pub fn matches(&self, other: &RequestFingerprint) -> bool {
150 self.protocol == other.protocol
151 && self.operation_hash == other.operation_hash
152 && self.path_hash == other.path_hash
153 }
154
155 pub fn exact_match(&self, other: &RequestFingerprint) -> bool {
157 self == other
158 }
159
160 pub fn similarity(&self, other: &RequestFingerprint) -> f64 {
162 if self.protocol != other.protocol {
163 return 0.0;
164 }
165
166 let mut score = 0.0;
167 let mut factors = 0;
168
169 if self.operation_hash == other.operation_hash {
171 score += 1.0;
172 }
173 factors += 1;
174
175 if self.path_hash == other.path_hash {
177 score += 1.0;
178 }
179 factors += 1;
180
181 if let (Some(hash1), Some(hash2)) = (self.metadata_hash, other.metadata_hash) {
183 if hash1 == hash2 {
184 score += 1.0;
185 }
186 factors += 1;
187 }
188
189 if let (Some(hash1), Some(hash2)) = (self.body_hash, other.body_hash) {
191 if hash1 == hash2 {
192 score += 1.0;
193 }
194 factors += 1;
195 }
196
197 score / factors as f64
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use std::collections::HashMap;
205
206 #[test]
207 fn test_simple_matcher() {
208 let matcher = SimpleRequestMatcher;
209 let request = ProtocolRequest {
210 protocol: Protocol::Http,
211 pattern: crate::MessagePattern::RequestResponse,
212 operation: "GET".to_string(),
213 path: "/test".to_string(),
214 topic: None,
215 routing_key: None,
216 partition: None,
217 qos: None,
218 metadata: HashMap::new(),
219 body: None,
220 client_ip: None,
221 };
222
223 assert_eq!(matcher.match_score(&request), 1.0);
224 }
225
226 #[test]
227 fn test_fuzzy_matcher_default() {
228 let matcher = FuzzyRequestMatcher::default();
229 assert_eq!(matcher.operation_weight, 0.4);
230 assert_eq!(matcher.path_weight, 0.4);
231 assert_eq!(matcher.metadata_weight, 0.1);
232 assert_eq!(matcher.body_weight, 0.1);
233 }
234
235 #[test]
236 fn test_fuzzy_matcher_full_request() {
237 let matcher = FuzzyRequestMatcher::default();
238 let request = ProtocolRequest {
239 protocol: Protocol::Http,
240 pattern: crate::MessagePattern::RequestResponse,
241 operation: "GET".to_string(),
242 path: "/test".to_string(),
243 topic: None,
244 routing_key: None,
245 partition: None,
246 qos: None,
247 metadata: {
248 let mut m = HashMap::new();
249 m.insert("content-type".to_string(), "application/json".to_string());
250 m
251 },
252 body: Some(b"{\"test\": true}".to_vec()),
253 client_ip: None,
254 };
255
256 let score = matcher.match_score(&request);
257 assert_eq!(score, 1.0); }
259
260 #[test]
261 fn test_request_fingerprint_from_request() {
262 let request = ProtocolRequest {
263 protocol: Protocol::Http,
264 pattern: crate::MessagePattern::RequestResponse,
265 operation: "GET".to_string(),
266 path: "/users".to_string(),
267 topic: None,
268 routing_key: None,
269 partition: None,
270 qos: None,
271 metadata: HashMap::new(),
272 body: None,
273 client_ip: None,
274 };
275
276 let fp = RequestFingerprint::from_request(&request);
277 assert_eq!(fp.protocol, Protocol::Http);
278 assert!(fp.metadata_hash.is_none());
279 assert!(fp.body_hash.is_none());
280 }
281
282 #[test]
283 fn test_request_fingerprint_simple() {
284 let request = ProtocolRequest {
285 protocol: Protocol::Grpc,
286 pattern: crate::MessagePattern::RequestResponse,
287 operation: "greeter.SayHello".to_string(),
288 path: "/greeter.Greeter/SayHello".to_string(),
289 topic: None,
290 routing_key: None,
291 partition: None,
292 qos: None,
293 metadata: {
294 let mut m = HashMap::new();
295 m.insert("grpc-metadata".to_string(), "value".to_string());
296 m
297 },
298 body: Some(b"test".to_vec()),
299 client_ip: None,
300 };
301
302 let fp = RequestFingerprint::simple(&request);
303 assert_eq!(fp.protocol, Protocol::Grpc);
304 assert!(fp.metadata_hash.is_none()); assert!(fp.body_hash.is_none()); }
307
308 #[test]
309 fn test_fingerprint_matches() {
310 let request1 = ProtocolRequest {
311 protocol: Protocol::GraphQL,
312 pattern: crate::MessagePattern::RequestResponse,
313 operation: "Query.users".to_string(),
314 path: "/graphql".to_string(),
315 topic: None,
316 routing_key: None,
317 partition: None,
318 qos: None,
319 metadata: HashMap::new(),
320 body: None,
321 client_ip: None,
322 };
323
324 let request2 = ProtocolRequest {
325 protocol: Protocol::GraphQL,
326 pattern: crate::MessagePattern::RequestResponse,
327 operation: "Query.users".to_string(),
328 path: "/graphql".to_string(),
329 topic: None,
330 routing_key: None,
331 partition: None,
332 qos: None,
333 metadata: HashMap::new(),
334 body: Some(b"different body".to_vec()),
335 client_ip: None,
336 };
337
338 let fp1 = RequestFingerprint::from_request(&request1);
339 let fp2 = RequestFingerprint::from_request(&request2);
340
341 assert!(fp1.matches(&fp2)); assert!(!fp1.exact_match(&fp2)); }
344
345 #[test]
346 fn test_fingerprint_similarity() {
347 let request = ProtocolRequest {
348 protocol: Protocol::Http,
349 pattern: crate::MessagePattern::RequestResponse,
350 operation: "GET".to_string(),
351 path: "/test".to_string(),
352 topic: None,
353 routing_key: None,
354 partition: None,
355 qos: None,
356 metadata: HashMap::new(),
357 body: None,
358 client_ip: None,
359 };
360
361 let fp1 = RequestFingerprint::from_request(&request);
362 let fp2 = RequestFingerprint::from_request(&request);
363
364 assert_eq!(fp1.similarity(&fp2), 1.0); }
366
367 #[test]
368 fn test_fingerprint_different_protocol() {
369 let request1 = ProtocolRequest {
370 protocol: Protocol::Http,
371 pattern: crate::MessagePattern::RequestResponse,
372 operation: "GET".to_string(),
373 path: "/test".to_string(),
374 topic: None,
375 routing_key: None,
376 partition: None,
377 qos: None,
378 metadata: HashMap::new(),
379 body: None,
380 client_ip: None,
381 };
382
383 let request2 = ProtocolRequest {
384 protocol: Protocol::Grpc,
385 pattern: crate::MessagePattern::RequestResponse,
386 operation: "GET".to_string(),
387 path: "/test".to_string(),
388 topic: None,
389 routing_key: None,
390 partition: None,
391 qos: None,
392 metadata: HashMap::new(),
393 body: None,
394 client_ip: None,
395 };
396
397 let fp1 = RequestFingerprint::from_request(&request1);
398 let fp2 = RequestFingerprint::from_request(&request2);
399
400 assert_eq!(fp1.similarity(&fp2), 0.0); }
402}