-
Notifications
You must be signed in to change notification settings - Fork 147
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"}))))