peta 0.0.4

Very small and simple http library
Documentation
// implement router logic
use crate::method;
use crate::request;
use crate::response;

use hashbrown;
use tokio::prelude::*;

use std::sync::Arc;

/// Boxed future which should be after executing any of the Router functions
pub type ReturnFuture = Box<dyn Future<Item = response::Response, Error = ()> + Send + Sync>;

type StoreFunc = Box<
  dyn Fn(request::Request) -> Box<dyn Future<Item = response::Response, Error = ()> + Send + Sync>
    + Send
    + Sync,
>;

struct Node {
  param: Option<&'static str>,
  method: Option<StoreFunc>,
  children: Option<hashbrown::HashMap<&'static str, Node>>,
}

impl Node {
  pub fn default() -> Node {
    Node {
      param: None,
      method: None,
      children: None,
    }
  }

  pub fn set_func(&mut self, func: StoreFunc) {
    self.method = Some(func);
  }

  pub fn add_child(&mut self, seg: &'static str, param: Option<&'static str>) -> &mut Node {
    if self.children.is_none() {
      self.children = Some(hashbrown::HashMap::new())
    }

    let node_map = self.children.as_mut().unwrap();

    // if key exist then return existing node ref
    if node_map.contains_key(seg) {
      return node_map.get_mut(seg).unwrap();
    }

    // create new if node
    node_map.insert(
      seg,
      Node {
        param,
        method: None,
        children: None,
      },
    );
    // this item is just added
    node_map.get_mut(seg).unwrap()
  }
}

/// Simple mapper which maps different route to the
/// corresponding functions which will be executed in
/// case of match
///
/// # Example
///
/// ```
/// let mut router = Router::new();
///
/// router.get("/", hello_world);
/// router.get("/home", home);
///
/// // It is important to add default route
/// router.add_default(not_found);
///
/// let router = router.build();
/// ```
///
pub struct Router {
  get: Node,
  put: Node,
  post: Node,
  head: Node,
  patch: Node,
  delete: Node,
  options: Node,
  routes: Node,
  default: Option<StoreFunc>,
}

impl Router {
  pub fn new() -> Router {
    Router {
      default: None,
      // for now leave this one in
      routes: Node::default(),
      // create separate bucket for each method (not fun) :(
      get: Node::default(),
      put: Node::default(),
      post: Node::default(),
      head: Node::default(),
      patch: Node::default(),
      delete: Node::default(),
      options: Node::default(),
    }
  }

  /// build wraps router in Arc so that we can copy ref
  /// pointer in to the tokio stream and fold functions
  pub fn build(self) -> Arc<Self> {
    Arc::new(self)
  }

  // TODO: sort out default route it does not look good
  /// Is used to find appropriate Node instance in Route map and return Boxed future
  /// example `find(req)` will return future which can be chained and executed
  pub fn find(&self, mut req: request::Request) -> ReturnFuture {
    let mut node = match req.method() {
      method::GET => &self.get,
      method::PUT => &self.put,
      method::POST => &self.post,
      method::HEAD => &self.head,
      method::PATCH => &self.patch,
      method::DELETE => &self.delete,
      method::OPTIONS => &self.options,
      _ => &self.routes,
    };

    // handle / route
    if req.uri().path() == "/" {
      return match node.method.as_ref() {
        Some(v) => (v)(req),
        None => (self.default.as_ref().unwrap())(req),
      };
    }

    let mut params: Vec<(&'static str, String)> = Vec::with_capacity(10);

    for seg in req.uri().path().split('/') {
      if !seg.is_empty() {
        if node.children.is_none() {
          // do default return
          return (self.default.as_ref().unwrap())(req);
        }

        let children = node.children.as_ref().unwrap();

        // find proper node with func
        let found_node = match children.get(seg) {
          Some(v) => v,
          None => {
            match children.get(":") {
              Some(v) => {
                params.push((v.param.unwrap(), seg.to_string()));
                v
              }
              // break from this function if we get in star
              None => match children.get("*") {
                Some(v) => {
                  // we need to attache params in here as we may and loop sooner
                  if !params.is_empty() {
                    req.set_params(Some(params));
                  }

                  return (v.method.as_ref().unwrap())(req);
                }
                None => {
                  // execute default if route not found at all
                  return (self.default.as_ref().unwrap())(req);
                }
              },
            }
          }
        };

        node = found_node;
      }
    }

    match node.method.as_ref() {
      Some(v) => {
        // set params only if it is not empty
        if !params.is_empty() {
          req.set_params(Some(params));
        }
        (v)(req)
      }
      None => (self.default.as_ref().unwrap())(req),
    }
  }

  /// Create and adds Node instances to the Router, can be used to attach
  /// different requests to the router for example `router.add(method::PATCH, path, func)`
  pub fn add<F>(&mut self, method: &str, path: &'static str, func: F)
  where
    F: Fn(request::Request) -> Box<Future<Item = response::Response, Error = ()> + Send + Sync>
      + Send
      + Sync
      + 'static,
  {
    // use proper enum
    let mut node = match method {
      method::GET => &mut self.get,
      method::PUT => &mut self.put,
      method::POST => &mut self.post,
      method::HEAD => &mut self.head,
      method::PATCH => &mut self.patch,
      method::DELETE => &mut self.delete,
      method::OPTIONS => &mut self.options,
      _ => &mut self.routes,
    };

    match path {
      "/" => {
        // handle / case
        node.set_func(Box::new(func));
      }
      _ => {
        // handle rest of the cases
        for seg in path.split('/') {
          if !seg.is_empty() {
            let mut seg_arr = seg.chars();
            // check if path is param
            if seg_arr.next() == Some(':') {
              node = node.add_child(":", Some(seg_arr.as_str()));
              continue;
            }
            node = node.add_child(seg, None);
          }
        }

        node.set_func(Box::new(func));
      }
    }
  }
}

/// Router helpers methods to simplify route creation
impl Router {
  /// Helper for `add` method which simplifies creation of `get` router
  /// instead of `router.add(method::GET, path, func)` you can write
  /// `router.get(path, func)`
  pub fn get<F>(&mut self, path: &'static str, func: F)
  where
    F: Fn(request::Request) -> Box<Future<Item = response::Response, Error = ()> + Send + Sync>
      + Send
      + Sync
      + 'static,
  {
    self.add(method::GET, path, func)
  }

  /// Helper for `add` method which simplifies creation of `post` router
  /// instead of `router.add(method::POST, path, func)` you can write
  /// `router.post(path, func)`
  pub fn post<F>(&mut self, path: &'static str, func: F)
  where
    F: Fn(request::Request) -> Box<Future<Item = response::Response, Error = ()> + Send + Sync>
      + Send
      + Sync
      + 'static,
  {
    self.add(method::POST, path, func)
  }

  /// Helper for `add` method which simplifies creation of `put` router
  /// instead of `router.add(method::PUT, path, func)` you can write
  /// `router.put(path, func)`
  pub fn put<F>(&mut self, path: &'static str, func: F)
  where
    F: Fn(request::Request) -> Box<Future<Item = response::Response, Error = ()> + Send + Sync>
      + Send
      + Sync
      + 'static,
  {
    self.add(method::PUT, path, func)
  }

  /// Helper for `add` method which simplifies creation of `head` router
  /// instead of `router.add(method::HEAD, path, func)` you can write
  /// `router.head(path, func)`
  pub fn head<F>(&mut self, path: &'static str, func: F)
  where
    F: Fn(request::Request) -> Box<Future<Item = response::Response, Error = ()> + Send + Sync>
      + Send
      + Sync
      + 'static,
  {
    self.add(method::HEAD, path, func)
  }

  /// Helper for `add` method which simplifies creation of `patch` router
  /// instead of `router.add(method::PATCH, path, func)` you can write
  /// `router.patch(path, func)`
  pub fn patch<F>(&mut self, path: &'static str, func: F)
  where
    F: Fn(request::Request) -> Box<Future<Item = response::Response, Error = ()> + Send + Sync>
      + Send
      + Sync
      + 'static,
  {
    self.add(method::PATCH, path, func)
  }

  /// Helper for `add` method which simplifies creation of `delete` router
  /// instead of `router.add(method::DELETE, path, func)` you can write
  /// `router.delete(path, func)`
  pub fn delete<F>(&mut self, path: &'static str, func: F)
  where
    F: Fn(request::Request) -> Box<Future<Item = response::Response, Error = ()> + Send + Sync>
      + Send
      + Sync
      + 'static,
  {
    self.add(method::DELETE, path, func)
  }

  /// Helper for `add` method which simplifies creation of `options` router
  /// instead of `router.add(method::OPTIONS, path, func)` you can write
  /// `router.options(path, func)`
  pub fn options<F>(&mut self, path: &'static str, func: F)
  where
    F: Fn(request::Request) -> Box<Future<Item = response::Response, Error = ()> + Send + Sync>
      + Send
      + Sync
      + 'static,
  {
    self.add(method::OPTIONS, path, func)
  }

  /// Adds default method to the Route instance, default method is executed
  /// when no matching route found can be used to response with 404 error
  /// It is mandatory to set default method
  pub fn add_default<F>(&mut self, func: F)
  where
    F: Fn(request::Request) -> Box<Future<Item = response::Response, Error = ()> + Send + Sync>
      + Send
      + Sync
      + 'static,
  {
    self.default = Some(Box::new(func));
  }
}

// temp debug setter
impl std::fmt::Debug for Router {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    write!(
      f,
      "Router {{ \n   get: {:#?}, \npost: {:#?} \ndelete:{:#?}\nput:{:#?} \nroutes:{:#?} \n}}",
      self.get, self.post, self.delete, self.put, self.routes
    )
  }
}

impl std::fmt::Debug for Node {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    write!(
      f,
      "Node {{ \n\tchildren: {:#?}, \n\tmethod: {:#?} \n\tparam:{:#?}\n}}",
      self.children,
      self.method.is_some(),
      self.param
    )
  }
}