Skip to content

Creating your own metadata handlers

Tommi Reiman edited this page Feb 9, 2016 · 2 revisions

Restructuring

Compojure-api handles the route metadata by calling the multimethod compojure.api.meta/restructure-param with metadata key as a dispatch value.

Multimethods take three parameters:

  1. metadata key
  2. metadata value
  3. accumulator map with keys
    • :lets, a vector of let bindings applied first before the actual body
    • :letks, a vector of letk bindings applied second before the actual body
    • :middleware, a vector of route specific middleware (applied from left to right)
    • :swagger, swaggerd-data of a route (without the key & value for the current multimethod)
    • :body, a sequence of the actual route body

.. and should return the modified accumulator. Multimethod calls are reduced to produce the final accumulator for code generation. Defined key-value -based metadatas for routes are guaranteed to run on top-to-bottom order of the so all the potential let and letk variable overrides can be solved by the client. Default implementation is to keep the key & value as a route metadata.

Example

Creating your own meta-data handler for role-based security.

(defn require-role! [required roles]
  (if-not (seq (clojure.set/intersection required roles))
    (ring.util.http-response/unauthorized! {:text "missing role", :required required, :roles roles})))

(defmethod compojure.api.meta/restructure-param :roles [_ roles acc]
  (update-in acc [:lets] into ['_ `(require-role! ~roles (:roles ~'+compojure-api-request+))]))

Using it:

(GET "/admin" []
  :roles #{:admin}
  (ok {:message "welcome!"})))

(r {:request-method :get :uri "/admin"})                    
; => throws 401

(r {:request-method :get :uri "/admin" :roles #{:admin}})
; => ok

macroexpanding-1 it too see what's get generated:

; normal case
(macroexpand-1
  `(GET "/admin" []
     (ok {:message "welcome!"})))

; (compojure.api.routes/create
;   "/admin"
;   :get
;   (compojure.api.meta/merge-parameters {})
;   nil
;   (compojure.core/make-route
;     :get
;     #clout.core.CompiledRoute{:source "/admin", :re #"/admin", :keys [], :absolute? false}
;     (clojure.core/fn [request__14718__auto__]
;       (compojure.core/let-request [[:as +compojure-api-request+] request__14718__auto__]
;         (do (ring.util.http-response/ok {:message "welcome!"}))))))

(macroexpand-1
  `(GET "/admin" []
    :roles #{:admin}
    (ok {:message "welcome!"})))

; (compojure.api.routes/create
;   "/admin"
;   :get
;   (compojure.api.meta/merge-parameters {})
;   nil
;   (compojure.core/make-route
;     :get
;     #clout.core.CompiledRoute{:source "/admin", :re #"/admin", :keys [], :absolute? false}
;     (clojure.core/fn [request__14718__auto__]
;       (compojure.core/let-request [[:as +compojure-api-request+] request__14718__auto__]
;         (clojure.core/let [_ (sampo.restructure/require-role! #{:admin} (:roles +compojure-api-request+))]
;           (do (ring.util.http-response/ok {:message "welcome!"})))))))