PaulLebedev / route-map

Clojure route lib

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

route-map

Clojars Project

Build Status

Half-page clojure library for routing (dispatching) in web applications.

Routes are represented as hierarchiecal hash-map:

Keys in map could be:

  • methods :GET, :POST, :PUT, :DELETE, :OPTION
  • hardcoded parts of path, for example "users"
  • vector with one keyword - parameter key - match part of path as parameter if key name ends with * (like :path*), it will match the rest of path There are could be only one parameter key per map.
  • any not listed keys, which will be present in result of looking up route
  • leafs could be anything
(def routes
  {:GET    'root
   "files" {:path* {:GET 'file}}
   "users" {:GET  'list
            :POST 'create
            [:uid] {:GET 'show
                   :PUT 'udpate
                   :DELETE 'destroy}}})

(route-map/match [:get "/unexisting"] routes) ;;=> nil
(route-map/match [:get "/users/1"] routes)
;;=> {:match 'show
;;    :parents [all nodes in path to match]
;;    :params {:uid "1"}}

(route-map/match [:get "/files/assets/img/icon.png"] routes)
;;=> {:match 'file
;;    :params {:path* ["assets" "img" "icon.png"]}
;;    :parents ...}

To match route you call:

(route-map/match [requrest-method uri] routes)

Request uri and method are transformed into vector splited by "/" - GET /users/1 => ["users" "1" :GET], which is treated as path in route tree.

If path is found match returns hash-map:

{:match node ;; matched node
 :parents parents ;; all nodes in path to matched
 :params params ;; params extracted while matching [:param-name] keys}

otherwise nil.

Library just match routes and dispatch execution is up to you:

(ns mywebapp
  (:require
    [route-map.core :as rm]
    [ring.adapter.jetty :as jetty]))

(defn list [req]...)
(defn create [req]...)
(defn show [req]...)
(defn update [req]...)
(defn delete [req]...)

(def routes
  {:interceptors ['ensure-admin]
   :GET  list
   :POST create
   [:uid] {:interceptors ['ensure-user]
           :GET show
           :PUT udpate
           :DELETE destroy}})

(defn app [{meth :request-method uri :uri :as req}]
  (if-let [res (rm/match [meth uri] routes)]
    (apply (:match res)  (update-in req [:params] merge (:params req))
    {:status 404 :body "Not found"})))

(jetty/run-jetty #'app {:port 3003 :join? false}))

See example app

ClojureScript

In ClojureScript scenario you do not have methods and to handle nested routes in the middle use magic keyword :.

(def routes
  {"admin" {"users" {:. 'users-list-view
                     [:id] 'user-view}
            "groups" 'groups-list-view}})

(match "/admin/users" routes)
;;=> {:match 'users-list-view ...}

(match "/admin/users/5" routes)
;;=> {:match 'users-list-view :params {:id "5"} ...}

(match "/admin/groups" routes)
;;=> {:match 'groups-list-view ...}

Tips

match could be used for links validation in app

(defn url [path]
  (if (match path)
    path
    (throw Exception. (str "url " path " does not match any paths"))))

One can put additional metadata into routes hash-map and interpret it in some useful way. For example dynamicaly build middlewares stack for specific paths:

(def routes
  {:interceptors [ensure-admin]
   :GET  list
   :POST create
   [:uid] {:interceptors [ensure-user]
           :GET show
           :PUT udpate
           :DELETE destroy}})

(defn app [{meth :request-method uri :uri params :params :as req}]
  (let [res (rm/match [meth uri] routes)
        ;; collect all :interceptors keys
        interceptors (mapcat :interceptors (:parents res))
        handler (:match res)
        ;; add route params to params
        req (update-in req [:params] merge (:params res))
        ;; build stack
        stack ((apply comp interceptors) handler)]
  ;; apply
  (stack req)))

Integrate with Prismatic Schema for input validation:

{"users" {:POST [UserSchema create-user]
          [:id] {:PUT [UserSchema create-user]
                 ....}}}

;; somewhere in dispatcher

(let [body (:body request)
      ;; destruct match
      [schema handler] route-match]
  (if (s/check schema body)
      (handler req)
      ....))

and generate swagger specification from routes.

License

Copyright © 2014 niquola

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

About

Clojure route lib

License:Eclipse Public License 1.0


Languages

Language:Clojure 100.0%