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
/// Returned by `router![]` to signify if the request was handled
pub enum RouterResult<T, U> {
    /// Response of the request
    Handled(T),
    /// Request type has been returned
    NotHandled(U),
}

impl<T, U> From<T> for RouterResult<T, U> {
    fn from(value: T) -> Self {
        RouterResult::Handled(value)
    }
}

pub enum Segment {
    Glob,
    Wildcard,
    Literal(&'static str),
    Argument(&'static str),
}

pub struct ConstRoute<T, U>
where
    T: 'static,
    U: 'static,
{
    /// All uppercase HTTP verb: GET, PUT, POST, DELETE, etc
    pub method: &'static str,

    /// The route's path segments
    pub segments: &'static [Segment],

    /// How many Segment::Argument(..) this route has
    pub args_count: usize,

    /// If the route globs (**) the end of the path
    pub has_glob: bool,

    /// A wrapper Fn that will invoke the route and pass in args
    pub wrapper: &'static dyn Fn(T, &[&str]) -> U,
}

impl<T, U> ConstRoute<T, U>
where
    T: 'static,
    U: 'static,
{
    #[inline]
    pub fn matches(&self, method: &str, segments: &[&str]) -> bool {
        // match the verb, returning None if there is no match
        if self.method != method {
            return false;
        }

        // match segments, returning None if there is no match
        // If there aren't any wildcard segments
        if !self.has_glob && self.segments.len() != segments.len() {
            return false;
        }

        // Check the segments of the request, returning None if a match fails
        let segments_zip = self.segments.iter().zip(segments.iter());
        for (route_segment, request_segment) in segments_zip {
            match route_segment {
                Segment::Literal(seg_lit) => {
                    if request_segment != seg_lit {
                        return false;
                    }
                }
                Segment::Glob => {
                    // Globs are only allowed at the end and match everything
                    return true;
                }
                Segment::Wildcard | Segment::Argument(_) => {
                    // skip the match
                }
            }
        }

        true
    }
}

/// Tries to match `request` to a route in `routes`.
/// On route match, the result of the route is returned in `Ok(_)`
/// If no route matches, `request` is returned in `Err(_)`
pub fn router<T, U>(request: T, routes: &'static [&ConstRoute<T, U>]) -> RouterResult<U, T>
where
    T: ::mimeograph_request::Request,
{
    let request_method: ::std::borrow::Cow<'_, str> =
        ::mimeograph_request::Request::method(&request);

    // create an array of Segments that can be matched
    let path = ::mimeograph_request::Request::path(&request).into_owned();
    let trimmed_path = path.trim_start_matches('/');
    let request_segments: Vec<&str> = trimmed_path.split('/').collect();

    for &route in routes {
        if !route.matches(request_method.as_ref(), &request_segments) {
            continue;
        }

        let ConstRoute {
            segments,
            wrapper,
            args_count,
            ..
        } = *route;

        // We have a WINNER!
        // Collect the args
        let mut args = Vec::with_capacity(args_count);
        let segments_zip = segments.iter().zip(request_segments.iter());
        for (route_segment, request_segment) in segments_zip {
            match route_segment {
                Segment::Literal(_) | Segment::Glob | Segment::Wildcard => {}
                Segment::Argument(_) => {
                    // skip the match
                    // store the argument
                    args.push(*request_segment);
                }
            }
        }

        return RouterResult::Handled(wrapper(request, &args));
    }

    RouterResult::NotHandled(request)
}