Skip to main content

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