mmids_core/http_api/
routing.rs

1//! Provides mechanisms to define routes for the Mmids HTTP apis, and what code should be executed
2//! for each route.
3
4use async_trait::async_trait;
5use hyper::{Body, Method, Request, Response};
6use std::collections::HashMap;
7
8/// Defines how a single fragment of the URL path should be read as.  Each part is the whole value
9/// between a `/` and either another `/` or the end of the string.  Query parameters are not
10/// considered.
11#[derive(Clone)]
12pub enum PathPart {
13    /// The fragment of the path should match this exact string value.  This *is* case sensitive.
14    Exact { value: String },
15
16    /// The fragment of the path can match any string.  The string value of this part of the path
17    /// will be stored as a parameter with the key being the specified name of the parameter, and
18    /// the value being the actual string in the path.
19    Parameter { name: String },
20}
21
22/// Represents code that will be executed for a given route.
23///
24/// Note: this trait uses the `async_trait` crate
25#[async_trait]
26pub trait RouteHandler {
27    /// Executes the handler for the specified HTTP request and pre-parsed path parameters.
28    ///
29    /// Note that implementors can use `async_trait` to clean up the signature.
30    async fn execute(
31        &self,
32        request: &mut Request<Body>,
33        path_parameters: HashMap<String, String>,
34        request_id: String,
35    ) -> Result<Response<Body>, hyper::Error>;
36}
37
38/// Defines the HTTP method, a specific path, and which handler should execute requests that match
39/// the route.
40pub struct Route {
41    pub method: Method,
42    pub path: Vec<PathPart>,
43    pub handler: Box<dyn RouteHandler + Sync + Send>,
44}
45
46/// Errors that can occur when registering new routes with the routing table
47#[derive(thiserror::Error, Debug)]
48pub enum RouteRegistrationError {
49    /// Raised when attempting to register a route whose http method and path parts match an
50    /// route that's already been registered.
51    #[error("A route is already registered that conflicts with this route")]
52    RouteConflict,
53}
54
55/// A system that contains all available routes.  Routes may be registered with it and can then be
56/// looked up from.
57pub struct RoutingTable {
58    routes: HashMap<Method, RouteNode>,
59}
60
61#[derive(PartialEq, Eq, Hash)]
62enum SearchablePathPart {
63    Exact(String),
64    Parameter,
65}
66
67struct RouteNode {
68    leaf: Option<Route>,
69    children: HashMap<SearchablePathPart, RouteNode>,
70}
71
72impl RoutingTable {
73    /// Creates an empty routing table
74    pub fn new() -> Self {
75        RoutingTable {
76            routes: HashMap::new(),
77        }
78    }
79
80    /// Registers a route to be available by the routing table
81    pub fn register(&mut self, route: Route) -> Result<(), RouteRegistrationError> {
82        let mut node = self
83            .routes
84            .entry(route.method.clone())
85            .or_insert(RouteNode {
86                leaf: None,
87                children: HashMap::new(),
88            });
89
90        for part in &route.path {
91            let searchable_part = match part {
92                PathPart::Exact { value: name } => SearchablePathPart::Exact(name.clone()),
93                PathPart::Parameter { .. } => SearchablePathPart::Parameter,
94            };
95
96            node = node.children.entry(searchable_part).or_insert(RouteNode {
97                leaf: None,
98                children: HashMap::new(),
99            });
100        }
101
102        if node.leaf.is_some() {
103            return Err(RouteRegistrationError::RouteConflict);
104        }
105
106        node.leaf = Some(route);
107
108        Ok(())
109    }
110
111    pub(super) fn get_route(&self, method: &Method, path_parts: &Vec<&str>) -> Option<&Route> {
112        let node = match self.routes.get(method) {
113            Some(node) => node,
114            None => return None,
115        };
116
117        find_route(0, &path_parts, node)
118    }
119}
120
121fn find_route<'a>(
122    index: usize,
123    parts: &Vec<&str>,
124    current_node: &'a RouteNode,
125) -> Option<&'a Route> {
126    if index >= parts.len() {
127        return match &current_node.leaf {
128            Some(route) => Some(route),
129            None => None,
130        };
131    }
132
133    if let Some(exact_child) = current_node
134        .children
135        .get(&SearchablePathPart::Exact(parts[index].to_string()))
136    {
137        if let Some(route) = find_route(index + 1, parts, exact_child) {
138            return Some(route);
139        }
140    }
141
142    if let Some(parameter_child) = current_node.children.get(&SearchablePathPart::Parameter) {
143        if let Some(route) = find_route(index + 1, parts, parameter_child) {
144            return Some(route);
145        }
146    }
147
148    None
149}
150
151impl Route {
152    pub(super) fn get_parameters(&self, path_parts: &Vec<&str>) -> HashMap<String, String> {
153        let mut results = HashMap::new();
154        for x in 0..self.path.len() {
155            if let PathPart::Parameter { name } = &self.path[x] {
156                results.insert(name.clone(), path_parts[x].to_string());
157            }
158        }
159
160        results
161    }
162}