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::{debug_fmt, error_fmt, trace_fmt, warn_fmt, 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        trace_fmt!("Router", "Routing request {} {} against {} routes", 
218            request.method, request.path, sorted_routes.len());
219
220        for route_with_predicates in sorted_routes.iter() {
221            // Check all predicates for this route
222            let mut all_match = true;
223            let route_id = &route_with_predicates.route.id;
224            
225            trace_fmt!("Router", "Checking route '{}' with {} predicates", 
226                route_id, route_with_predicates.predicates.len());
227
228            for predicate in &route_with_predicates.predicates {
229                let predicate_type = predicate.predicate_type();
230                let matches = predicate.matches(request).await;
231                
232                trace_fmt!("Router", "  Predicate '{}' for route '{}': {}", 
233                    predicate_type, route_id, if matches { "match" } else { "no match" });
234                
235                if !matches {
236                    all_match = false;
237                    break;
238                }
239            }
240
241            // If all predicates match, use this route
242            if all_match {
243                debug_fmt!("Router", "Route '{}' matched request {} {}", 
244                    route_id, request.method, request.path);
245                return Ok(route_with_predicates.route.clone());
246            }
247        }
248
249        // No route matched
250        let err = ProxyError::RoutingError(format!("No route matched the request: {} {}",
251                                             request.method, request.path));
252        warn_fmt!("Router", "{}", err);
253        Err(err)
254    }
255
256    async fn get_routes(&self) -> Vec<Route> {
257        let routes = self.routes.read().await;
258        routes.values().map(|r| r.route.clone()).collect()
259    }
260
261    async fn add_route(&self, route: Route) -> Result<(), ProxyError> {
262        // Create an empty predicate list - this is not the recommended way to add routes
263        // Users should use add_route_with_predicates instead
264        self.add_route_with_predicates(route, Vec::new(), 0).await
265    }
266
267    async fn remove_route(&self, route_id: &str) -> Result<(), ProxyError> {
268        // Remove the route from the routes map
269        {
270            let mut routes = self.routes.write().await;
271            if routes.remove(route_id).is_none() {
272                return Err(ProxyError::RoutingError(format!("Route not found: {}", route_id)));
273            }
274        }
275
276        // Remove the route from the sorted list
277        {
278            let mut sorted_routes = self.sorted_routes.write().await;
279            sorted_routes.retain(|r| r.route.id != route_id);
280        }
281
282        Ok(())
283    }
284}
285
286/// Factory for creating predicates based on configuration.
287#[derive(Debug)]
288pub struct PredicateFactory;
289
290impl PredicateFactory {
291    /// Create a predicate based on the predicate type and configuration.
292    pub fn create_predicate(
293        predicate_type: &str,
294        config: serde_json::Value,
295    ) -> Result<Arc<dyn Predicate>, ProxyError> {
296        debug_fmt!("Router", "Creating predicate of type '{}' with config: {}", 
297            predicate_type, config);
298            
299        match predicate_type {
300            "path" => {
301                let path_config: PathPredicateConfig = serde_json::from_value(config)
302                    .map_err(|e| {
303                        let err = ProxyError::RoutingError(
304                            format!("Invalid path predicate config: {}", e)
305                        );
306                        error_fmt!("Router", "{}", err);
307                        err
308                    })?;
309                
310                match PathPredicate::new(path_config) { 
311                    Ok(predicate) => Ok(Arc::new(predicate)),
312                    Err(error) => Err(error),
313                }
314            },
315            "method" => {
316                let method_config: MethodPredicateConfig = serde_json::from_value(config)
317                    .map_err(|e| {
318                        let err = ProxyError::RoutingError(
319                            format!("Invalid method predicate config: {}", e)
320                        );
321                        error_fmt!("Router", "{}", err);
322                        err
323                    })?;
324                Ok(Arc::new(MethodPredicate::new(method_config)))
325            },
326            "header" => {
327                let header_config: HeaderPredicateConfig = serde_json::from_value(config)
328                    .map_err(|e| {
329                        let err = ProxyError::RoutingError(
330                            format!("Invalid header predicate config: {}", e)
331                        );
332                        error_fmt!("Router", "{}", err);
333                        err
334                    })?;
335                Ok(Arc::new(HeaderPredicate::new(header_config)))
336            },
337            "query" => {
338                let query_config: QueryPredicateConfig = serde_json::from_value(config)
339                    .map_err(|e| {
340                        let err = ProxyError::RoutingError(
341                            format!("Invalid query predicate config: {}", e)
342                        );
343                        error_fmt!("Router", "{}", err);
344                        err
345                    })?;
346                Ok(Arc::new(QueryPredicate::new(query_config)))
347            },
348            _ => {
349                let err = ProxyError::RoutingError(
350                    format!("Unknown predicate type: {}", predicate_type)
351                );
352                error_fmt!("Router", "{}", err);
353                Err(err)
354            },
355        }
356    }
357}