text: /* router.h
     Mathieu Stefani, 05 janvier 2016
  
     Simple HTTP Rest Router
  */
  
  #pragma once
  
  #include <regex>
  #include <string>
  #include <tuple>
  #include <unordered_map>
  #include <memory>
  #include <vector>
  
  #include <pistache/http.h>
  #include <pistache/http_defs.h>
  #include <pistache/flags.h>
  
  #include "pistache/string_view.h"
  
  namespace Pistache {
  namespace Rest {
  
  class Description;
  
  namespace details {
  template<typename T>
  struct LexicalCast {
      static T cast(const std::string& value) {
          std::istringstream iss(value);
          T out;
          if (!(iss >> out))
              throw std::runtime_error("Bad lexical cast");
           return out;
      }
  };
  
  template<>
  struct LexicalCast<std::string> {
      static std::string cast(const std::string& value) {
          return value;
      }
  };
  }  // namespace details
  
  class TypedParam {
  public:
      TypedParam(std::string name, std::string value)
          : name_(std::move(name))
          , value_(std::move(value))
      { }
  
      template<typename T>
      T as() const {
          return details::LexicalCast<T>::cast(value_);
      }
  
      const std::string& name() const {
          return name_;
      }
  
  private:
      const std::string name_;
      const std::string value_;
  };
  
  class Request;
  
  struct Route {
      enum class Result { Ok, Failure };
  
      enum class Status { Match, NotFound };
  
      typedef std::function<Result(const Request, Http::ResponseWriter)> Handler;
  
      explicit Route(Route::Handler handler) : handler_(std::move(handler)) { }
  
      template<typename... Args>
      void invokeHandler(Args&& ...args) const {
          handler_(std::forward<Args>(args)...);
      }
  
      Handler handler_;
  };
  
  namespace Private {
      class RouterHandler;
  }
  
  /**
   * A request URI is made of various path segments.
   * Since all routes handled by a router are naturally
   * represented as a tree, this class provides support for it.
   * It is possible to perform tree-based routing search instead
   * of linear one.
   * This class holds all data for a given path segment, meaning
   * that it holds the associated route handler (if any) and all
   * next child routes (by means of fixed routes, parametric,
   * optional parametric and splats).
   * Each child is in turn a SegmentTreeNode.
   */
  class SegmentTreeNode {
  private:
      enum class SegmentType {
          Fixed, Param, Optional, Splat
      };
  
      /**
       * string_view are very efficient when working with the
       * substring function (massively used for routing) but are
       * non-owning. To let the content survive after it is firstly
       * created, their content is stored in this reference pointer
       * that is in charge of managing their lifecycle (create on add,
       * reset on remove).
       */
      std::shared_ptr<char> resource_ref_;
  
      std::unordered_map<std::string_view, std::shared_ptr<SegmentTreeNode>> fixed_;
      std::unordered_map<std::string_view, std::shared_ptr<SegmentTreeNode>> param_;
      std::unordered_map<std::string_view, std::shared_ptr<SegmentTreeNode>> optional_;
      std::shared_ptr<SegmentTreeNode> splat_;
      std::shared_ptr<Route> route_;
  
      /**
       * Common web servers (nginx, httpd, IIS) collapse multiple
       * forward slashes to a single one. This regex is used to
       * obtain the same result.
       */
      static std::regex multiple_slash;
  
      static SegmentType getSegmentType(const std::string_view& fragment);
  
      /**
       * Fetches the route associated to a given path.
       * \param[in] path Requested resource path. Must have no leading slash
       * and no multiple slashes:
       * eg:
       * - auth/login is valid
       * - /auth/login is invalid
       * - auth//login is invalid
       * \param[in,out] params Contains all the parameters parsed so far.
       * \param[in,out] splats Contains all the splats parsed so far.
       * \returns Tuple containing the route, the list of all parsed parameters
       * and the list of all parsed splats.
       * \throws std::runtime_error An empty path was given
       */
      std::tuple<std::shared_ptr<Route>,
      std::vector<TypedParam>, std::vector<TypedParam>>
      findRoute(const std::string_view& path,
                std::vector<TypedParam> &params,
                std::vector<TypedParam> &splats) const;
  
  public:
      SegmentTreeNode();
      explicit SegmentTreeNode(const std::shared_ptr<char> &resourceReference);
  
      /**
       * Sanitizes a resource URL by removing any duplicate slash, leading
       * slash and trailing slash.
       * @param path URL to sanitize.
       * @return Sanitized URL.
       */
      static std::string sanitizeResource(const std::string& path);
  
      /**
       * Associates a route handler to a given path.
       * \param[in] path Requested resource path. Must have no leading and trailing
       * slashes and no multiple slashes:
       * eg:
       * - auth/login is valid
       * - /auth/login is invalid
       * - auth/login/ is invalid
       * - auth//login is invalid
       * \param[in] handler Handler to associate to path.
       * \param[in] resource_reference \see SegmentTreeNode::resource_ref_
       * \throws std::runtime_error An empty path was given
       */
      void addRoute(const std::string_view& path, const Route::Handler &handler,
              const std::shared_ptr<char> &resource_reference);
  
      /**
       * Removes the route handler associated to a given path.
       * \param[in] path Requested resource path. Must have no leading slash
       * and no multiple slashes:
       * eg:
       * - auth/login is valid
       * - /auth/login is invalid
       * - auth//login is invalid
       * \throws std::runtime_error An empty path was given
       */
      bool removeRoute(const std::string_view& path);
  
      /**
       * Finds the correct route for the given path.
       * \param[in] path Requested resource path. Must have no leading slash
       * and no multiple slashes:
       * eg:
       * - auth/login is valid
       * - /auth/login is invalid
       * - auth//login is invalid
       * \throws std::runtime_error An empty path was given
       * \return Found route with its resolved parameters and splats (if no route
       * is found, first element of the tuple is a null pointer).
       */
      std::tuple<std::shared_ptr<Route>,
      std::vector<TypedParam>, std::vector<TypedParam>>
      findRoute(const std::string_view& path) const;
  };
  
  class Router {
  public:
      static Router fromDescription(const Rest::Description& desc);
  
      std::shared_ptr<Private::RouterHandler>
      handler() const;
      static std::shared_ptr<Private::RouterHandler>
      handler(std::shared_ptr<Rest::Router> router);
  
      void initFromDescription(const Rest::Description& desc);
  
      void get(const std::string& resource, Route::Handler handler);
      void post(const std::string& resource, Route::Handler handler);
      void put(const std::string& resource, Route::Handler handler);
      void patch(const std::string& resource, Route::Handler handler);
      void del(const std::string& resource, Route::Handler handler);
      void options(const std::string& resource, Route::Handler handler);
      void removeRoute(Http::Method method, const std::string& resource);
  
      void addCustomHandler(Route::Handler handler);
  
      void addNotFoundHandler(Route::Handler handler);
      inline bool hasNotFoundHandler() { return notFoundHandler != nullptr; }
      void invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter resp) const;
  
      Route::Status route(const Http::Request& request, Http::ResponseWriter response);
  
  private:
  
      void addRoute(Http::Method method, const std::string& resource,
                    Route::Handler handler);
  
      std::unordered_map<Http::Method, SegmentTreeNode> routes;
  
      std::vector<Route::Handler> customHandlers;
  
      Route::Handler notFoundHandler;
  };
  
  namespace Private {
  
      class RouterHandler : public Http::Handler {
      public:
  
          /**
           * Used for immutable router. Useful if all the routes are
           * defined at compile time (and for backward compatibility)
           * \param[in] router Immutable router.
           */
          explicit RouterHandler(const Rest::Router& router);
  
          /**
           * Used for mutable router. Useful if it is required to
           * add/remove routes at runtime.
           * \param[in] router Pointer to a (mutable) router.
           */
          explicit RouterHandler(std::shared_ptr<Rest::Router> router);
  
          void onRequest(
                  const Http::Request& req,
                  Http::ResponseWriter response);
  
      private:
          std::shared_ptr<Tcp::Handler> clone() const final {
              return std::make_shared<RouterHandler>(router);
          }
  
          std::shared_ptr<Rest::Router> router;
      };
  }
  
  class Request : public Http::Request {
  public:
      friend class Router;
  
      bool hasParam(const std::string& name) const;
      TypedParam param(const std::string& name) const;
      TypedParam param(std::size_t index) const;
  
      TypedParam splatAt(size_t index) const;
      std::vector<TypedParam> splat() const;
  
  private:
      explicit Request(
              const Http::Request& request,
              std::vector<TypedParam>&& params,
              std::vector<TypedParam>&& splats);
  
      std::vector<TypedParam> params_;
      std::vector<TypedParam> splats_;
  };
  
  
  namespace Routes {
  
      void Get(Router& router, const std::string& resource, Route::Handler handler);
      void Post(Router& router, const std::string& resource, Route::Handler handler);
      void Put(Router& router, const std::string& resource, Route::Handler handler);
      void Patch(Router& router, const std::string& resource, Route::Handler handler);
      void Delete(Router& router, const std::string& resource, Route::Handler handler);
      void Options(Router& router, const std::string& resource, Route::Handler handler);
      void Remove(Router& router, Http::Method method, const std::string& resource);
  
      void NotFound(Router& router, Route::Handler handler);
  
      namespace details {
          template <class... Args>
          struct TypeList
          {
              template<size_t N>
              struct At {
                  static_assert(N < sizeof...(Args), "Invalid index");
                  typedef typename std::tuple_element<N, std::tuple<Args...>>::type Type;
              };
          };
  
          template<typename... Args>
          void static_checks() {
              static_assert(sizeof...(Args) == 2, "Function should take 2 parameters");
  //            typedef details::TypeList<Args...> Arguments;
              // Disabled now as it
              // 1/ does not compile
              // 2/ might not be relevant
  #if 0
              static_assert(std::is_same<Arguments::At<0>::Type, const Rest::Request&>::value, "First argument should be a const Rest::Request&");
              static_assert(std::is_same<typename Arguments::At<0>::Type, Http::Response>::value, "Second argument should be a Http::Response");
  #endif
          }
      }
  
  
      template<typename Result, typename Cls, typename... Args, typename Obj>
      Route::Handler bind(Result (Cls::*func)(Args...), Obj obj) {
          details::static_checks<Args...>();
  
          #define CALL_MEMBER_FN(obj, pmf)  ((obj)->*(pmf))
  
          return [=](const Rest::Request& request, Http::ResponseWriter response) {
              CALL_MEMBER_FN(obj, func)(request, std::move(response));
  
              return Route::Result::Ok;
          };
  
          #undef CALL_MEMBER_FN
      }
  
      template<typename Result, typename... Args>
      Route::Handler bind(Result (*func)(Args...)) {
          details::static_checks<Args...>();
  
          return [=](const Rest::Request& request, Http::ResponseWriter response) {
              func(request, std::move(response));
  
              return Route::Result::Ok;
          };
      }
  
  } // namespace Routing
  } // namespace Rest
  } // namespace Pistache
  