-
Notifications
You must be signed in to change notification settings - Fork 148
Resources and Liberator
Marcin Kulik edited this page Jan 8, 2017
·
4 revisions
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:
- Wanting to add swagger-docs and/or schema-based coercion to your existing resource-based apis, like the Liberator.
- Generate endpoints based on external data, e.g.
- generate REST or CRUD-apis from (database) entity definitions
- generate RPC-apis from function/data definitions
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)}))}}))
(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))))
(resource
{:handler (constantly (ok "hello world"))})
(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"}))})
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)
- https://github.com/metosin/compojure-api/tree/master/examples/resources
- https://github.com/metosin/compojure-api/tree/master/examples/reusable-resources
- set the Liberator resource as top-level handler
- return raw responses to enable response coercion via
:as-response
orring-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.