Skip to main content

middleware_core/route/
rules.rs

1use 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}