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!"})))))))
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.