foxy/router/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Routing DSL – *predicates* & helper logic.
6//!
7//! A [`PredicateRouter`] owns an ordered vector of [`Route`]s.  
8//! The first route whose **predicate stack** returns `true` wins and its
9//! filter-chain is executed.
10//!
11//! ### Built-in predicates
12//! | type              | configuration key     | example                              |
13//! |-------------------|-----------------------|--------------------------------------|
14//! | `MethodPredicate` | `method`              | `"GET"`                              |
15//! | `PathPredicate`   | `path` (regex)        | `"/api/v1/.*"`                       |
16//! | `HeaderPredicate` | `header.<NAME>`       | `"X-Request-Id" = "^[0-9a-f-]{36}$"` |
17//! | `QueryPredicate`  | `query.<NAME>`        | `"tenant"` = `"acme-corp"`           |
18
19mod predicates;
20
21#[cfg(test)]
22mod tests;
23
24pub use predicates::*;
25
26use std::collections::HashMap;
27use std::sync::Arc;
28use async_trait::async_trait;
29use tokio::sync::RwLock;
30use serde::{Serialize, Deserialize};
31
32use crate::config::Config;
33use crate::core::{ProxyRequest, ProxyError, Route};
34use crate::FilterFactory;
35
36/// Configuration for a route.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct RouteConfig {
39    /// The ID of the route (for logging and reference)
40    pub id: String,
41    /// The base URL of the target
42    pub target: String,
43    /// Filters to apply to this route
44    #[serde(default)]
45    pub filters: Vec<FilterConfig>,
46    /// Priority of the route (higher means higher priority)
47    #[serde(default = "default_priority")]
48    pub priority: i32,
49    /// Predicates for this route
50    #[serde(default)]
51    pub predicates: Vec<PredicateConfig>,
52}
53
54/// Configuration for a filter.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct FilterConfig {
57    /// The type of filter
58    #[serde(rename = "type")]
59    pub type_: String,
60    /// The configuration for the filter
61    pub config: serde_json::Value,
62}
63
64fn default_priority() -> i32 {
65    0
66}
67
68/// Configuration for a predicate.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct PredicateConfig {
71    /// The type of predicate
72    pub type_: String,
73    /// The configuration for the predicate
74    pub config: serde_json::Value,
75}
76
77/// A predicate that determines if a request matches a route.
78#[async_trait]
79pub trait Predicate: Send + Sync + std::fmt::Debug {
80    /// Check if the request matches this predicate.
81    async fn matches(&self, request: &ProxyRequest) -> bool;
82
83    /// Get the predicate type.
84    fn predicate_type(&self) -> &str;
85}
86
87/// The predicable router implementation.
88#[derive(Debug)]
89pub struct PredicateRouter {
90    /// Routes managed by this router, stored by ID
91    routes: RwLock<HashMap<String, RouteWithPredicates>>,
92    /// Sorted list of routes by priority
93    sorted_routes: RwLock<Vec<RouteWithPredicates>>,
94    /// Configuration for the router
95    config: Arc<Config>,
96}
97
98/// A route with associated predicates.
99#[derive(Debug, Clone)]
100struct RouteWithPredicates {
101    /// The route
102    route: Route,
103    /// Predicates that must match for this route
104    predicates: Vec<Arc<dyn Predicate>>,
105    /// Priority of the route
106    priority: i32,
107}
108
109impl PredicateRouter {
110    /// Create a new predicate router with the given configuration.
111    pub async fn new(config: Arc<Config>) -> Result<Self, ProxyError> {
112        let router = Self {
113            routes: RwLock::new(HashMap::new()),
114            sorted_routes: RwLock::new(Vec::new()),
115            config,
116        };
117
118        // Initialize routes from configuration
119        router.load_routes_from_config().await?;
120
121        Ok(router)
122    }
123
124    /// Load routes from the configuration.
125    async fn load_routes_from_config(&self) -> Result<(), ProxyError> {
126        // Get routes from configuration
127        let route_configs: Option<Vec<RouteConfig>> = self.config.get("routes")?;
128
129        if let Some(route_configs) = route_configs {
130            // Add each route
131            for route_config in route_configs {
132                // Create predicates for this route
133                let mut predicates = Vec::new();
134                for predicate_config in &route_config.predicates {
135                    let predicate = PredicateFactory::create_predicate(
136                        &predicate_config.type_,
137                        predicate_config.config.clone(),
138                    )?;
139                    predicates.push(predicate);
140                }
141
142                // Create filters for this route
143                let mut filters = Vec::new();
144                for filter_config in &route_config.filters {
145                    let filter = FilterFactory::create_filter(
146                        &filter_config.type_,
147                        filter_config.config.clone(),
148                    )?;
149                    filters.push(filter);
150                }
151
152                // Find the first path predicate to use as the route pattern
153                let path_pattern = route_config.predicates.iter()
154                    .find(|p| p.type_ == "path")
155                    .map(|p| p.config.get("pattern")
156                        .and_then(|v| v.as_str())
157                        .unwrap_or("/*"))
158                    .unwrap_or("/*")
159                    .to_string();
160
161                let route = Route {
162                    id: route_config.id.clone(),
163                    target_base_url: route_config.target.clone(),
164                    path_pattern,
165                    filters: if filters.is_empty() { None } else { Some(filters) },
166                };
167
168                // Add the route with its predicates
169                self.add_route_with_predicates(
170                    route,
171                    predicates,
172                    route_config.priority,
173                ).await?;
174            }
175        }
176
177        Ok(())
178    }
179
180    /// Add a route with predicates and priority.
181    async fn add_route_with_predicates(
182        &self,
183        route: Route,
184        predicates: Vec<Arc<dyn Predicate>>,
185        priority: i32,
186    ) -> Result<(), ProxyError> {
187        let route_with_predicates = RouteWithPredicates {
188            route: route.clone(),
189            predicates,
190            priority,
191        };
192
193        // Store the route
194        {
195            let mut routes = self.routes.write().await;
196            routes.insert(route.id.clone(), route_with_predicates.clone());
197        }
198
199        // Update sorted routes
200        {
201            let mut sorted_routes = self.sorted_routes.write().await;
202            sorted_routes.push(route_with_predicates);
203
204            // Sort by priority (higher first)
205            sorted_routes.sort_by(|a, b| b.priority.cmp(&a.priority));
206        }
207
208        Ok(())
209    }
210}
211
212#[async_trait]
213impl crate::core::Router for PredicateRouter {
214    async fn route(&self, request: &ProxyRequest) -> Result<Route, ProxyError> {
215        // Find the first route where all predicates match
216        let sorted_routes = self.sorted_routes.read().await;
217
218        for route_with_predicates in sorted_routes.iter() {
219            // Check all predicates for this route
220            let mut all_match = true;
221
222            for predicate in &route_with_predicates.predicates {
223                if !predicate.matches(request).await {
224                    all_match = false;
225                    break;
226                }
227            }
228
229            // If all predicates match, use this route
230            if all_match {
231                return Ok(route_with_predicates.route.clone());
232            }
233        }
234
235        // No route matched
236        Err(ProxyError::RoutingError(format!("No route matched the request: {} {}",
237                                             request.method, request.path)))
238    }
239
240    async fn get_routes(&self) -> Vec<Route> {
241        let routes = self.routes.read().await;
242        routes.values().map(|r| r.route.clone()).collect()
243    }
244
245    async fn add_route(&self, route: Route) -> Result<(), ProxyError> {
246        // Create an empty predicate list - this is not the recommended way to add routes
247        // Users should use add_route_with_predicates instead
248        self.add_route_with_predicates(route, Vec::new(), 0).await
249    }
250
251    async fn remove_route(&self, route_id: &str) -> Result<(), ProxyError> {
252        // Remove the route from the routes map
253        {
254            let mut routes = self.routes.write().await;
255            if routes.remove(route_id).is_none() {
256                return Err(ProxyError::RoutingError(format!("Route not found: {}", route_id)));
257            }
258        }
259
260        // Remove the route from the sorted list
261        {
262            let mut sorted_routes = self.sorted_routes.write().await;
263            sorted_routes.retain(|r| r.route.id != route_id);
264        }
265
266        Ok(())
267    }
268}
269
270/// Factory for creating predicates based on configuration.
271#[derive(Debug)]
272pub struct PredicateFactory;
273
274impl PredicateFactory {
275    /// Create a predicate based on the predicate type and configuration.
276    pub fn create_predicate(
277        predicate_type: &str,
278        config: serde_json::Value,
279    ) -> Result<Arc<dyn Predicate>, ProxyError> {
280        match predicate_type {
281            "path" => {
282                let path_config: PathPredicateConfig = serde_json::from_value(config)
283                    .map_err(|e| ProxyError::RoutingError(
284                        format!("Invalid path predicate config: {}", e)
285                    ))?;
286                Ok(Arc::new(PathPredicate::new(path_config)))
287            },
288            "method" => {
289                let method_config: MethodPredicateConfig = serde_json::from_value(config)
290                    .map_err(|e| ProxyError::RoutingError(
291                        format!("Invalid method predicate config: {}", e)
292                    ))?;
293                Ok(Arc::new(MethodPredicate::new(method_config)))
294            },
295            "header" => {
296                let header_config: HeaderPredicateConfig = serde_json::from_value(config)
297                    .map_err(|e| ProxyError::RoutingError(
298                        format!("Invalid header predicate config: {}", e)
299                    ))?;
300                Ok(Arc::new(HeaderPredicate::new(header_config)))
301            },
302            "query" => {
303                let query_config: QueryPredicateConfig = serde_json::from_value(config)
304                    .map_err(|e| ProxyError::RoutingError(
305                        format!("Invalid query predicate config: {}", e)
306                    ))?;
307                Ok(Arc::new(QueryPredicate::new(query_config)))
308            },
309            _ => Err(ProxyError::RoutingError(
310                format!("Unknown predicate type: {}", predicate_type)
311            )),
312        }
313    }
314}