-
Notifications
You must be signed in to change notification settings - Fork 148
Routing
Compojure-api uses Compojure for routing.
The big difference is, that all compojure-api route functions & macros return compojure.api.routes/Route
-records which can both act as normal ring handlers (e.g. can be called with request to produce an response) and they satisfy the compojure.api.routes/Routing
protocol for collecting the route information.
(def inc-route
(GET "/inc" []
:query-params [x :- s/Int]
:return {:result s/Int}
(ok {:result (inc x)})))
inc-route
; #Route{:path "/inc"
; :method :get
; :info {:parameters {:query {Keyword Any, :x Int}}
; :responses {200 {:schema {:result Int}, :description ""}}}}
(inc-route {:request-method :get, :uri "/inc" :query-params {}})
; CompilerException clojure.lang.ExceptionInfo: Request validation failed: {:x missing-required-key}
(inc-route {:request-method :get, :uri "/inc" :query-params {:x 1}})
; {:status 200, :headers {}, :body {:result 2}, :compojure.api.meta/serializable? true}
At api creation time, the route-tree is walked and the reverse-route tree is generated - to be used both for swagger-docs & for bi-directional routing. To resolve the route-tree manually, you can call get-routes
.
(compojure.api.routes/get-routes
(context "/api" []
inc-route
(POST "/mortem" []
:summary "oh, noes"
(ok {:rest "in piece"}))))
; [["/api/inc" :get {:parameters {:query {Keyword Any, :x Int}}, :responses {200 {:schema {:result Int}, :description ""}}}]
; ["/api/mortem" :post {:summary "oh, well"}]]
Routes can have a (preferably qualified) keyword :name
. When an api
is created, a reverse route tree is created from it's subroutes and the routing table is injected into the request. One can get a full string path to a route by caling compojure.api.routes/path-for*
with the request, route name and optionally path-parameters as a map. There is also a helper macro compojure.api.routes/path-for
which reads the request from lexical scope bindings. It is only available in the http endpoint macros (GET
, POST
etc.). Path parameters are written into Strings using standard JSON decoding.
(require '[compojure.api.routes :as routes])
(def app
(api
;; a named route
(GET "/pong/:id" []
:path-params [id :- s/Int]
:name ::pong
(ok {:id id}))
;; reverse routing via a macro
(GET "/ping1" []
(temporary-redirect (routes/path-for ::pong {:id 1})))
;; reverse routing via function
(GET "/ping2" request
(temporary-redirect (routes/path-for* ::pong request {:id 2})))))
(app {:request-method :get, :uri "/ping1"})
; {:status 307, :headers {"Location" "/pong/1"}, :body ""}
(app {:request-method :get, :uri "/ping2"})
; {:status 307, :headers {"Location" "/pong/2"}, :body ""}
If the api routes contain routes, which do not satisfy the Routing
protocol (e.g. normal ring/compojure functions), an callback-function [:api :invalid-routes-fn]
is called. By default, an warning is logged. At runtime, everything works as expected.
By default, a WARN is logged.
(api
(GET "/ping" []
(ok {:message "I satisfy the Routing protocol!"}))
(compojure.core/GET "/pong" []
(ok {:message "I dont."})))
; WARN Not all child routes satisfy compojure.api.routing/Routing. {:path nil, :method nil}, invalid child routes: [#function[compojure.core/if-method/fn--19598]]
Marking routes undocumented
will stop the route collector to entering those routes (still works at runtime thou).
(api
(GET "/ping" []
(ok {:message "I satisfy the Routing protocol!"}))
(undocumented
(compojure.core/GET "/pong" []
(ok {:message "I dont."}))))
You can also mark compojure-api routes as undocumented
- here, the whole api is undocumented
(api
(undocumented
(GET "/ping" []
(ok {:message "I satisfy the Routing protocol!"}))
(compojure.core/GET "/pong" []
(ok {:message "I dont."}))))
One can also change how the api handles non-compojure-api routes. Here, we break at compile-time:
(api
{:api {:invalid-routes-fn compojure.api.routes/fail-on-invalid-child-routes}}
(GET "/ping" []
(ok {:message "I satisfy the Routing protocol!"}))
(compojure.core/GET "/pong" []
(ok {:message "I dont."})))
; CompilerException clojure.lang.ExceptionInfo: Not all child routes satisfy compojure.api.routing/Routing. {:path nil, :method nil, :invalid [#function[compojure.core/if-method/fn--19598]]}
... or just ignore the bad routes
(api
{:api {:invalid-routes-fn nil}}
(GET "/ping" []
(ok {:message "I satisfy the Routing protocol!"}))
(compojure.core/GET "/pong" []
(ok {:message "I dont."})))
To set up a "didn't match anything" handler within an api
, just do like you would do with ring/compojure.
(api
inc-route
(undocumented
(compojure.route/not-found (ok {:not "found"}))))