Skip to content
Tommi Reiman edited this page Dec 27, 2016 · 3 revisions

Endpoints

  • GET, POST, PUT, HEAD, PATCH, DELETE, OPTIONS and ANY

Compojure-api has it's own versions of Compojure http endpoint macros in compojure.api.core (and also in compojure.api.sweet). They can be used just like the originals, but they also support the compojure-api restructuring syntax.

(require '[compojure.api.sweet :refer :all])
(require '[ring.util.http-response :refer :all])

;; vanilla compojure app
(def app
  (GET "/ping" []
    (ok {:ping "pong"})))

(app {:request-method :get, :uri "/ping"})
; => {:status 200, :headers {}, :body {:ping "pong"}}

Routes and Contexts

  • routes can be used to group routes together (match-first)
  • context allows routes to be grouped under a common path prefix
(def app
  (routes
    (GET "/ping" []
      (ok {:ping "pong"}))
    (context "/api" []
      (GET "/kikka" []
        (ok {:kikka true}))
      (POST "/kukka" []
        (ok {:kukka true})))))

(app {:request-method :get, :uri "/ping"})
; {:status 200, :headers {}, :body {:ping "pong"}}

(app {:request-method :get, :uri "/api/kikka"})
; {:status 200, :headers {}, :body {:kikka true}}

(app {:request-method :post, :uri "/api/kukka"})
; {:status 200, :headers {}, :body {:kukka true}}

Restructuring

Both context and all endpoint macros can have a extra restructuring map (or pairs of keyword key + value) after the 3 compojure-params before the actual body of a handler function. Restructuring is applied at macro-expansion time, enabling new code to be emitted into the handler. A multimethod compojure.api.meta/restructure-param is used as restructuring key dispatch. Compojure-api ships with a set of predefined resturcture dispatch keys, for things like validation, api-docs and more.

;; keys & vals
(GET "/ping" []
  :summary "this is a ping endpoint"
  (ok {:ping "pong"}))

;; as map
(GET "/ping" []
  {:summary "this is a ping endpoint"}
  (ok {:ping "pong"}))

A more useful example, with typed & automatically coerced query-parameters and response, producing also relevand swagger-docs.

(GET "/plus" []
  :query-params [x :- Long, y :- Long]
  :return {:result Long}
  (ok {:result (+ x y)}))

Out-of-the-box restructurings

TODO Rewrite this with code-samples

;;
;; Pass-through swagger metadata
;;

(defmethod restructure-param :summary [k v acc]
  (update-in acc [:swagger] assoc k v))

(defmethod restructure-param :description [k v acc]
  (update-in acc [:swagger] assoc k v))

(defmethod restructure-param :operationId [k v acc]
  (update-in acc [:swagger] assoc k v))

(defmethod restructure-param :consumes [k v acc]
  (update-in acc [:swagger] assoc k v))

(defmethod restructure-param :produces [k v acc]
  (update-in acc [:swagger] assoc k v))

;;
;; Smart restructurings
;;

; Boolean to discard the route out from api documentation
; Example:
; :no-doc true
(defmethod restructure-param :no-doc [_ v acc]
  (update-in acc [:swagger] assoc :x-no-doc v))

; publishes the data as swagger-parameters without any side-effects / coercion.
; Examples:
; :swagger {:responses {200 {:schema User}
;                       404 {:schema Error
;                            :description "Not Found"} }
;           :paramerers {:query {:q s/Str}
;                        :body NewUser}}}
(defmethod restructure-param :swagger [_ swagger acc]
  (assoc-in acc [:swagger :swagger] swagger))

; Route name, used with path-for
; Example:
; :name :user-route
(defmethod restructure-param :name [_ v acc]
  (update-in acc [:swagger] assoc :x-name v))

; Tags for api categorization. Ignores duplicates.
; Examples:
; :tags [:admin]
(defmethod restructure-param :tags [_ tags acc]
  (update-in acc [:swagger :tags] (comp set into) tags))

; Defines a return type and coerces the return value of a body against it.
; Examples:
; :return MySchema
; :return {:value String}
; :return #{{:key (s/maybe Long)}}
(defmethod restructure-param :return [_ schema acc]
  (let [response (convert-return schema)]
    (-> acc
        (update-in [:swagger :responses] (fnil conj []) response)
        (update-in [:responses] (fnil conj []) response))))

; value is a map of http-response-code -> Schema. Translates to both swagger
; parameters and return schema coercion. Schemas can be decorated with meta-data.
; Examples:
; :responses {403 nil}
; :responses {403 {:schema ErrorEnvelope}}
; :responses {403 {:schema ErrorEnvelope, :description \"Underflow\"}}
(defmethod restructure-param :responses [_ responses acc]
  (-> acc
      (update-in [:swagger :responses] (fnil conj []) responses)
      (update-in [:responses] (fnil conj []) responses)))

; reads body-params into a enhanced let. First parameter is the let symbol,
; second is the Schema to be coerced! against.
; Examples:
; :body [user User]
(defmethod restructure-param :body [_ [value schema] acc]
  (-> acc
      (update-in [:lets] into [value (src-coerce! schema :body-params :body)])
      (assoc-in [:swagger :parameters :body] schema)))

; reads query-params into a enhanced let. First parameter is the let symbol,
; second is the Schema to be coerced! against.
; Examples:
; :query [user User]
(defmethod restructure-param :query [_ [value schema] acc]
  (-> acc
      (update-in [:lets] into [value (src-coerce! schema :query-params :string)])
      (assoc-in [:swagger :parameters :query] schema)))

; reads header-params into a enhanced let. First parameter is the let symbol,
; second is the Schema to be coerced! against.
; Examples:
; :headers [headers Headers]
(defmethod restructure-param :headers [_ [value schema] acc]
  (-> acc
      (update-in [:lets] into [value (src-coerce! schema :headers :string)])
      (assoc-in [:swagger :parameters :header] schema)))

; restructures body-params with plumbing letk notation. Example:
; :body-params [id :- Long name :- String]
(defmethod restructure-param :body-params [_ body-params acc]
  (let [schema (strict (fnk-schema body-params))]
    (-> acc
        (update-in [:letks] into [body-params (src-coerce! schema :body-params :body)])
        (assoc-in [:swagger :parameters :body] schema))))

; restructures form-params with plumbing letk notation. Example:
; :form-params [id :- Long name :- String]
(defmethod restructure-param :form-params [_ form-params acc]
  (let [schema (strict (fnk-schema form-params))]
    (-> acc
        (update-in [:letks] into [form-params (src-coerce! schema :form-params :string)])
        (update-in [:swagger :parameters :formData] st/merge schema)
        (assoc-in [:swagger :consumes] ["application/x-www-form-urlencoded"]))))

; restructures multipart-params with plumbing letk notation and consumes "multipart/form-data"
; :multipart-params [file :- compojure.api.upload/TempFileUpload]
(defmethod restructure-param :multipart-params [_ params acc]
  (let [schema (strict (fnk-schema params))]
    (-> acc
        (update-in [:letks] into [params (src-coerce! schema :multipart-params :string)])
        (update-in [:swagger :parameters :formData] st/merge schema)
        (assoc-in [:swagger :consumes] ["multipart/form-data"]))))

; restructures header-params with plumbing letk notation. Example:
; :header-params [id :- Long name :- String]
(defmethod restructure-param :header-params [_ header-params acc]
  (let [schema (fnk-schema header-params)]
    (-> acc
        (update-in [:letks] into [header-params (src-coerce! schema :headers :string)])
        (assoc-in [:swagger :parameters :header] schema))))

; restructures query-params with plumbing letk notation. Example:
; :query-params [id :- Long name :- String]
(defmethod restructure-param :query-params [_ query-params acc]
  (let [schema (fnk-schema query-params)]
    (-> acc
        (update-in [:letks] into [query-params (src-coerce! schema :query-params :string)])
        (assoc-in [:swagger :parameters :query] schema))))

; restructures path-params by plumbing letk notation. Example:
; :path-params [id :- Long name :- String]
(defmethod restructure-param :path-params [_ path-params acc]
  (let [schema (fnk-schema path-params)]
    (-> acc
        (update-in [:letks] into [path-params (src-coerce! schema :route-params :string)])
        (assoc-in [:swagger :parameters :path] schema))))

; Applies the given vector of middlewares to the route
(defmethod restructure-param :middleware [_ middleware acc]
  (update-in acc [:middleware] into middleware))

; Bind to stuff in request components using letk syntax
(defmethod restructure-param :components [_ components acc]
  (update-in acc [:letks] into [components `(mw/get-components ~+compojure-api-request+)]))

; route-specific override for coercers
(defmethod restructure-param :coercion [_ coercion acc]
  (update-in acc [:middleware] conj [mw/wrap-coercion coercion]))

Create your own restructuring

See https://github.com/metosin/compojure-api/wiki/Creating-your-own-metadata-handlers

Clone this wiki locally