server_nano 0.2.1

A fast and lightweight HTTP server implementation in Rust.
Documentation
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use std::io;
use std::sync::Arc;

use crate::request::request::Request;
use crate::Response;

pub type RouteHandler =
    Box<dyn Fn(Request, &mut Response) -> io::Result<()> + Send + Sync + 'static>;

#[derive(Clone)]
pub struct RouteMatcher {
    routes: HashSet<RouteNode>,
}

struct RouteNode {
    method: String,
    handler: Arc<RouteHandler>,
    path: String,
    segments: Vec<Segment>,
}

impl Clone for RouteNode {
    fn clone(&self) -> Self {
        RouteNode {
            method: self.method.clone(),
            path: self.path.clone(),
            segments: self.segments.clone(),
            handler: Arc::clone(&self.handler),
        }
    }
}

#[derive(PartialEq, Eq, Hash, Clone)]
enum Segment {
    Static(String),
    Parameter(String),
    Wildcard,
}

impl Hash for RouteNode {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.method.hash(state);
        self.path.hash(state);
    }
}

impl PartialEq for RouteNode {
    fn eq(&self, other: &Self) -> bool {
        self.method == other.method && self.path == other.path
    }
}

impl Eq for RouteNode {}

pub struct MatchedRoute {
    pub method: String,
    pub path: String,
    pub parameters: HashMap<String, String>,
    pub url_parameters: HashMap<String, String>,
    pub handler: Arc<RouteHandler>,
}

impl RouteMatcher {
    pub fn new() -> RouteMatcher {
        RouteMatcher {
            routes: HashSet::new(),
        }
    }

    pub fn add_route(&mut self, method: &str, path: &str, handler: RouteHandler) {
        let segments = path
            .split('/')
            .filter(|s| !s.is_empty())
            .map(|s| {
                if s.starts_with(':') {
                    Segment::Parameter(s[1..].to_string())
                } else if s == "*" {
                    Segment::Wildcard
                } else {
                    Segment::Static(s.to_string())
                }
            })
            .collect::<Vec<_>>();
        self.routes.insert(RouteNode {
            method: method.to_string(),
            path: path.to_string(),
            segments,
            handler: Arc::new(handler),
        });
    }

    pub fn match_route(&self, method: &str, url: &str) -> Option<MatchedRoute> {
        let (path, query_string) = url.split_at(url.find('?').unwrap_or_else(|| url.len()));
        let segments = path
            .split('/')
            .filter(|s| !s.is_empty())
            .collect::<Vec<_>>();

        for route in &self.routes {
            if let Some(parameters) = route.match_segments(&segments) {
                let url_parameters = query_string
                    .trim_start_matches('?')
                    .split('&')
                    .filter(|s| !s.is_empty())
                    .map(|s| {
                        let mut parts = s.split('=');
                        (
                            parts.next().unwrap().to_string(),
                            parts.next().unwrap_or("").to_string(),
                        )
                    })
                    .collect::<HashMap<_, _>>();

                if &route.method != method && route.method != "*" {
                    continue;
                }
                return Some(MatchedRoute {
                    method: route.method.clone(),
                    path: route.path.clone(),
                    parameters,
                    url_parameters,
                    handler: Arc::clone(&route.handler),
                });
            }
        }

        None
    }
}

impl RouteNode {
    fn match_segments(&self, segments: &[&str]) -> Option<HashMap<String, String>> {
        if self.segments.len() != segments.len() && !self.segments.contains(&Segment::Wildcard) {
            return None;
        }

        let mut parameters = HashMap::new();
        let mut wildcard = false;

        for (route_segment, segment) in self.segments.iter().zip(segments.iter()) {
            match route_segment {
                Segment::Static(s) => {
                    if s != segment {
                        return None;
                    }
                }
                Segment::Parameter(param) => {
                    parameters.insert(param.clone(), (*segment).to_string());
                }
                Segment::Wildcard => {
                    wildcard = true;
                    break;
                }
            }
        }

        if wildcard {
            Some(parameters)
        } else if self.segments.len() == segments.len() {
            Some(parameters)
        } else {
            None
        }
    }
}