Skip to main content

fastapi_router/
match.rs

1//! Route matching result.
2
3use crate::trie::Route;
4use crate::trie::{Converter, ParamInfo};
5use fastapi_types::Method;
6
7/// A matched route with extracted parameters.
8#[derive(Debug)]
9pub struct RouteMatch<'a> {
10    /// The matched route.
11    pub route: &'a Route,
12    /// Extracted path parameters.
13    pub params: Vec<(&'a str, &'a str)>,
14}
15
16impl<'a> RouteMatch<'a> {
17    /// Get a parameter value by name.
18    #[must_use]
19    pub fn get_param(&self, name: &str) -> Option<&str> {
20        self.params
21            .iter()
22            .find(|(n, _)| *n == name)
23            .map(|(_, v)| *v)
24    }
25
26    /// Number of extracted parameters.
27    #[must_use]
28    pub fn param_count(&self) -> usize {
29        self.params.len()
30    }
31
32    /// Returns true if there are no extracted parameters.
33    #[must_use]
34    pub fn is_empty(&self) -> bool {
35        self.params.is_empty()
36    }
37
38    /// Iterate over extracted parameters as `(name, value)` pairs.
39    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
40        self.params.iter().map(|(k, v)| (*k, *v))
41    }
42
43    /// Returns true if the route declares the given param as a UUID converter.
44    #[must_use]
45    pub fn is_param_uuid(&self, name: &str) -> Option<bool> {
46        self.route
47            .path_params
48            .iter()
49            .find(|p: &&ParamInfo| p.name == name)
50            .map(|p| p.converter == Converter::Uuid)
51    }
52
53    /// Parse a parameter as i64.
54    pub fn get_param_int(&self, name: &str) -> Option<Result<i64, std::num::ParseIntError>> {
55        self.get_param(name).map(str::parse::<i64>)
56    }
57
58    /// Parse a parameter as i32.
59    pub fn get_param_i32(&self, name: &str) -> Option<Result<i32, std::num::ParseIntError>> {
60        self.get_param(name).map(str::parse::<i32>)
61    }
62
63    /// Parse a parameter as u64.
64    pub fn get_param_u64(&self, name: &str) -> Option<Result<u64, std::num::ParseIntError>> {
65        self.get_param(name).map(str::parse::<u64>)
66    }
67
68    /// Parse a parameter as u32.
69    pub fn get_param_u32(&self, name: &str) -> Option<Result<u32, std::num::ParseIntError>> {
70        self.get_param(name).map(str::parse::<u32>)
71    }
72
73    /// Parse a parameter as f64.
74    pub fn get_param_float(&self, name: &str) -> Option<Result<f64, std::num::ParseFloatError>> {
75        self.get_param(name).map(str::parse::<f64>)
76    }
77
78    /// Parse a parameter as f32.
79    pub fn get_param_f32(&self, name: &str) -> Option<Result<f32, std::num::ParseFloatError>> {
80        self.get_param(name).map(str::parse::<f32>)
81    }
82}
83
84/// Result of attempting to locate a route by path and method.
85#[derive(Debug)]
86pub enum RouteLookup<'a> {
87    /// A route matched by path and method.
88    Match(RouteMatch<'a>),
89    /// Path matched, but method is not allowed.
90    MethodNotAllowed { allowed: AllowedMethods },
91    /// No route matched the path.
92    NotFound,
93}
94
95/// Allowed methods for a matched path.
96#[derive(Debug, Clone)]
97pub struct AllowedMethods {
98    methods: Vec<Method>,
99}
100
101impl AllowedMethods {
102    /// Create a normalized allow list.
103    ///
104    /// - Adds `HEAD` if `GET` is present.
105    /// - Sorts and de-duplicates for stable output.
106    #[must_use]
107    pub fn new(mut methods: Vec<Method>) -> Self {
108        if methods.contains(&Method::Get) && !methods.contains(&Method::Head) {
109            methods.push(Method::Head);
110        }
111        methods.sort_by_key(method_order);
112        methods.dedup();
113        Self { methods }
114    }
115
116    /// Access the normalized methods.
117    #[must_use]
118    pub fn methods(&self) -> &[Method] {
119        &self.methods
120    }
121
122    /// Check whether a method is allowed.
123    #[must_use]
124    pub fn contains(&self, method: Method) -> bool {
125        self.methods.contains(&method)
126    }
127
128    /// Format as an HTTP Allow header value.
129    #[must_use]
130    pub fn header_value(&self) -> String {
131        let mut out = String::new();
132        for (idx, method) in self.methods.iter().enumerate() {
133            if idx > 0 {
134                out.push_str(", ");
135            }
136            out.push_str(method.as_str());
137        }
138        out
139    }
140}
141
142fn method_order(method: &Method) -> u8 {
143    match *method {
144        Method::Get => 0,
145        Method::Head => 1,
146        Method::Post => 2,
147        Method::Put => 3,
148        Method::Delete => 4,
149        Method::Patch => 5,
150        Method::Options => 6,
151        Method::Trace => 7,
152    }
153}