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 once_cell::sync::Lazy;
29use std::sync::RwLock as StdRwLock;
30use async_trait::async_trait;
31use tokio::sync::RwLock;
32use serde::{Serialize, Deserialize};
33
34use crate::config::Config;
35use crate::core::{ProxyRequest, ProxyError, Route};
36use crate::{debug_fmt, error_fmt, trace_fmt, warn_fmt, FilterFactory};
37
38/// Configuration for a route.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct RouteConfig {
41    /// The ID of the route (for logging and reference)
42    pub id: String,
43    /// The base URL of the target
44    pub target: String,
45    /// Filters to apply to this route
46    #[serde(default)]
47    pub filters: Vec<FilterConfig>,
48    /// Priority of the route (higher means higher priority)
49    #[serde(default = "default_priority")]
50    pub priority: i32,
51    /// Predicates for this route
52    #[serde(default)]
53    pub predicates: Vec<PredicateConfig>,
54}
55
56/// Configuration for a filter.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct FilterConfig {
59    /// The type of filter
60    #[serde(rename = "type")]
61    pub type_: String,
62    /// The configuration for the filter
63    pub config: serde_json::Value,
64}
65
66fn default_priority() -> i32 {
67    0
68}
69
70/// Configuration for a predicate.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct PredicateConfig {
73    /// The type of predicate
74    pub type_: String,
75    /// The configuration for the predicate
76    pub config: serde_json::Value,
77}
78
79/// A predicate that determines if a request matches a route.
80#[async_trait]
81pub trait Predicate: Send + Sync + std::fmt::Debug {
82    /// Check if the request matches this predicate.
83    async fn matches(&self, request: &ProxyRequest) -> bool;
84
85    /// Get the predicate type.
86    fn predicate_type(&self) -> &str;
87}
88
89/// Constructor signature every dynamic predicate must implement
90pub type PredicateConstructor =
91fn(serde_json::Value) -> Result<Arc<dyn Predicate>, ProxyError>;
92
93
94/// Global registry – `register_predicate()` writes to it,
95/// `PredicateFactory::create_predicate()` reads from it.
96static PREDICATE_REGISTRY: Lazy<StdRwLock<HashMap<String, PredicateConstructor>>> =
97    Lazy::new(|| StdRwLock::new(HashMap::new()));
98
99/// Register a predicate under a unique name.
100pub fn register_predicate(name: &str, ctor: PredicateConstructor) {
101    PREDICATE_REGISTRY
102        .write()
103        .expect("PREDICATE_REGISTRY poisoned")
104        .insert(name.to_string(), ctor);
105}
106
107/// Internal helper – fetch a constructor if somebody registered one.
108fn get_registered_predicate(name: &str) -> Option<PredicateConstructor> {
109    PREDICATE_REGISTRY
110        .read()
111        .expect("PREDICATE_REGISTRY poisoned")
112        .get(name)
113        .copied()
114}
115
116/// The predicable router implementation.
117#[derive(Debug)]
118pub struct PredicateRouter {
119    /// Routes managed by this router, stored by ID
120    routes: RwLock<HashMap<String, RouteWithPredicates>>,
121    /// Sorted list of routes by priority
122    sorted_routes: RwLock<Vec<RouteWithPredicates>>,
123    /// Configuration for the router
124    config: Arc<Config>,
125}
126
127/// A route with associated predicates.
128#[derive(Debug, Clone)]
129struct RouteWithPredicates {
130    /// The route
131    route: Route,
132    /// Predicates that must match for this route
133    predicates: Vec<Arc<dyn Predicate>>,
134    /// Priority of the route
135    priority: i32,
136}
137
138impl PredicateRouter {
139    /// Create a new predicate router with the given configuration.
140    pub async fn new(config: Arc<Config>) -> Result<Self, ProxyError> {
141        let router = Self {
142            routes: RwLock::new(HashMap::new()),
143            sorted_routes: RwLock::new(Vec::new()),
144            config,
145        };
146
147        // Initialize routes from configuration
148        router.load_routes_from_config().await?;
149
150        Ok(router)
151    }
152
153    /// Load routes from the configuration.
154    async fn load_routes_from_config(&self) -> Result<(), ProxyError> {
155        // Get routes from configuration
156        let route_configs: Option<Vec<RouteConfig>> = self.config.get("routes")?;
157
158        if let Some(route_configs) = route_configs {
159            // Add each route
160            for route_config in route_configs {
161                // Create predicates for this route
162                let mut predicates = Vec::new();
163                for predicate_config in &route_config.predicates {
164                    let predicate = PredicateFactory::create_predicate(
165                        &predicate_config.type_,
166                        predicate_config.config.clone(),
167                    )?;
168                    predicates.push(predicate);
169                }
170
171                // Create filters for this route
172                let mut filters = Vec::new();
173                for filter_config in &route_config.filters {
174                    let filter = FilterFactory::create_filter(
175                        &filter_config.type_,
176                        filter_config.config.clone(),
177                    )?;
178                    filters.push(filter);
179                }
180
181                // Find the first path predicate to use as the route pattern
182                let path_pattern = route_config.predicates.iter()
183                    .find(|p| p.type_ == "path")
184                    .map(|p| p.config.get("pattern")
185                        .and_then(|v| v.as_str())
186                        .unwrap_or("/*"))
187                    .unwrap_or("/*")
188                    .to_string();
189
190                let route = Route {
191                    id: route_config.id.clone(),
192                    target_base_url: route_config.target.clone(),
193                    path_pattern,
194                    filters: if filters.is_empty() { None } else { Some(filters) },
195                };
196
197                // Add the route with its predicates
198                self.add_route_with_predicates(
199                    route,
200                    predicates,
201                    route_config.priority,
202                ).await?;
203            }
204        }
205
206        Ok(())
207    }
208
209    /// Add a route with predicates and priority.
210    async fn add_route_with_predicates(
211        &self,
212        route: Route,
213        predicates: Vec<Arc<dyn Predicate>>,
214        priority: i32,
215    ) -> Result<(), ProxyError> {
216        let route_with_predicates = RouteWithPredicates {
217            route: route.clone(),
218            predicates,
219            priority,
220        };
221
222        // Store the route
223        {
224            let mut routes = self.routes.write().await;
225            routes.insert(route.id.clone(), route_with_predicates.clone());
226        }
227
228        // Update sorted routes
229        {
230            let mut sorted_routes = self.sorted_routes.write().await;
231            sorted_routes.push(route_with_predicates);
232
233            // Sort by priority (higher first)
234            sorted_routes.sort_by(|a, b| b.priority.cmp(&a.priority));
235        }
236
237        Ok(())
238    }
239}
240
241#[async_trait]
242impl crate::core::Router for PredicateRouter {
243    async fn route(&self, request: &ProxyRequest) -> Result<Route, ProxyError> {
244        // Find the first route where all predicates match
245        let sorted_routes = self.sorted_routes.read().await;
246        trace_fmt!("Router", "Routing request {} {} against {} routes", 
247            request.method, request.path, sorted_routes.len());
248
249        for route_with_predicates in sorted_routes.iter() {
250            // Check all predicates for this route
251            let mut all_match = true;
252            let route_id = &route_with_predicates.route.id;
253            
254            trace_fmt!("Router", "Checking route '{}' with {} predicates", 
255                route_id, route_with_predicates.predicates.len());
256
257            for predicate in &route_with_predicates.predicates {
258                let predicate_type = predicate.predicate_type();
259                let matches = predicate.matches(request).await;
260                
261                trace_fmt!("Router", "  Predicate '{}' for route '{}': {}", 
262                    predicate_type, route_id, if matches { "match" } else { "no match" });
263                
264                if !matches {
265                    all_match = false;
266                    break;
267                }
268            }
269
270            // If all predicates match, use this route
271            if all_match {
272                debug_fmt!("Router", "Route '{}' matched request {} {}", 
273                    route_id, request.method, request.path);
274                return Ok(route_with_predicates.route.clone());
275            }
276        }
277
278        // No route matched
279        let err = ProxyError::RoutingError(format!("No route matched the request: {} {}",
280                                             request.method, request.path));
281        warn_fmt!("Router", "{}", err);
282        Err(err)
283    }
284
285    async fn get_routes(&self) -> Vec<Route> {
286        let routes = self.routes.read().await;
287        routes.values().map(|r| r.route.clone()).collect()
288    }
289
290    async fn add_route(&self, route: Route) -> Result<(), ProxyError> {
291        // Create an empty predicate list - this is not the recommended way to add routes
292        // Users should use add_route_with_predicates instead
293        self.add_route_with_predicates(route, Vec::new(), 0).await
294    }
295
296    async fn remove_route(&self, route_id: &str) -> Result<(), ProxyError> {
297        // Remove the route from the routes map
298        {
299            let mut routes = self.routes.write().await;
300            if routes.remove(route_id).is_none() {
301                return Err(ProxyError::RoutingError(format!("Route not found: {}", route_id)));
302            }
303        }
304
305        // Remove the route from the sorted list
306        {
307            let mut sorted_routes = self.sorted_routes.write().await;
308            sorted_routes.retain(|r| r.route.id != route_id);
309        }
310
311        Ok(())
312    }
313}
314
315/// Factory for creating predicates based on configuration.
316#[derive(Debug)]
317pub struct PredicateFactory;
318
319impl PredicateFactory {
320    /// Create a predicate based on the predicate type and configuration.
321    pub fn create_predicate(
322        predicate_type: &str,
323        config: serde_json::Value,
324    ) -> Result<Arc<dyn Predicate>, ProxyError> {
325        debug_fmt!("Router", "Creating predicate of type '{}' with config: {}", 
326            predicate_type, config);
327
328        // See if we've got an external predicate registered of that name
329        if let Some(ctor) = get_registered_predicate(predicate_type) {
330            return ctor(config);
331        }
332            
333        match predicate_type {
334            "path" => {
335                let path_config: PathPredicateConfig = serde_json::from_value(config)
336                    .map_err(|e| {
337                        let err = ProxyError::RoutingError(
338                            format!("Invalid path predicate config: {}", e)
339                        );
340                        error_fmt!("Router", "{}", err);
341                        err
342                    })?;
343                
344                match PathPredicate::new(path_config) { 
345                    Ok(predicate) => Ok(Arc::new(predicate)),
346                    Err(error) => Err(error),
347                }
348            },
349            "method" => {
350                let method_config: MethodPredicateConfig = serde_json::from_value(config)
351                    .map_err(|e| {
352                        let err = ProxyError::RoutingError(
353                            format!("Invalid method predicate config: {}", e)
354                        );
355                        error_fmt!("Router", "{}", err);
356                        err
357                    })?;
358                Ok(Arc::new(MethodPredicate::new(method_config)))
359            },
360            "header" => {
361                let header_config: HeaderPredicateConfig = serde_json::from_value(config)
362                    .map_err(|e| {
363                        let err = ProxyError::RoutingError(
364                            format!("Invalid header predicate config: {}", e)
365                        );
366                        error_fmt!("Router", "{}", err);
367                        err
368                    })?;
369                Ok(Arc::new(HeaderPredicate::new(header_config)))
370            },
371            "query" => {
372                let query_config: QueryPredicateConfig = serde_json::from_value(config)
373                    .map_err(|e| {
374                        let err = ProxyError::RoutingError(
375                            format!("Invalid query predicate config: {}", e)
376                        );
377                        error_fmt!("Router", "{}", err);
378                        err
379                    })?;
380                Ok(Arc::new(QueryPredicate::new(query_config)))
381            },
382            _ => {
383                let err = ProxyError::RoutingError(
384                    format!("Unknown predicate type: {}", predicate_type)
385                );
386                error_fmt!("Router", "{}", err);
387                Err(err)
388            },
389        }
390    }
391}