1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! Provides mechanisms to define routes for the Mmids HTTP apis, and what code should be executed
//! for each route.

use async_trait::async_trait;
use hyper::{Body, Method, Request, Response};
use std::collections::HashMap;

/// Defines how a single fragment of the URL path should be read as.  Each part is the whole value
/// between a `/` and either another `/` or the end of the string.  Query parameters are not
/// considered.
#[derive(Clone)]
pub enum PathPart {
    /// The fragment of the path should match this exact string value.  This *is* case sensitive.
    Exact { value: String },

    /// The fragment of the path can match any string.  The string value of this part of the path
    /// will be stored as a parameter with the key being the specified name of the parameter, and
    /// the value being the actual string in the path.
    Parameter { name: String },
}

/// Represents code that will be executed for a given route.
///
/// Note: this trait uses the `async_trait` crate
#[async_trait]
pub trait RouteHandler {
    /// Executes the handler for the specified HTTP request and pre-parsed path parameters.
    ///
    /// Note that implementors can use `async_trait` to clean up the signature.
    async fn execute(
        &self,
        request: &mut Request<Body>,
        path_parameters: HashMap<String, String>,
        request_id: String,
    ) -> Result<Response<Body>, hyper::Error>;
}

/// Defines the HTTP method, a specific path, and which handler should execute requests that match
/// the route.
pub struct Route {
    pub method: Method,
    pub path: Vec<PathPart>,
    pub handler: Box<dyn RouteHandler + Sync + Send>,
}

/// Errors that can occur when registering new routes with the routing table
#[derive(thiserror::Error, Debug)]
pub enum RouteRegistrationError {
    /// Raised when attempting to register a route whose http method and path parts match an
    /// route that's already been registered.
    #[error("A route is already registered that conflicts with this route")]
    RouteConflict,
}

/// A system that contains all available routes.  Routes may be registered with it and can then be
/// looked up from.
#[derive(Default)]
pub struct RoutingTable {
    routes: HashMap<Method, RouteNode>,
}

#[derive(PartialEq, Eq, Hash)]
enum SearchablePathPart {
    Exact(String),
    Parameter,
}

struct RouteNode {
    leaf: Option<Route>,
    children: HashMap<SearchablePathPart, RouteNode>,
}

impl RoutingTable {
    /// Creates an empty routing table
    pub fn new() -> Self {
        Default::default()
    }

    /// Registers a route to be available by the routing table
    pub fn register(&mut self, route: Route) -> Result<(), RouteRegistrationError> {
        let mut node = self
            .routes
            .entry(route.method.clone())
            .or_insert(RouteNode {
                leaf: None,
                children: HashMap::new(),
            });

        for part in &route.path {
            let searchable_part = match part {
                PathPart::Exact { value: name } => SearchablePathPart::Exact(name.clone()),
                PathPart::Parameter { .. } => SearchablePathPart::Parameter,
            };

            node = node.children.entry(searchable_part).or_insert(RouteNode {
                leaf: None,
                children: HashMap::new(),
            });
        }

        if node.leaf.is_some() {
            return Err(RouteRegistrationError::RouteConflict);
        }

        node.leaf = Some(route);

        Ok(())
    }

    pub(super) fn get_route(&self, method: &Method, path_parts: &Vec<&str>) -> Option<&Route> {
        let node = match self.routes.get(method) {
            Some(node) => node,
            None => return None,
        };

        find_route(0, path_parts, node)
    }
}

fn find_route<'a>(
    index: usize,
    parts: &Vec<&str>,
    current_node: &'a RouteNode,
) -> Option<&'a Route> {
    if index >= parts.len() {
        return match &current_node.leaf {
            Some(route) => Some(route),
            None => None,
        };
    }

    if let Some(exact_child) = current_node
        .children
        .get(&SearchablePathPart::Exact(parts[index].to_string()))
    {
        if let Some(route) = find_route(index + 1, parts, exact_child) {
            return Some(route);
        }
    }

    if let Some(parameter_child) = current_node.children.get(&SearchablePathPart::Parameter) {
        if let Some(route) = find_route(index + 1, parts, parameter_child) {
            return Some(route);
        }
    }

    None
}

impl Route {
    pub(super) fn get_parameters(&self, path_parts: &[&str]) -> HashMap<String, String> {
        let mut results = HashMap::new();
        for (x, path_part) in self.path.iter().enumerate() {
            if let PathPart::Parameter { name } = path_part {
                results.insert(name.clone(), path_parts[x].to_string());
            }
        }

        results
    }
}