This commit is contained in:
+23
-1
@@ -5,7 +5,12 @@
|
|||||||
[reitit.ring :as ring]
|
[reitit.ring :as ring]
|
||||||
[reitit.swagger :as swagger]
|
[reitit.swagger :as swagger]
|
||||||
[reitit.swagger-ui :as swagger-ui]
|
[reitit.swagger-ui :as swagger-ui]
|
||||||
|
[reitit.coercion]
|
||||||
|
[reitit.coercion.malli :as malli]
|
||||||
|
[reitit.ring.coercion :as coercion]
|
||||||
|
[reitit.ring.middleware.exception :as exception]
|
||||||
[reitit.ring.middleware.muuntaja :as muuntaja-middleware]
|
[reitit.ring.middleware.muuntaja :as muuntaja-middleware]
|
||||||
|
[reitit.ring.middleware.parameters :as parameters]
|
||||||
[muuntaja.core :as m]
|
[muuntaja.core :as m]
|
||||||
[tbd.health.routes :as health]
|
[tbd.health.routes :as health]
|
||||||
[tbd.math.routes :as math]))
|
[tbd.math.routes :as math]))
|
||||||
@@ -21,6 +26,19 @@
|
|||||||
(and (map? (:body resp)) (nil? (:definitions (:body resp))))
|
(and (map? (:body resp)) (nil? (:definitions (:body resp))))
|
||||||
(update :body dissoc :definitions))))))
|
(update :body dissoc :definitions))))))
|
||||||
|
|
||||||
|
(defn- coercion-error-handler
|
||||||
|
"Invalid request params -> 400 in the same EDN shape the routes use."
|
||||||
|
[e _req]
|
||||||
|
{:status 400
|
||||||
|
:headers {"Content-Type" "application/edn; charset=utf-8"}
|
||||||
|
:body (pr-str {:error "invalid request parameters"
|
||||||
|
:details (:humanized (reitit.coercion/encode-error (ex-data e)))})})
|
||||||
|
|
||||||
|
(def ^:private exception-middleware
|
||||||
|
(exception/create-exception-middleware
|
||||||
|
(assoc exception/default-handlers
|
||||||
|
:reitit.coercion/request-coercion coercion-error-handler)))
|
||||||
|
|
||||||
;; Each logical group (health, users, orders, ...) owns a `tbd.<group>.routes`
|
;; Each logical group (health, users, orders, ...) owns a `tbd.<group>.routes`
|
||||||
;; namespace that returns its own route subtree; they're concatenated here.
|
;; namespace that returns its own route subtree; they're concatenated here.
|
||||||
(def router
|
(def router
|
||||||
@@ -37,7 +55,11 @@
|
|||||||
(health/routes)
|
(health/routes)
|
||||||
(math/routes)]
|
(math/routes)]
|
||||||
{:data {:muuntaja m/instance
|
{:data {:muuntaja m/instance
|
||||||
:middleware [muuntaja-middleware/format-middleware]}}))
|
:coercion malli/coercion
|
||||||
|
:middleware [parameters/parameters-middleware
|
||||||
|
muuntaja-middleware/format-middleware
|
||||||
|
exception-middleware
|
||||||
|
coercion/coerce-request-middleware]}}))
|
||||||
|
|
||||||
(def app
|
(def app
|
||||||
(-> (ring/ring-handler
|
(-> (ring/ring-handler
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
(ns tbd.math.routes
|
(ns tbd.math.routes
|
||||||
(:require [tbd.math.core :as math]))
|
(:require [tbd.math.core :as math]))
|
||||||
|
|
||||||
(defn- parse-num [s]
|
;; :int first so "2" stays a long (a bare `number?` or :double would decode it to 2.0).
|
||||||
(when s
|
;; The :swagger/* props make the spec say plain `number` instead of the first :or branch.
|
||||||
(or (parse-long s) (parse-double s))))
|
(def ^:private number-schema
|
||||||
|
[:or {:swagger/type "number" :swagger/format "double"} :int :double])
|
||||||
|
|
||||||
(defn- edn-response [status body]
|
(defn- edn-response [status body]
|
||||||
{:status status
|
{:status status
|
||||||
:headers {"Content-Type" "application/edn; charset=utf-8"}
|
:headers {"Content-Type" "application/edn; charset=utf-8"}
|
||||||
:body (pr-str body)})
|
:body (pr-str body)})
|
||||||
|
|
||||||
(defn- add-handler [{:keys [query-params]}]
|
(defn- add-handler [{{{:keys [x y]} :query} :parameters}]
|
||||||
(let [x (parse-num (get query-params "x"))
|
(edn-response 200 {:result (math/add x y)}))
|
||||||
y (parse-num (get query-params "y"))]
|
|
||||||
(if (and x y)
|
|
||||||
(edn-response 200 {:result (math/add x y)})
|
|
||||||
(edn-response 400 {:error "x and y query params are required and must be numbers"}))))
|
|
||||||
|
|
||||||
(defn routes []
|
(defn routes []
|
||||||
["/api/add"
|
["/api/add"
|
||||||
{:get {:tags ["math"]
|
{:get {:tags ["math"]
|
||||||
:summary "Add two numbers (?x=2&y=3 => {:result 5})"
|
:summary "Add two numbers (?x=2&y=3 => {:result 5})"
|
||||||
|
:parameters {:query [:map
|
||||||
|
[:x {:description "First addend"} number-schema]
|
||||||
|
[:y {:description "Second addend"} number-schema]]}
|
||||||
:handler add-handler}}])
|
:handler add-handler}}])
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
(ns tbd.math.routes-test
|
(ns tbd.math.routes-test
|
||||||
(:require [clojure.edn :as edn]
|
(:require [clojure.edn :as edn]
|
||||||
[clojure.test :refer [deftest is testing]]
|
[clojure.test :refer [deftest is testing]]
|
||||||
[reitit.ring :as ring]
|
[muuntaja.core :as m]
|
||||||
[ring.middleware.params :refer [wrap-params]]
|
[tbd.core :as core]))
|
||||||
[tbd.math.routes :as math]))
|
|
||||||
|
|
||||||
;; wrap-params populates :query-params, as wrap-defaults does in tbd.core.
|
|
||||||
(def handler
|
|
||||||
(wrap-params (ring/ring-handler (ring/router (math/routes)))))
|
|
||||||
|
|
||||||
|
;; Coercion/validation is configured on the router in tbd.core, so go through the real app.
|
||||||
(defn add [query]
|
(defn add [query]
|
||||||
(let [resp (handler {:request-method :get :uri "/api/add" :query-string query})]
|
(let [resp (core/app {:request-method :get :uri "/api/add" :query-string query})]
|
||||||
(-> resp
|
(-> resp
|
||||||
(update :body edn/read-string))))
|
(update :body edn/read-string))))
|
||||||
|
|
||||||
@@ -27,4 +23,19 @@
|
|||||||
(doseq [query [nil "x=2" "y=3" "x=abc&y=3" "x=2&y=abc"]]
|
(doseq [query [nil "x=2" "y=3" "x=abc&y=3" "x=2&y=abc"]]
|
||||||
(let [resp (add query)]
|
(let [resp (add query)]
|
||||||
(is (= 400 (:status resp)) (str "query: " query))
|
(is (= 400 (:status resp)) (str "query: " query))
|
||||||
|
(is (= "application/edn; charset=utf-8" (get-in resp [:headers "Content-Type"])))
|
||||||
(is (contains? (:body resp) :error))))))
|
(is (contains? (:body resp) :error))))))
|
||||||
|
|
||||||
|
(deftest add-route-swagger-test
|
||||||
|
(let [spec (-> (core/app {:request-method :get :uri "/swagger.json"
|
||||||
|
:headers {"accept" "application/json"}})
|
||||||
|
:body
|
||||||
|
(->> (m/decode "application/json")))
|
||||||
|
params (->> (:paths spec)
|
||||||
|
(some (fn [[path v]] (when (= "/api/add" (str (namespace path) "/" (name path))) v)))
|
||||||
|
:get
|
||||||
|
:parameters)]
|
||||||
|
(testing "x and y are documented as required numeric query params"
|
||||||
|
(is (= [{:name "x" :in "query" :type "number" :required true}
|
||||||
|
{:name "y" :in "query" :type "number" :required true}]
|
||||||
|
(map #(select-keys % [:name :in :type :required]) params))))))
|
||||||
|
|||||||
Reference in New Issue
Block a user