Resources and Liberator

Marcin Kulik edited this page Jan 8, 2017 · 4 revisions

Resources

Compojure-api also allows http-endpoints to be modeled as data-driven resources, using enhanced Ring-Swagger resource definitions. Resources might be good in the following cases:

  1. Wanting to add swagger-docs and/or schema-based coercion to your existing resource-based apis, like the Liberator.
  2. Generate endpoints based on external data, e.g.
  • generate REST or CRUD-apis from (database) entity definitions
  • generate RPC-apis from function/data definitions

Usage

Resources are created with function compojure.api.resource/resource, which is also imported in the compojure.api.sweet namespace. Both request & response coercion can be disabled for resources, so that they can be used just to generate swagger-docs on top of existing/legacy apis.

Resources are routed with context.

(context "/hello" []
  :middleware [[require-role :admin]]
  (resource
    {:description "hello-resource"
     :responses {200 {:schema {:message s/Str}}}
     :post {:summary "post-hello"
            :parameters {:body-params {:name s/Str}}
            :handler (fnk [[:body-params name]]
                       (ok {:message (format "hello, %s!" name)}))}
     :get {:summary "get-hello"
           :parameters {:query-params {:name s/Str}}
           :handler (fnk [[:query-params name]]
                      (ok {:message (format "hello, %s!" name)}))}}))

Source code

(defn resource
  "Creates a nested compojure-api Route from enhanced ring-swagger operations map and options.
  By default, applies both request- and response-coercion based on those definitions.

  Options:

  - **:coercion**       A function from request->type->coercion-matcher, used
                        in resource coercion for :body, :string and :response.
                        Setting value to `(constantly nil)` disables both request- &
                        response coercion. See tests and wiki for details.

  Enhancements to ring-swagger operations map:

  1) :parameters use ring request keys (query-params, path-params, ...) instead of
  swagger-params (query, path, ...). This keeps things simple as ring keys are used in
  the handler when destructuring the request.

  2) at resource root, one can add any ring-swagger operation definitions, which will be
  available for all operations, using the following rules:

    2.1) :parameters are deep-merged into operation :parameters
    2.2) :responses are merged into operation :responses (operation can fully override them)
    2.3) all others (:produces, :consumes, :summary,...) are deep-merged by compojure-api

  3) special key `:handler` either under operations or at top-level. Value should be a
  ring-handler function, responsible for the actual request processing. Handler lookup
  order is the following: operations-level, top-level.

  4) request-coercion is applied once, using deep-merged parameters for a given
  operation or resource-level if only resource-level handler is defined.

  5) response-coercion is applied once, using merged responses for a given
  operation or resource-level if only resource-level handler is defined.

  Note: Swagger operations are generated only from declared operations (:get, :post, ..),
  despite the top-level handler could process more operations.

  Example:

  (resource
    {:parameters {:query-params {:x Long}}
     :responses {500 {:schema {:reason s/Str}}}
     :get {:parameters {:query-params {:y Long}}
           :responses {200 {:schema {:total Long}}}
           :handler (fn [request]
                      (ok {:total (+ (-> request :query-params :x)
                                     (-> request :query-params :y))}))}
     :post {}
     :handler (constantly
                (internal-server-error {:reason \"not implemented\"}))})"
  ([info]
   (resource info {}))
  ([info options]
   (let [info (merge-parameters-and-responses info)
         root-info (swaggerize (root-info info))
         childs (create-childs info)
         handler (create-handler info options)]
     (routes/create nil nil root-info childs handler))))

More examples

Hello World

(resource
  {:handler (constantly (ok "hello world"))})

Shared parameters, more swagger-info & a fallback-handler

(resource
  {:description "shared description, can be overridden"
   :parameters {:path-params {:id Long}}
   :get {:description "get user"
         :summary "get-user ftw!"
         :handler get-user}
   :put {:parameters {:body-params User}
         :responses {200 {:schema User}}
         :handler modify-user}
   :delete {:description "delete's a user"
            :handler delete-user}
   :handler (constantly
              (internal-server-error
                {:reason "other methods not implemented"}))})

Integrated with routing

Inlined (as a closure):

(context "/plus" []
  :middleware [require-admin]
  (resource
    {:get {:parameters {:query-params {:x Long, :y Long}}
           :responses {200 {:schema {:total Long}}}
           :handler my-plus-request-handler-with-coerced-inputs-and-outputs}}))

Predefined (bit faster):

(def plus-resource
  (resource
    {:get {:parameters {:query-params {:x Long, :y Long}}
           :responses {200 {:schema {:total Long}}}
           :handler my-plus-request-handler-with-coerced-inputs-and-outputs}}))

(context "/plus" []
  :middleware [require-admin]
  plus-resource)

More examples

Integration with Liberator

  • set the Liberator resource as top-level handler
  • return raw responses to enable response coercion via :as-response or ring-response, see the guide
  • read the coerced parameters from the liberator context: (get-in ctx [:request :body-parameters])
  • optionally disable coercion on resource just to get just swagger-docs
(def user-resource
  (resource
    {:parameters {:path-params {:id Long}}
     :get {:parameters {:query-params {(s/optional-key :fields) [String]}}
           :responses {200 {:schema User}}
           :summary "returns a User (or fields of it)"}
     :put {:parameters {:body-params User}
           :responses {200 {:schema User}}
           :summary "Updates a user"}
     :delete {:summary "Deletes a user"}
     :handler my-liberator-resource}
    ;; add this if you just want the swagger-docs
    {:coercion (constantly nil)}))

(context "/user" []
  user-resource)

Feel free to update this guide. Initial discussion here.

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.