diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd565b5e..3bdf6289 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Run tests on: push: - branches: [master, "1.1.x"] + branches: [master, "1.1.x", "rmf-adapter"] pull_request: branches: [master, "1.1.x"] diff --git a/examples/thingie/src/examples/thingie.clj b/examples/thingie/src/examples/thingie.clj index 70a3eb74..7b089268 100644 --- a/examples/thingie/src/examples/thingie.clj +++ b/examples/thingie/src/examples/thingie.clj @@ -2,6 +2,7 @@ (:require [ring.util.http-response :refer :all] [compojure.api.sweet :refer :all] [compojure.api.upload :refer :all] + compojure.api.middleware.rmf-muuntaja-adapter [schema.core :as s] ring.swagger.json-schema-dirty ring.middleware.multipart-params.byte-array diff --git a/project.clj b/project.clj index ac12927b..da640106 100644 --- a/project.clj +++ b/project.clj @@ -5,18 +5,23 @@ :url "http://www.eclipse.org/legal/epl-v10.html" :distribution :repo :comments "same as Clojure"} - :scm {:name "git" - :url "https://github.com/metosin/compojure-api"} - :dependencies [[prismatic/plumbing "0.6.0"] - [cheshire "5.13.0"] - [compojure "1.6.1"] - [prismatic/schema "1.1.12"] - [org.tobereplaced/lettercase "1.0.0"] - [frankiesardo/linked "1.3.0"] - [ring-middleware-format "0.7.4"] + :dependencies [[prismatic/schema "1.1.12"] + [prismatic/plumbing "0.5.5"] + [ikitommi/linked "1.3.1-alpha1"] ;; waiting for the original + [metosin/muuntaja "0.6.6"] + [com.fasterxml.jackson.datatype/jackson-datatype-joda "2.10.1"] + [ring/ring-core "1.8.0"] + [compojure "1.6.1" ] + [metosin/spec-tools "0.10.6"] [metosin/ring-http-response "0.9.1"] + [metosin/ring-swagger-ui "3.24.3"] [metosin/ring-swagger "1.0.0"] - [metosin/ring-swagger-ui "2.2.10"]] + + ;; Fix dependency conflicts + [clj-time "0.15.2"] + [joda-time "2.10.5"] + [riddley "0.2.0"]] + :pedantic? :abort :profiles {:uberjar {:aot :all :ring {:handler examples.thingie/app} :source-paths ["examples/thingie/src"] @@ -24,27 +29,27 @@ [http-kit "2.3.0"] [reloaded.repl "0.2.4"] [com.stuartsierra/component "0.4.0"]]} - :dev {:jvm-opts ["-Dcompojure.api.core.allow-dangerous-middleware=true"] - :repl-options {:init-ns user} - :plugins [[lein-clojars "0.9.1"] + :dev {:plugins [[lein-clojars "0.9.1"] [lein-midje "3.2.1"] - [lein-ring "0.12.0"] + [lein-ring "0.12.5"] [funcool/codeina "0.5.0"]] :dependencies [[org.clojure/clojure "1.9.0"] - ;; bump - [fipp "0.6.26"] - [metosin/spec-tools "0.10.6"] - [metosin/muuntaja "0.6.6"] - [metosin/jsonista "0.2.5"] - [com.fasterxml.jackson.datatype/jackson-datatype-joda "2.10.1"] - [slingshot "0.12.2"] - [peridot "0.5.1"] - [javax.servlet/servlet-api "2.5"] - [midje "1.9.9"] + [org.clojure/core.unify "0.6.0"] + [org.clojure/core.async "0.6.532"] + [javax.servlet/javax.servlet-api "4.0.1"] + [peridot "0.5.2"] [com.stuartsierra/component "0.4.0"] + [expound "0.8.2"] + [metosin/jsonista "0.2.5"] [reloaded.repl "0.2.4"] + [midje "1.9.9" :exclusions [commons-codec org.clojure/tools.namespace]] + [metosin/muuntaja-msgpack "0.6.6"] + [metosin/muuntaja-yaml "0.6.6"] + [org.immutant/immutant "2.1.10"] [http-kit "2.3.0"] - [criterium "0.4.5"]] + [criterium "0.4.5"] + ;; compojure.api.integration-test + [ring-middleware-format "0.7.4" :exclusions [org.clojure/core.memoize]]] :ring {:handler examples.thingie/app :reload-paths ["src" "examples/thingie/src"]} :source-paths ["examples/thingie/src" "examples/thingie/dev-src"] @@ -52,10 +57,16 @@ :perf {:jvm-opts ^:replace ["-server" "-Xmx4096m" "-Dclojure.compiler.direct-linking=true"]} - :logging {:dependencies [[org.clojure/tools.logging "0.5.0"]]} + :logging {:dependencies [[org.clojure/tools.logging "0.5.0"] + [org.slf4j/jcl-over-slf4j "1.7.30"] + [org.slf4j/jul-to-slf4j "1.7.30"] + [org.slf4j/log4j-over-slf4j "1.7.30"] + [ch.qos.logback/logback-classic "1.2.3" ]]} :1.10 {:dependencies [[org.clojure/clojure "1.10.1"]]} :1.11 {:dependencies [[org.clojure/clojure "1.11.3"]]} - :1.12 {:dependencies [[org.clojure/clojure "1.12.0-alpha11"]]}} + :1.12 {:dependencies [[org.clojure/clojure "1.12.0-alpha11"]]} + :async {:jvm-opts ["-Dcompojure-api.test.async=true"] + :dependencies [[manifold "0.1.8" :exclusions [org.clojure/tools.logging]]]}} :eastwood {:namespaces [:source-paths] :add-linters [:unused-namespaces]} :codeina {:sources ["src"] diff --git a/src/compojure/api/coercion.clj b/src/compojure/api/coercion.clj index c891383d..50ad74b1 100644 --- a/src/compojure/api/coercion.clj +++ b/src/compojure/api/coercion.clj @@ -4,7 +4,7 @@ [compojure.api.request :as request] [compojure.api.coercion.core :as cc] ;; side effects - compojure.api.coercion.schema + [compojure.api.coercion.schema :as cschema] compojure.api.coercion.spec) (:import (compojure.api.coercion.core CoercionError))) @@ -23,6 +23,7 @@ (nil? coercion) nil (keyword? coercion) (cc/named-coercion coercion) (satisfies? cc/Coercion coercion) coercion + (fn? coercion) (cschema/create-coercion coercion) :else (throw (ex-info (str "invalid coercion " coercion) {:coercion coercion})))) (defn get-apidocs [maybe-coercion spec info] diff --git a/src/compojure/api/exception.clj b/src/compojure/api/exception.clj index 0844bc5d..d4ed4b17 100644 --- a/src/compojure/api/exception.clj +++ b/src/compojure/api/exception.clj @@ -42,6 +42,11 @@ [e data req] (response/bad-request {:errors (stringify-error (su/error-val data))})) +(defn http-response-handler + "reads response from ex-data :response" + [_ {:keys [response]} _] + response) + (defn schema-error-handler "Creates error response based on Schema error." [e data req] diff --git a/src/compojure/api/meta.clj b/src/compojure/api/meta.clj index 1b093871..d6a399f3 100644 --- a/src/compojure/api/meta.clj +++ b/src/compojure/api/meta.clj @@ -8,8 +8,11 @@ [ring.swagger.json-schema :as js] [schema.core :as s] [schema-tools.core :as st] - [compojure.api.coerce :as coerce] - compojure.core)) + [compojure.api.coercion :as coercion] + [compojure.api.help :as help] + compojure.core + compojure.api.compojure-compat + [compojure.api.common :as common])) (def +compojure-api-request+ "lexically bound ring-request for handlers." @@ -33,9 +36,10 @@ (s/defn src-coerce! "Return source code for coerce! for a schema with coercion type, extracted from a key in a ring request." - [schema, key, type :- mw/CoercionType] - (assert (not (#{:query :json} type)) (str type " is DEPRECATED since 0.22.0. Use :body or :string instead.")) - `(coerce/coerce! ~schema ~key ~type ~+compojure-api-request+)) + ([schema, key, type] + (src-coerce! schema, key, type, true)) + ([schema, key, type, keywordize?] + `(coercion/coerce-request! ~schema ~key ~type ~keywordize? false ~+compojure-api-request+))) (defn- convert-return [schema] {200 {:schema schema @@ -303,7 +307,7 @@ _ (assert (not parameters) ":parameters is deprecated with 1.0.0, use :swagger instead.") ;; response coercion middleware, why not just code? - middleware (if (seq responses) (conj middleware `[coerce/body-coercer-middleware (common/merge-vector ~responses)]) middleware)] + middleware (if (seq responses) (conj middleware `[coercion/wrap-coerce-response (common/merge-vector ~responses)]) middleware)] (if context? diff --git a/src/compojure/api/middleware.clj b/src/compojure/api/middleware.clj index 545209a4..a4e28de9 100644 --- a/src/compojure/api/middleware.clj +++ b/src/compojure/api/middleware.clj @@ -1,21 +1,25 @@ (ns compojure.api.middleware (:require [compojure.core :refer :all] [compojure.api.exception :as ex] + [compojure.api.common :as common] + [compojure.api.coercion :as coercion] + [compojure.api.request :as request] [compojure.api.impl.logging :as logging] - [ring.middleware.format-params :refer [wrap-restful-params]] - [ring.middleware.format-response :refer [wrap-restful-response]] - ring.middleware.http-response [ring.middleware.keyword-params :refer [wrap-keyword-params]] [ring.middleware.nested-params :refer [wrap-nested-params]] [ring.middleware.params :refer [wrap-params]] - [ring.swagger.common :as rsc] - [ring.swagger.middleware :as rsm] [ring.swagger.coerce :as coerce] + + [muuntaja.middleware] + [muuntaja.core :as m] + + [ring.swagger.common :as rsc] [ring.util.http-response :refer :all] [schema.core :as s]) - (:import [com.fasterxml.jackson.core JsonParseException] + (:import [clojure.lang ArityException] + [com.fasterxml.jackson.core JsonParseException] [org.yaml.snakeyaml.parser ParserException] - [clojure.lang ArityException])) + [com.fasterxml.jackson.datatype.joda JodaModule])) ;; ;; Catch exceptions @@ -78,12 +82,27 @@ [request] (::options request)) +(defn wrap-inject-data + "Injects data into the request." + [handler data] + (fn + ([request] + (handler (common/fast-map-merge request data))) + ([request respond raise] + (handler (common/fast-map-merge request data) respond raise)))) + ;; ;; coercion ;; -(s/defschema CoercionType (s/enum :body :string :response)) +(defn wrap-coercion [handler coercion] + (fn + ([request] + (handler (coercion/set-request-coercion request coercion))) + ([request respond raise] + (handler (coercion/set-request-coercion request coercion) respond raise)))) +;; 1.1.x (def default-coercion-matchers {:body coerce/json-schema-coercion-matcher :string coerce/query-schema-coercion-matcher @@ -99,11 +118,63 @@ (provider request)) default-coercion-matchers))) -(def coercion-request-ks [::options :coercion]) +;; +;; Muuntaja +;; -(defn wrap-coercion [handler coercion] - (fn [request] - (handler (assoc-in request coercion-request-ks coercion)))) +(defn encode? + "Returns true if the response body is serializable: body is a + collection or response has key :compojure.api.meta/serializable?" + [_ response] + (or (:compojure.api.meta/serializable? response) + (coll? (:body response)))) + +(def default-muuntaja-options + (assoc-in + m/default-options + [:formats "application/json" :opts :modules] + [(JodaModule.)])) + +(defn create-muuntaja + ([] + (create-muuntaja default-muuntaja-options)) + ([muuntaja-or-options] + (let [opts #(assoc-in % [:http :encode-response-body?] encode?)] + (cond + + (nil? muuntaja-or-options) + nil + + (= ::default muuntaja-or-options) + (m/create (opts default-muuntaja-options)) + + (m/muuntaja? muuntaja-or-options) + (-> muuntaja-or-options (m/options) (opts) (m/create)) + + (map? muuntaja-or-options) + (m/create (opts muuntaja-or-options)) + + :else + (throw + (ex-info + (str "Invalid :formats - " muuntaja-or-options) + {:options muuntaja-or-options})))))) + +;; +;; middleware +;; + +(defn middleware-fn [middleware] + (if (vector? middleware) + (let [[f & arguments] middleware] + #(apply f % arguments)) + middleware)) + +(defn compose-middleware [middleware] + (->> middleware + (keep identity) + (map middleware-fn) + (apply comp identity))) ;; ;; ring-middleware-format stuff @@ -146,6 +217,31 @@ (or (:compojure.api.meta/serializable? response) (coll? body)))) +;; +;; swagger-data +;; + +(defn set-swagger-data + "Add extra top-level swagger-data into a request. + Data can be read with get-swagger-data." + ([request data] + (update request ::request/swagger (fnil conj []) data))) + +(defn get-swagger-data + "Reads and deep-merges top-level swagger-data from request, + pushed in by set-swagger-data." + [request] + (apply rsc/deep-merge (::request/swagger request))) + +(defn wrap-swagger-data + "Middleware that adds top level swagger-data into request." + [handler data] + (fn + ([request] + (handler (set-swagger-data request data))) + ([request respond raise] + (handler (set-swagger-data request data) respond raise)))) + ;; ;; Api Middleware ;; @@ -161,6 +257,49 @@ :coercion (constantly default-coercion-matchers) :ring-swagger nil}) +(def api-middleware-defaults-v2 + {:formats ::default + :exceptions {:handlers {:ring.util.http-response/response ex/http-response-handler + ::ex/request-validation ex/request-validation-handler + ::ex/request-parsing ex/request-parsing-handler + ::ex/response-validation ex/response-validation-handler + ::ex/default ex/safe-handler}} + :middleware nil + :coercion coercion/default-coercion + :ring-swagger nil}) + +(defn api-middleware-options-v1 [options] + (rsc/deep-merge api-middleware-defaults-v1 options)) + +(defn api-middleware-options-v2 [options] + (rsc/deep-merge api-middleware-defaults-v2 options)) + +(defonce rmf-format->muuntaja-formats (atom {})) + +(comment + (do @rmf-format->muuntaja-formats + )) + +(defn- format->formats [format] + (when-some [{:keys [formats params-opts response-opts]} format] + (assert (empty? params-opts) (pr-str params-opts)) + (assert (empty? response-opts) (pr-str params-opts)) + (reduce (fn [m k] + (case k + (let [formats (get @rmf-format->muuntaja-formats k)] + (assert formats (str "Unknown translation for :formats, please ensure compojure.api.middleware.rmf-muuntaja-adapter is loaded: " + (pr-str k) + "\n" (vec (keys @rmf-format->muuntaja-formats)))) + formats))) + {} formats))) + +(defn- v1->v2 [options] + (let [{:keys [format] :as options} (api-middleware-options-v1 options)] + (-> options + (assoc :formats (when (some? format) + (update m/default-options :formats into (format->formats format)))) + (dissoc :format)))) + ;; TODO: test all options! (https://github.com/metosin/compojure-api/issues/137) (defn api-middleware "Opinionated chain of middlewares for web apis. Takes optional options-map. @@ -189,13 +328,20 @@ - **:exceptions** for *compojure.api.middleware/wrap-exceptions* (nil to unmount it) - **:handlers** Map of error handlers for different exception types, type refers to `:type` key in ExceptionInfo data. - - **:format** for ring-middleware-format middlewares (nil to unmount it) + - **:formats** for Muuntaja middleware. Value can be a valid muuntaja options-map, + a Muuntaja instance or nil (to unmount it). See + https://github.com/metosin/muuntaja/blob/master/doc/Configuration.md for details. + Cannot be combined with :format. + + - **:format** for ring-middleware-format middlewares (nil to unmount it). Cannot be combined with :formats. - **:formats** sequence of supported formats, e.g. `[:json-kw :edn]` - **:params-opts** for *ring.middleware.format-params/wrap-restful-params*, e.g. `{:transit-json {:handlers readers}}` - **:response-opts** for *ring.middleware.format-params/wrap-restful-response*, e.g. `{:transit-json {:handlers writers}}` + - **:middleware** vector of extra middleware to be applied last (just before the handler). + - **:ring-swagger** options for ring-swagger's swagger-json method. e.g. `{:ignore-missing-mappings? true}` @@ -210,43 +356,53 @@ middleware manually.). Defaults to nil (middleware not mounted)." ([handler] (api-middleware handler nil)) ([handler options] - (let [options (rsc/deep-merge api-middleware-defaults-v1 options) - {:keys [exceptions format components]} options - {:keys [formats params-opts response-opts]} format] - ; Break at compile time if there are deprecated options - ; These three have been deprecated with 0.23 - (assert (not (:error-handler (:validation-errors options))) - (str "ERROR: Option: [:validation-errors :error-handler] is no longer supported, " - "use {:exceptions {:handlers {:compojure.api.middleware/request-validation your-handler}}} instead." - "Also note that exception-handler arity has been changed.")) - (assert (not (:catch-core-errors? (:validation-errors options))) - (str "ERROR: Option [:validation-errors :catch-core-errors?] is no longer supported, " - "use {:exceptions {:handlers {:schema.core/error compojure.api.exception/schema-error-handler}}} instead." - "Also note that exception-handler arity has been changed.")) - (assert (not (:exception-handler (:exceptions options))) - (str "ERROR: Option [:exceptions :exception-handler] is no longer supported, " - "use {:exceptions {:handlers {:compojure.api.exception/default your-handler}}} instead." - "Also note that exception-handler arity has been changed.")) - (assert (not (map? (:coercion options))) - (str "ERROR: Option [:coercion] should be a funtion of request->type->matcher, got a map instead." - "From 1.0.0 onwards, you should wrap your type->matcher map into a request-> function. If you " - "want to apply the matchers for all request types, wrap your option with 'constantly'")) - (cond-> handler - components (wrap-components components) - true ring.middleware.http-response/wrap-http-response - (seq formats) (rsm/wrap-swagger-data {:produces (->mime-types (remove response-only-mimes formats)) - :consumes (->mime-types formats)}) - true (wrap-options (select-keys options [:ring-swagger :coercion])) - (seq formats) (wrap-restful-params {:formats (remove response-only-mimes formats) - :handle-error handle-req-error - :format-options params-opts}) - exceptions (wrap-exceptions exceptions) - (seq formats) (wrap-restful-response {:formats formats - :predicate serializable? - :format-options response-opts}) - true wrap-keyword-params - true wrap-nested-params - true wrap-params)))) + (let [v1? (or (contains? options :format) + (seq @rmf-format->muuntaja-formats)) + _ (assert v1? "Please require compojure.api.middleware.rmf-muuntaja-adapter for future compatability") + _ (when v1? + (assert (not (contains? options :formats)) + "Cannot combine :format and :formats")) + options (if v1? + (v1->v2 options) + (api-middleware-options-v2 options)) + {:keys [exceptions components formats middleware ring-swagger coercion]} options + muuntaja (create-muuntaja formats)] + + ;; 1.2.0+ + (assert (not (contains? options :format)) + (str "ERROR: Option [:format] is not used with 2.* version.\n" + "Compojure-api uses now Muuntaja insted of ring-middleware-format,\n" + "the new formatting options for it should be under [:formats]. See\n" + "[[api-middleware]] documentation for more details.\n")) + + (-> handler + (cond-> middleware ((compose-middleware middleware))) + (cond-> components (wrap-components components)) + (cond-> muuntaja (wrap-swagger-data {:consumes (m/decodes muuntaja) + :produces (m/encodes muuntaja)})) + (wrap-inject-data + (cond-> {::request/coercion coercion} + muuntaja (assoc ::request/muuntaja muuntaja) + ring-swagger (assoc ::request/ring-swagger ring-swagger))) + (cond-> muuntaja (muuntaja.middleware/wrap-params)) + ;; all but request-parsing exceptions (to make :body-params visible) + (cond-> exceptions (wrap-exceptions + (update exceptions :handlers dissoc ::ex/request-parsing))) + (cond-> muuntaja (muuntaja.middleware/wrap-format-request muuntaja)) + ;; just request-parsing exceptions + (cond-> exceptions (wrap-exceptions + (update exceptions :handlers select-keys [::ex/request-parsing]))) + (cond-> muuntaja (muuntaja.middleware/wrap-format-response muuntaja)) + (cond-> muuntaja (muuntaja.middleware/wrap-format-negotiate muuntaja)) + + ;; these are really slow middleware, 4.5µs => 9.1µs (+100%) + + ;; 7.8µs => 9.1µs (+27%) + wrap-keyword-params + ;; 7.1µs => 7.8µs (+23%) + wrap-nested-params + ;; 4.5µs => 7.1µs (+50%) + wrap-params)))) (defn middleware-fn [middleware] (if (vector? middleware) diff --git a/src/compojure/api/middleware/rmf_muuntaja_adapter.clj b/src/compojure/api/middleware/rmf_muuntaja_adapter.clj new file mode 100644 index 00000000..71d0a98c --- /dev/null +++ b/src/compojure/api/middleware/rmf_muuntaja_adapter.clj @@ -0,0 +1,21 @@ +(ns compojure.api.middleware.rmf-muuntaja-adapter + (:require [compojure.api.middleware :refer [rmf-format->muuntaja-formats]] + [muuntaja.core :as m] + ;[muuntaja.format.msgpack] + [muuntaja.format.yaml])) + +(swap! rmf-format->muuntaja-formats + #(into (let [defaults (:formats m/default-options)] + (reduce-kv (fn [m k s] + (assoc m k {s (get defaults s)})) + {:yaml-kw {"application/x-yaml" muuntaja.format.yaml/format}} + {:json "application/json" + :json-kw "application/json" + :edn "application/edn" + ;:clojure "application/clojure" + ;:yaml "application/x-yaml" + ;:yaml-kw "application/x-yaml" + ;:yaml-in-html "text/html" + :transit-json "application/transit+json" + :transit-msgpack "application/transit+msgpack"})) + %)) diff --git a/src/compojure/api/resource.clj b/src/compojure/api/resource.clj index b3d2a770..fed6d5f6 100644 --- a/src/compojure/api/resource.clj +++ b/src/compojure/api/resource.clj @@ -1,11 +1,13 @@ (ns compojure.api.resource (:require [compojure.api.routes :as routes] - [compojure.api.coerce :as coerce] + [compojure.api.coercion :as coercion] [compojure.api.methods :as methods] [ring.swagger.common :as rsc] [schema.core :as s] [plumbing.core :as p] - [compojure.api.middleware :as mw])) + [compojure.api.async] + [compojure.api.middleware :as mw] + [compojure.api.coercion.core :as cc])) (def ^:private +mappings+ {:methods methods/all-methods @@ -26,51 +28,112 @@ (:parameters +mappings+)) (dissoc info :handler))) +(defn- inject-coercion [request info] + (if (contains? info :coercion) + (coercion/set-request-coercion request (:coercion info)) + request)) + (defn- coerce-request [request info ks] (reduce-kv (fn [request ring-key [compojure-key _ type open?]] - (if-let [schema (get-in info (concat ks [:parameters ring-key]))] - (let [schema (if open? (assoc schema s/Keyword s/Any) schema)] - (update request ring-key merge (coerce/coerce! schema compojure-key type request))) + (if-let [model (get-in info (concat ks [:parameters ring-key]))] + (let [coerced (coercion/coerce-request! + model compojure-key type (not= :body type) open? request)] + (if open? + (update request ring-key merge coerced) + (assoc request ring-key coerced))) request)) - request + (inject-coercion request info) (:parameters +mappings+))) (defn- coerce-response [response info request ks] - (coerce/coerce-response! request response (get-in info (concat ks [:responses])))) - -(defn- resolve-handler [info request-method] - (or - (get-in info [request-method :handler]) - (get-in info [:handler]))) + (coercion/coerce-response! request response (get-in info (concat ks [:responses])))) + +(defn- maybe-async [async? x] + (if (and async? x) [x true])) + +(defn- maybe-sync [x] + (if x [x false])) + +(defn- resolve-handler [info path-info route request-method async?] + (and + (or + ;; directly under a context + (= path-info "/") + ;; under an compojure endpoint + route + ;; vanilla ring + (nil? path-info)) + (let [[handler async] (or + (maybe-async async? (get-in info [request-method :async-handler])) + (maybe-sync (get-in info [request-method :handler])) + (maybe-async async? (get-in info [:async-handler])) + (maybe-sync (get-in info [:handler])))] + (if handler + [handler async])))) + +(defn- middleware-chain [info request-method handler] + (let [direct-mw (:middleware info) + method-mw (:middleware (get info request-method)) + middleware (mw/compose-middleware (concat direct-mw method-mw))] + (middleware handler))) (defn- create-childs [info] (map (fn [[method info]] - (routes/create "/" method (swaggerize info) nil nil)) + (routes/map->Route + {:path "/" + :method method + :info {:public (swaggerize info)}})) (select-keys info (:methods +mappings+)))) -(defn- create-handler [info {:keys [coercion]}] - (fn [{:keys [request-method] :as request}] - (let [request (if coercion (assoc-in request mw/coercion-request-ks coercion) request) - ks (if (contains? info request-method) [request-method] [])] - (if-let [handler (resolve-handler info request-method)] - (-> (coerce-request request info ks) - handler - (coerce-response info request ks)))))) +(defn- handle-sync [info {:keys [request-method path-info :compojure/route] :as request}] + (when-let [[raw-handler] (resolve-handler info path-info route request-method false)] + (let [ks (if (contains? info request-method) [request-method] []) + handler (middleware-chain info request-method raw-handler)] + (-> (coerce-request request info ks) + (handler) + (compojure.response/render request) + (coerce-response info request ks))))) + +(defn- handle-async [info {:keys [request-method path-info :compojure/route] :as request} respond raise] + (if-let [[raw-handler async?] (resolve-handler info path-info route request-method true)] + (let [ks (if (contains? info request-method) [request-method] []) + respond-coerced (fn [response] + (respond + (try (coerce-response response info request ks) + (catch Throwable e (raise e))))) + handler (middleware-chain info request-method raw-handler)] + (try + (as-> (coerce-request request info ks) $ + (if async? + (handler $ #(compojure.response/send % $ respond-coerced raise) raise) + (compojure.response/send (handler $) $ respond-coerced raise))) + (catch Throwable e + (raise e)))) + (respond nil))) + +(defn- create-handler [info] + (fn + ([request] + (handle-sync info request)) + ([request respond raise] + (handle-async info request respond raise)))) (defn- merge-parameters-and-responses [info] (let [methods (select-keys info (:methods +mappings+))] (-> info (merge - (p/for-map [[method method-info] methods] - method (-> method-info - (->> (rsc/deep-merge (select-keys info [:parameters]))) - (update :responses (fn [responses] (merge (:responses info) responses))))))))) - -(defn- root-info [info] + (p/for-map [[method method-info] methods + :let [responses (merge + (:responses info) + (:responses method-info))]] + method (cond-> (->> method-info (rsc/deep-merge (select-keys info [:parameters]))) + (seq responses) (assoc :responses responses))))))) + +(defn- public-root-info [info] (-> (reduce dissoc info (:methods +mappings+)) - (dissoc :parameters :responses))) + (dissoc :parameters :responses :coercion))) ;; ;; Public api @@ -82,17 +145,21 @@ ; TODO: validate input against ring-swagger schema, fail for missing handlers ; TODO: extract parameter schemas from handler fnks? (defn resource - "Creates a nested compojure-api Route from enchanced ring-swagger operations map and options. + "Creates a nested compojure-api Route from enchanced ring-swagger operations map. By default, applies both request- and response-coercion based on those definitions. - Options: + Extra keys: - - **:coercion** A function from request->type->coercion-matcher, used + - **:middleware** Middleware in duct-format either at top-level or under methods. + Top-level mw are applied first if route matches, method-level + mw are applied next if method matches + + - **:coercion** A named coercion or instance of Coercion in resource coercion for :body, :string and :response. - Setting value to `(constantly nil)` disables both request- & + Setting value to `nil` disables both request- & response coercion. See tests and wiki for details. - Enchancements to ring-swagger operations map: + 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 @@ -105,9 +172,14 @@ 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. + 3) special keys `:handler` and/or `:async-handler` either under operations or at top-level. + They should be 1-ary and 3-ary Ring handler functions, respectively, that are responsible + for the actual request processing. Handler lookup order is the following: + + 3.1) If called asynchronously, operations-level :async-handler + 3.2) Operations-level :handler + 3.3) If called asynchronously, top-level :async-handler + 3.4) Top-level :handler 4) request-coercion is applied once, using deep-merged parameters for a given operation or resource-level if only resource-level handler is defined. @@ -131,11 +203,13 @@ :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)))) + [data] + (let [data (merge-parameters-and-responses data) + public-info (swaggerize (public-root-info data)) + info (merge {:public public-info} (select-keys data [:coercion])) + childs (create-childs data) + handler (create-handler data)] + (routes/map->Route + {:info info + :childs childs + :handler handler}))) diff --git a/src/compojure/api/sweet.clj b/src/compojure/api/sweet.clj index 1a84d637..6ef3b197 100644 --- a/src/compojure/api/sweet.clj +++ b/src/compojure/api/sweet.clj @@ -4,7 +4,7 @@ (defmacro defroutes {:doc "Define a Ring handler function from a sequence of routes.\n The name may optionally be followed by a doc-string and metadata map."} [name & routes] (list* (quote compojure.api.core/defroutes) name routes)) (defmacro let-routes {:doc "Takes a vector of bindings and a body of routes.\n\n Equivalent to: `(let [...] (routes ...))`"} [bindings & body] (list* (quote compojure.api.core/let-routes) bindings body)) (def ^{:arglists (quote ([& handlers])), :doc "Routes without route-documentation. Can be used to wrap routes,\n not satisfying compojure.api.routes/Routing -protocol."} undocumented compojure.api.core/undocumented) -(defmacro middleware {:deprecated "1.1.14", :doc "Wraps routes with given middleware using thread-first macro.\n\n Note that middlewares will be executed even if routes in body\n do not match the request uri. Be careful with middleware that\n has side-effects."} [middleware & body] (list* (quote compojure.api.core/middleware) middleware body)) +(defmacro middleware {:deprecated "1.1.14", :doc "Wraps routes with given middlewares using thread-first macro.\n\n Note that middlewares will be executed even if routes in body\n do not match the request uri. Be careful with middleware that\n has side-effects."} [middleware & body] (list* (quote compojure.api.core/middleware) middleware body)) (def ^{:arglists (quote ([middleware & body])), :doc "Wraps routes with given middleware using thread-first macro."} route-middleware compojure.api.core/route-middleware) (defmacro context [& args] (list* (quote compojure.api.core/context) args)) (defmacro GET [& args] (list* (quote compojure.api.core/GET) args)) @@ -15,9 +15,9 @@ (defmacro OPTIONS [& args] (list* (quote compojure.api.core/OPTIONS) args)) (defmacro POST [& args] (list* (quote compojure.api.core/POST) args)) (defmacro PUT [& args] (list* (quote compojure.api.core/PUT) args)) -(def ^{:arglists (quote ([& body])), :doc "Returns a ring handler wrapped in compojure.api.middleware/api-middlware.\n Creates the route-table at api creation time and injects that into the request via\n middlewares. Api and the mounted api-middleware can be configured by optional\n options map as the first parameter:\n\n (api\n {:formats [:json-kw :edn :transit-msgpack :transit-json]\n :exceptions {:handlers {:compojure.api.exception/default my-logging-handler}}\n :api {:invalid-routes-fn (constantly nil)}\n :swagger {:spec \"/swagger.json\"\n :ui \"/api-docs\"\n :data {:info {:version \"1.0.0\"\n :title \"My API\"\n :description \"the description\"}}}}\n (context \"/api\" []\n ...))\n\n ### direct api options:\n\n - **:api** All api options are under `:api`.\n - **:invalid-routes-fn** A 2-arity function taking handler and a sequence of\n invalid routes (not satisfying compojure.api.route.Routing)\n setting value to nil ignores invalid routes completely.\n defaults to `compojure.api.routes/log-invalid-child-routes`\n - **:disable-api-middleware?** boolean to disable the `api-middleware` from api.\n - **:swagger** Options to configure the Swagger-routes. Defaults to nil.\n See `compojure.api.swagger/swagger-routes` for details.\n\n ### api-middleware options\n\n Opinionated chain of middlewares for web apis. Takes optional options-map.\n\n ### Exception handlers\n\n An error handler is a function of exception, ex-data and request to response.\n\n When defining these options, it is suggested to use alias for the exceptions namespace,\n e.g. `[compojure.api.exception :as ex]`.\n\n Default:\n\n {::ex/request-validation ex/request-validation-handler\n ::ex/request-parsing ex/request-parsing-handler\n ::ex/response-validation ex/response-validation-handler\n ::ex/default ex/safe-handler}\n\n Note: Because the handlers are merged into default handlers map, to disable default handler you\n need to provide `nil` value as handler.\n\n Note: To catch Schema errors use `{:schema.core/error ex/schema-error-handler}`.\n\n ### Options\n\n - **:exceptions** for *compojure.api.middleware/wrap-exceptions* (nil to unmount it)\n - **:handlers** Map of error handlers for different exception types, type refers to `:type` key in ExceptionInfo data.\n\n - **:format** for ring-middleware-format middlewares (nil to unmount it)\n - **:formats** sequence of supported formats, e.g. `[:json-kw :edn]`\n - **:params-opts** for *ring.middleware.format-params/wrap-restful-params*,\n e.g. `{:transit-json {:handlers readers}}`\n - **:response-opts** for *ring.middleware.format-params/wrap-restful-response*,\n e.g. `{:transit-json {:handlers writers}}`\n\n - **:ring-swagger** options for ring-swagger's swagger-json method.\n e.g. `{:ignore-missing-mappings? true}`\n\n - **:coercion** A function from request->type->coercion-matcher, used\n in endpoint coercion for :body, :string and :response.\n Defaults to `(constantly compojure.api.middleware/default-coercion-matchers)`\n Setting value to nil disables all coercion\n\n - **:components** Components which should be accessible to handlers using\n :components restructuring. (If you are using api,\n you might want to take look at using wrap-components\n middleware manually.). Defaults to nil (middleware not mounted)."} api compojure.api.api/api) -(defmacro defapi {:doc "Defines an api.\n\n API middleware options:\n\n Opinionated chain of middlewares for web apis. Takes optional options-map.\n\n ### Exception handlers\n\n An error handler is a function of exception, ex-data and request to response.\n\n When defining these options, it is suggested to use alias for the exceptions namespace,\n e.g. `[compojure.api.exception :as ex]`.\n\n Default:\n\n {::ex/request-validation ex/request-validation-handler\n ::ex/request-parsing ex/request-parsing-handler\n ::ex/response-validation ex/response-validation-handler\n ::ex/default ex/safe-handler}\n\n Note: Because the handlers are merged into default handlers map, to disable default handler you\n need to provide `nil` value as handler.\n\n Note: To catch Schema errors use `{:schema.core/error ex/schema-error-handler}`.\n\n ### Options\n\n - **:exceptions** for *compojure.api.middleware/wrap-exceptions* (nil to unmount it)\n - **:handlers** Map of error handlers for different exception types, type refers to `:type` key in ExceptionInfo data.\n\n - **:format** for ring-middleware-format middlewares (nil to unmount it)\n - **:formats** sequence of supported formats, e.g. `[:json-kw :edn]`\n - **:params-opts** for *ring.middleware.format-params/wrap-restful-params*,\n e.g. `{:transit-json {:handlers readers}}`\n - **:response-opts** for *ring.middleware.format-params/wrap-restful-response*,\n e.g. `{:transit-json {:handlers writers}}`\n\n - **:ring-swagger** options for ring-swagger's swagger-json method.\n e.g. `{:ignore-missing-mappings? true}`\n\n - **:coercion** A function from request->type->coercion-matcher, used\n in endpoint coercion for :body, :string and :response.\n Defaults to `(constantly compojure.api.middleware/default-coercion-matchers)`\n Setting value to nil disables all coercion\n\n - **:components** Components which should be accessible to handlers using\n :components restructuring. (If you are using api,\n you might want to take look at using wrap-components\n middleware manually.). Defaults to nil (middleware not mounted)."} [name & body] (list* (quote compojure.api.api/defapi) name body)) -(def ^{:arglists (quote ([info] [info options])), :doc "Creates a nested compojure-api Route from enchanced ring-swagger operations map and options.\n By default, applies both request- and response-coercion based on those definitions.\n\n Options:\n\n - **:coercion** A function from request->type->coercion-matcher, used\n in resource coercion for :body, :string and :response.\n Setting value to `(constantly nil)` disables both request- &\n response coercion. See tests and wiki for details.\n\n Enchancements to ring-swagger operations map:\n\n 1) :parameters use ring request keys (query-params, path-params, ...) instead of\n swagger-params (query, path, ...). This keeps things simple as ring keys are used in\n the handler when destructuring the request.\n\n 2) at resource root, one can add any ring-swagger operation definitions, which will be\n available for all operations, using the following rules:\n\n 2.1) :parameters are deep-merged into operation :parameters\n 2.2) :responses are merged into operation :responses (operation can fully override them)\n 2.3) all others (:produces, :consumes, :summary,...) are deep-merged by compojure-api\n\n 3) special key `:handler` either under operations or at top-level. Value should be a\n ring-handler function, responsible for the actual request processing. Handler lookup\n order is the following: operations-level, top-level.\n\n 4) request-coercion is applied once, using deep-merged parameters for a given\n operation or resource-level if only resource-level handler is defined.\n\n 5) response-coercion is applied once, using merged responses for a given\n operation or resource-level if only resource-level handler is defined.\n\n Note: Swagger operations are generated only from declared operations (:get, :post, ..),\n despite the top-level handler could process more operations.\n\n Example:\n\n (resource\n {:parameters {:query-params {:x Long}}\n :responses {500 {:schema {:reason s/Str}}}\n :get {:parameters {:query-params {:y Long}}\n :responses {200 {:schema {:total Long}}}\n :handler (fn [request]\n (ok {:total (+ (-> request :query-params :x)\n (-> request :query-params :y))}))}\n :post {}\n :handler (constantly\n (internal-server-error {:reason \"not implemented\"}))})"} resource compojure.api.resource/resource) +(def ^{:arglists (quote ([& body])), :doc "Returns a ring handler wrapped in compojure.api.middleware/api-middlware.\n Creates the route-table at api creation time and injects that into the request via\n middlewares. Api and the mounted api-middleware can be configured by optional\n options map as the first parameter:\n\n (api\n {:formats [:json-kw :edn :transit-msgpack :transit-json]\n :exceptions {:handlers {:compojure.api.exception/default my-logging-handler}}\n :api {:invalid-routes-fn (constantly nil)}\n :swagger {:spec \"/swagger.json\"\n :ui \"/api-docs\"\n :data {:info {:version \"1.0.0\"\n :title \"My API\"\n :description \"the description\"}}}}\n (context \"/api\" []\n ...))\n\n ### direct api options:\n\n - **:api** All api options are under `:api`.\n - **:invalid-routes-fn** A 2-arity function taking handler and a sequence of\n invalid routes (not satisfying compojure.api.route.Routing)\n setting value to nil ignores invalid routes completely.\n defaults to `compojure.api.routes/log-invalid-child-routes`\n - **:disable-api-middleware?** boolean to disable the `api-middleware` from api.\n - **:swagger** Options to configure the Swagger-routes. Defaults to nil.\n See `compojure.api.swagger/swagger-routes` for details.\n\n ### api-middleware options\n\n Opinionated chain of middlewares for web apis. Takes optional options-map.\n\n ### Exception handlers\n\n An error handler is a function of exception, ex-data and request to response.\n\n When defining these options, it is suggested to use alias for the exceptions namespace,\n e.g. `[compojure.api.exception :as ex]`.\n\n Default:\n\n {::ex/request-validation ex/request-validation-handler\n ::ex/request-parsing ex/request-parsing-handler\n ::ex/response-validation ex/response-validation-handler\n ::ex/default ex/safe-handler}\n\n Note: Because the handlers are merged into default handlers map, to disable default handler you\n need to provide `nil` value as handler.\n\n Note: To catch Schema errors use `{:schema.core/error ex/schema-error-handler}`.\n\n ### Options\n\n - **:exceptions** for *compojure.api.middleware/wrap-exceptions* (nil to unmount it)\n - **:handlers** Map of error handlers for different exception types, type refers to `:type` key in ExceptionInfo data.\n\n - **:formats** for Muuntaja middleware. Value can be a valid muuntaja options-map,\n a Muuntaja instance or nil (to unmount it). See\n https://github.com/metosin/muuntaja/blob/master/doc/Configuration.md for details.\n Cannot be combined with :format.\n\n - **:format** for ring-middleware-format middlewares (nil to unmount it). Cannot be combined with :formats.\n - **:formats** sequence of supported formats, e.g. `[:json-kw :edn]`\n - **:params-opts** for *ring.middleware.format-params/wrap-restful-params*,\n e.g. `{:transit-json {:handlers readers}}`\n - **:response-opts** for *ring.middleware.format-params/wrap-restful-response*,\n e.g. `{:transit-json {:handlers writers}}`\n\n - **:middleware** vector of extra middleware to be applied last (just before the handler).\n\n - **:ring-swagger** options for ring-swagger's swagger-json method.\n e.g. `{:ignore-missing-mappings? true}`\n\n - **:coercion** A function from request->type->coercion-matcher, used\n in endpoint coercion for :body, :string and :response.\n Defaults to `(constantly compojure.api.middleware/default-coercion-matchers)`\n Setting value to nil disables all coercion\n\n - **:components** Components which should be accessible to handlers using\n :components restructuring. (If you are using api,\n you might want to take look at using wrap-components\n middleware manually.). Defaults to nil (middleware not mounted)."} api compojure.api.api/api) +(defmacro defapi {:doc "Defines an api.\n\n API middleware options:\n\n Opinionated chain of middlewares for web apis. Takes optional options-map.\n\n ### Exception handlers\n\n An error handler is a function of exception, ex-data and request to response.\n\n When defining these options, it is suggested to use alias for the exceptions namespace,\n e.g. `[compojure.api.exception :as ex]`.\n\n Default:\n\n {::ex/request-validation ex/request-validation-handler\n ::ex/request-parsing ex/request-parsing-handler\n ::ex/response-validation ex/response-validation-handler\n ::ex/default ex/safe-handler}\n\n Note: Because the handlers are merged into default handlers map, to disable default handler you\n need to provide `nil` value as handler.\n\n Note: To catch Schema errors use `{:schema.core/error ex/schema-error-handler}`.\n\n ### Options\n\n - **:exceptions** for *compojure.api.middleware/wrap-exceptions* (nil to unmount it)\n - **:handlers** Map of error handlers for different exception types, type refers to `:type` key in ExceptionInfo data.\n\n - **:formats** for Muuntaja middleware. Value can be a valid muuntaja options-map,\n a Muuntaja instance or nil (to unmount it). See\n https://github.com/metosin/muuntaja/blob/master/doc/Configuration.md for details.\n Cannot be combined with :format.\n\n - **:format** for ring-middleware-format middlewares (nil to unmount it). Cannot be combined with :formats.\n - **:formats** sequence of supported formats, e.g. `[:json-kw :edn]`\n - **:params-opts** for *ring.middleware.format-params/wrap-restful-params*,\n e.g. `{:transit-json {:handlers readers}}`\n - **:response-opts** for *ring.middleware.format-params/wrap-restful-response*,\n e.g. `{:transit-json {:handlers writers}}`\n\n - **:middleware** vector of extra middleware to be applied last (just before the handler).\n\n - **:ring-swagger** options for ring-swagger's swagger-json method.\n e.g. `{:ignore-missing-mappings? true}`\n\n - **:coercion** A function from request->type->coercion-matcher, used\n in endpoint coercion for :body, :string and :response.\n Defaults to `(constantly compojure.api.middleware/default-coercion-matchers)`\n Setting value to nil disables all coercion\n\n - **:components** Components which should be accessible to handlers using\n :components restructuring. (If you are using api,\n you might want to take look at using wrap-components\n middleware manually.). Defaults to nil (middleware not mounted)."} [name & body] (list* (quote compojure.api.api/defapi) name body)) +(def ^{:arglists (quote ([data])), :doc "Creates a nested compojure-api Route from enchanced ring-swagger operations map.\n By default, applies both request- and response-coercion based on those definitions.\n\n Extra keys:\n\n - **:middleware** Middleware in duct-format either at top-level or under methods.\n Top-level mw are applied first if route matches, method-level\n mw are applied next if method matches\n\n - **:coercion** A named coercion or instance of Coercion\n in resource coercion for :body, :string and :response.\n Setting value to `nil` disables both request- &\n response coercion. See tests and wiki for details.\n\n Enhancements to ring-swagger operations map:\n\n 1) :parameters use ring request keys (query-params, path-params, ...) instead of\n swagger-params (query, path, ...). This keeps things simple as ring keys are used in\n the handler when destructuring the request.\n\n 2) at resource root, one can add any ring-swagger operation definitions, which will be\n available for all operations, using the following rules:\n\n 2.1) :parameters are deep-merged into operation :parameters\n 2.2) :responses are merged into operation :responses (operation can fully override them)\n 2.3) all others (:produces, :consumes, :summary,...) are deep-merged by compojure-api\n\n 3) special keys `:handler` and/or `:async-handler` either under operations or at top-level.\n They should be 1-ary and 3-ary Ring handler functions, respectively, that are responsible\n for the actual request processing. Handler lookup order is the following:\n\n 3.1) If called asynchronously, operations-level :async-handler\n 3.2) Operations-level :handler\n 3.3) If called asynchronously, top-level :async-handler\n 3.4) Top-level :handler\n\n 4) request-coercion is applied once, using deep-merged parameters for a given\n operation or resource-level if only resource-level handler is defined.\n\n 5) response-coercion is applied once, using merged responses for a given\n operation or resource-level if only resource-level handler is defined.\n\n Note: Swagger operations are generated only from declared operations (:get, :post, ..),\n despite the top-level handler could process more operations.\n\n Example:\n\n (resource\n {:parameters {:query-params {:x Long}}\n :responses {500 {:schema {:reason s/Str}}}\n :get {:parameters {:query-params {:y Long}}\n :responses {200 {:schema {:total Long}}}\n :handler (fn [request]\n (ok {:total (+ (-> request :query-params :x)\n (-> request :query-params :y))}))}\n :post {}\n :handler (constantly\n (internal-server-error {:reason \"not implemented\"}))})"} resource compojure.api.resource/resource) (defmacro path-for {:doc "Extracts the lookup-table from request and finds a route by name."} [route-name & arg2] (list* (quote compojure.api.routes/path-for) route-name arg2)) (def ^{:arglists (quote ([] [options])), :doc "Returns routes for swagger-articats (ui & spec). Accepts an options map, with the\n following options:\n **:ui** Path for the swagger-ui (defaults to \"/\").\n Setting the value to nil will cause the swagger-ui not to be mounted\n **:spec** Path for the swagger-spec (defaults to \"/swagger.json\")\n Setting the value to nil will cause the swagger-ui not to be mounted\n **:data** Swagger data in the Ring-Swagger format.\n **:options**\n **:ui** Options to configure the ui\n **:spec** Options to configure the spec. Nada at the moment.\n Example options:\n {:ui \"/api-docs\"\n :spec \"/swagger.json\"\n :options {:ui {:jsonEditor true}\n :spec {}}\n :data {:basePath \"/app\"\n :info {:version \"1.0.0\"\n :title \"Sausages\"\n :description \"Sausage description\"\n :termsOfService \"http://helloreverb.com/terms/\"\n :contact {:name \"My API Team\"\n :email \"foo@example.com\"\n :url \"http://www.metosin.fi\"}\n :license {:name: \"Eclipse Public License\"\n :url: \"http://www.eclipse.org/legal/epl-v10.html\"}}\n :tags [{:name \"sausages\", :description \"Sausage api-set\"}]}}"} swagger-routes compojure.api.swagger/swagger-routes) (def ^{:arglists (quote ([schema desc & kvs])), :doc "Attach description and possibly other meta-data to a schema."} describe ring.swagger.json-schema/describe) diff --git a/src/compojure/api/upload.clj b/src/compojure/api/upload.clj index 0a59e5e1..61ca42a0 100644 --- a/src/compojure/api/upload.clj +++ b/src/compojure/api/upload.clj @@ -1,5 +1,5 @@ ;; NOTE: This namespace is generated by compojure.api.dev.gen (ns compojure.api.upload (:require ring.middleware.multipart-params ring.swagger.upload)) -(def ^{:arglists (quote ([handler] [handler options])), :doc "Middleware to parse multipart parameters from a request. Adds the\n following keys to the request map:\n\n :multipart-params - a map of multipart parameters\n :params - a merged map of all types of parameter\n\n The following options are accepted\n\n :encoding - character encoding to use for multipart parsing.\n Overrides the encoding specified in the request. If not\n specified, uses the encoding specified in a part named\n \"_charset_\", or the content type for each part, or\n request character encoding if the part has no encoding,\n or \"UTF-8\" if no request character encoding is set.\n\n :fallback-encoding - specifies the character encoding used in parsing if a\n part of the request does not specify encoding in its\n content type or no part named \"_charset_\" is present.\n Has no effect if :encoding is also set.\n\n :store - a function that stores a file upload. The function\n should expect a map with :filename, content-type and\n :stream keys, and its return value will be used as the\n value for the parameter in the multipart parameter map.\n The default storage function is the temp-file-store.\n\n :progress-fn - a function that gets called during uploads. The\n function should expect four parameters: request,\n bytes-read, content-length, and item-count."} wrap-multipart-params ring.middleware.multipart-params/wrap-multipart-params) +(def ^{:arglists (quote ([handler] [handler options])), :doc "Middleware to parse multipart parameters from a request. Adds the\n following keys to the request map:\n\n :multipart-params - a map of multipart parameters\n :params - a merged map of all types of parameter\n\n The following options are accepted\n\n :encoding - character encoding to use for multipart parsing.\n Overrides the encoding specified in the request. If not\n specified, uses the encoding specified in a part named\n \"_charset_\", or the content type for each part, or\n request character encoding if the part has no encoding,\n or \"UTF-8\" if no request character encoding is set.\n\n :fallback-encoding - specifies the character encoding used in parsing if a\n part of the request does not specify encoding in its\n content type or no part named \"_charset_\" is present.\n Has no effect if :encoding is also set.\n\n :store - a function that stores a file upload. The function\n should expect a map with :filename, :content-type and\n :stream keys, and its return value will be used as the\n value for the parameter in the multipart parameter map.\n The default storage function is the temp-file-store.\n\n :progress-fn - a function that gets called during uploads. The\n function should expect four parameters: request,\n bytes-read, content-length, and item-count."} wrap-multipart-params ring.middleware.multipart-params/wrap-multipart-params) (def ^{:doc "Schema for file param created by ring.middleware.multipart-params.temp-file store."} TempFileUpload ring.swagger.upload/TempFileUpload) (def ^{:doc "Schema for file param created by ring.middleware.multipart-params.byte-array store."} ByteArrayUpload ring.swagger.upload/ByteArrayUpload) diff --git a/test/compojure/api/integration_test.clj b/test/compojure/api/integration_test.clj index 1c907ded..320a4ce1 100644 --- a/test/compojure/api/integration_test.clj +++ b/test/compojure/api/integration_test.clj @@ -1,6 +1,7 @@ (ns compojure.api.integration-test (:require [compojure.api.sweet :refer :all] [compojure.api.test-utils :refer :all] + compojure.api.middleware.rmf-muuntaja-adapter [compojure.api.exception :as ex] [compojure.api.swagger :as swagger] [midje.sweet :refer :all] diff --git a/test/compojure/api/meta_test.clj b/test/compojure/api/meta_test.clj deleted file mode 100644 index 68c1a03d..00000000 --- a/test/compojure/api/meta_test.clj +++ /dev/null @@ -1,7 +0,0 @@ -(ns compojure.api.meta-test - (:require [compojure.api.meta :refer :all] - [midje.sweet :refer :all])) - -(fact "src-coerce! with deprecated types" - (src-coerce! nil nil :query) => (throws AssertionError) - (src-coerce! nil nil :json) => (throws AssertionError)) diff --git a/test/compojure/api/routes_test.clj b/test/compojure/api/routes_test.clj index b44e100b..89d64ce1 100644 --- a/test/compojure/api/routes_test.clj +++ b/test/compojure/api/routes_test.clj @@ -1,5 +1,6 @@ (ns compojure.api.routes-test (:require [midje.sweet :refer :all] + compojure.api.middleware.rmf-muuntaja-adapter [compojure.api.sweet :refer :all] [compojure.api.routes :as routes] [ring.util.http-response :refer :all]