diff --git a/modules/reitit-malli/src/reitit/coercion/malli.cljc b/modules/reitit-malli/src/reitit/coercion/malli.cljc index 50878ccb..c0f7a56c 100644 --- a/modules/reitit-malli/src/reitit/coercion/malli.cljc +++ b/modules/reitit-malli/src/reitit/coercion/malli.cljc @@ -1,7 +1,9 @@ (ns reitit.coercion.malli (:require [reitit.coercion :as coercion] [malli.transform :as mt] - [malli.core :as m])) + [malli.swagger :as swagger] + [malli.core :as m] + [clojure.set :as set])) (defrecord Coercer [decoder encoder validator explainer]) @@ -17,6 +19,29 @@ (defmulti coerce-response? identity :default ::default) (defmethod coerce-response? ::default [_] true) +(defmulti extract-parameter (fn [in _] in)) + +(defmethod extract-parameter :body [_ schema] + (let [swagger-schema (swagger/transform schema {:in :body, :type :parameter})] + [{:in "body" + :name (:title swagger-schema "") + :description (:description swagger-schema "") + :required (not= :maybe (m/name schema)) + :schema swagger-schema}])) + +(defmethod extract-parameter :default [in schema] + (let [{:keys [properties required]} (swagger/transform schema {:in in, :type :parameter})] + (mapv + (fn [[k {:keys [type] :as schema}]] + (merge + {:in (name in) + :name k + :description (:description schema "") + :type type + :required (contains? (set required) k)} + schema)) + properties))) + (def default-options {:coerce-response? coerce-response? :transformers {:body {:default default-transformer @@ -29,31 +54,30 @@ (reify coercion/Coercion (-get-name [_] :malli) (-get-options [_] opts) - (-get-apidocs [this specification {:keys [parameters responses]}] - ;; TODO: this looks identical to spec, refactor when schema is done. - #_(case specification - :swagger (swagger/swagger-spec - (merge - (if parameters - {:swagger/parameters - (into - (empty parameters) - (for [[k v] parameters] - [k (coercion/-compile-model this v nil)]))}) - (if responses - {:swagger/responses - (into - (empty responses) - (for [[k response] responses] - [k (as-> response $ - (set/rename-keys $ {:body :schema}) - (if (:schema $) - (update $ :schema #(coercion/-compile-model this % nil)) - $))]))}))) - (throw - (ex-info - (str "Can't produce Schema apidocs for " specification) - {:type specification, :coercion :schema})))) + (-get-apidocs [_ specification {:keys [parameters responses]}] + (case specification + :swagger (merge + (if parameters + {:parameters + (->> (for [[in schema] parameters + parameter (extract-parameter in schema)] + parameter) + (into []))}) + (if responses + {:responses + (into + (empty responses) + (for [[status response] responses] + [status (as-> response $ + (set/rename-keys $ {:body :schema}) + (update $ :description (fnil identity "")) + (if (:schema $) + (update $ :schema swagger/transform {:type :schema}) + $))]))})) + (throw + (ex-info + (str "Can't produce Schema apidocs for " specification) + {:type specification, :coercion :schema})))) (-compile-model [_ model _] (m/schema model)) (-open-model [_ schema] schema) (-encode-error [_ error] error) diff --git a/test/cljc/reitit/swagger_test.clj b/test/cljc/reitit/swagger_test.clj index 75305021..6214cf74 100644 --- a/test/cljc/reitit/swagger_test.clj +++ b/test/cljc/reitit/swagger_test.clj @@ -5,9 +5,11 @@ [reitit.swagger-ui :as swagger-ui] [reitit.ring.coercion :as rrc] [reitit.coercion.spec :as spec] + [reitit.coercion.malli :as malli] [reitit.coercion.schema :as schema] [schema.core :refer [Int]] - [muuntaja.core :as m])) + [muuntaja.core :as m] + [spec-tools.data-spec :as ds])) (def app (ring/ring-handler @@ -33,7 +35,7 @@ {:keys [z]} :path} :parameters}] {:status 200, :body {:total (+ x y z)}})} :post {:summary "plus with body" - :parameters {:body [int?] + :parameters {:body (ds/maybe [int?]) :path {:z int?}} :swagger {:responses {400 {:schema {:type "string"} :description "kosh"}}} @@ -43,6 +45,29 @@ xs :body} :parameters}] {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] + ["/malli" {:coercion malli/coercion} + ["/plus/*z" + {:get {:summary "plus" + :parameters {:query [:map [:x int?] [:y int?]] + :path [:map [:z int?]]} + :swagger {:responses {400 {:schema {:type "string"} + :description "kosh"}}} + :responses {200 {:body [:map [:total int?]]} + 500 {:description "fail"}} + :handler (fn [{{{:keys [x y]} :query + {:keys [z]} :path} :parameters}] + {:status 200, :body {:total (+ x y z)}})} + :post {:summary "plus with body" + :parameters {:body [:maybe [:vector int?]] + :path [:map [:z int?]]} + :swagger {:responses {400 {:schema {:type "string"} + :description "kosh"}}} + :responses {200 {:body [:map [:total int?]]} + 500 {:description "fail"}} + :handler (fn [{{{:keys [z]} :path + xs :body} :parameters}] + {:status 200, :body {:total (+ (reduce + xs) z)}})}}]] + ["/schema" {:coercion schema/coercion} ["/plus/*z" {:get {:summary "plus" @@ -115,6 +140,56 @@ :description "kosh"} 500 {:description "fail"}} :summary "plus"}} + "/api/malli/plus/{z}" {:get {:parameters [{:description "" + :format "int64" + :in "query" + :name :x + :required true + :type "integer"} + {:description "" + :format "int64" + :in "query" + :name :y + :required true + :type "integer"} + {:in "path" + :name :z + :description "" + :type "integer" + :required true + :format "int64"}] + :responses {200 {:description "" + :schema {:properties {:total {:format "int64" + :type "integer"}} + :required [:total] + :type "object"}} + 400 {:schema {:type "string"} + :description "kosh"} + 500 {:description "fail"}} + :summary "plus"} + :post {:parameters [{:in "body", + :name "", + :description "", + :required false, + :schema {:type "array", + :items {:type "integer", + :format "int64"} + :x-nullable true}} + {:in "path" + :name :z + :description "" + :type "integer" + :required true + :format "int64"}] + :responses {200 {:description "" + :schema {:properties {:total {:format "int64" + :type "integer"}} + :required [:total] + :type "object"}} + 400 {:schema {:type "string"} + :description "kosh"} + 500 {:description "fail"}} + :summary "plus with body"}} "/api/spec/plus/{z}" {:get {:parameters [{:description "" :format "int64" :in "query" @@ -145,10 +220,11 @@ :post {:parameters [{:in "body", :name "", :description "", - :required true, + :required false, :schema {:type "array", :items {:type "integer", - :format "int64"}}} + :format "int64"} + :x-nullable true}} {:in "path" :name "z" :description ""