middleware_core/route/
rules.rs1use transport_core::Endpoint;
2
3use super::selection::weighted_pick;
4use super::{RouteHint, RouteRule};
5
6impl RouteRule {
7 fn wildcard_match(pattern: &str, target: &str) -> bool {
8 if pattern == "*" {
9 return true;
10 }
11 if let Some(star) = pattern.find('*') {
12 let prefix = &pattern[..star];
13 let suffix = &pattern[star + 1..];
14 return target.starts_with(prefix) && target.ends_with(suffix);
15 }
16 pattern == target
17 }
18
19 pub fn matches(&self, endpoint: &Endpoint, hint: &RouteHint) -> bool {
20 let label_match = self.required_labels.iter().all(|required| {
21 endpoint
22 .labels
23 .iter()
24 .any(|candidate| candidate == required)
25 });
26 if !label_match {
27 return false;
28 }
29
30 if !self.match_kinds.is_empty() {
31 let Some(kind) = hint.traffic_kind else {
32 return false;
33 };
34 if !self.match_kinds.iter().any(|allowed| allowed == &kind) {
35 return false;
36 }
37 }
38
39 if !self.name_patterns.is_empty() {
40 let Some(target) = &hint.target_name else {
41 return false;
42 };
43 let pattern_match = self
44 .name_patterns
45 .iter()
46 .any(|pattern| Self::wildcard_match(pattern, target));
47 if !pattern_match {
48 return false;
49 }
50 }
51
52 if self.preferred_schemes.is_empty() {
53 return true;
54 }
55
56 self.preferred_schemes
57 .iter()
58 .any(|scheme| scheme == &endpoint.scheme)
59 }
60}
61
62pub fn resolve_with_rules(
63 endpoints: &[Endpoint],
64 rules: &[RouteRule],
65 hint: &RouteHint,
66) -> Option<Endpoint> {
67 for rule in rules {
68 let domain_match = match rule.preferred_domain {
69 Some(domain) => domain == hint.preferred_domain,
70 None => true,
71 };
72
73 if !domain_match {
74 continue;
75 }
76
77 let matching: Vec<&Endpoint> = endpoints
78 .iter()
79 .filter(|ep| rule.matches(ep, hint))
80 .collect();
81 if matching.is_empty() {
82 continue;
83 }
84
85 let best_scheme_rank = matching
86 .iter()
87 .map(|ep| scheme_rank(rule, ep))
88 .min()
89 .unwrap_or(usize::MAX);
90
91 let prioritized: Vec<&Endpoint> = matching
92 .into_iter()
93 .filter(|ep| scheme_rank(rule, ep) == best_scheme_rank)
94 .collect();
95
96 if let Some(endpoint) = weighted_pick(&prioritized, hint) {
97 return Some(endpoint.clone());
98 }
99 }
100 None
101}
102
103fn scheme_rank(rule: &RouteRule, endpoint: &Endpoint) -> usize {
104 if rule.preferred_schemes.is_empty() {
105 return 0;
106 }
107 rule.preferred_schemes
108 .iter()
109 .position(|scheme| scheme == &endpoint.scheme)
110 .unwrap_or(usize::MAX)
111}