Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ce54553

Browse files
committedMay 14, 2024
[skip ci]
1 parent a8cf4d6 commit ce54553

14 files changed

+1551
-486
lines changed
 
File renamed without changes.

‎project.clj

+40-31
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,70 @@
1-
(defproject metosin/compojure-api "1.1.15-SNAPSHOT"
1+
(defproject metosin/compojure-api "2.0.0-alpha34-SNAPSHOT"
22
:description "Compojure Api"
33
:url "https://github.com/metosin/compojure-api"
44
:license {:name "Eclipse Public License"
55
:url "http://www.eclipse.org/legal/epl-v10.html"
66
:distribution :repo
77
:comments "same as Clojure"}
8-
:scm {:name "git"
9-
:url "https://github.com/metosin/compojure-api"}
10-
:dependencies [[prismatic/plumbing "0.6.0"]
11-
[cheshire "5.13.0"]
12-
[compojure "1.6.1"]
13-
[prismatic/schema "1.1.12"]
14-
[org.tobereplaced/lettercase "1.0.0"]
15-
[frankiesardo/linked "1.3.0"]
16-
[ring-middleware-format "0.7.4"]
8+
:dependencies [[prismatic/schema "1.1.12"]
9+
[prismatic/plumbing "0.5.5"]
10+
[ikitommi/linked "1.3.1-alpha1"] ;; waiting for the original
11+
[metosin/muuntaja "0.6.6"]
12+
[com.fasterxml.jackson.datatype/jackson-datatype-joda "2.10.1"]
13+
[ring/ring-core "1.8.0"]
14+
[compojure "1.6.1" ]
15+
[metosin/spec-tools "0.10.6"]
1716
[metosin/ring-http-response "0.9.1"]
17+
[metosin/ring-swagger-ui "3.24.3"]
1818
[metosin/ring-swagger "1.0.0"]
19-
[metosin/ring-swagger-ui "2.2.10"]]
19+
20+
;; Fix dependency conflicts
21+
[clj-time "0.15.2"]
22+
[joda-time "2.10.5"]
23+
[riddley "0.2.0"]]
24+
:pedantic? :abort
2025
:profiles {:uberjar {:aot :all
2126
:ring {:handler examples.thingie/app}
2227
:source-paths ["examples/thingie/src"]
2328
:dependencies [[org.clojure/clojure "1.9.0"]
2429
[http-kit "2.3.0"]
2530
[reloaded.repl "0.2.4"]
2631
[com.stuartsierra/component "0.4.0"]]}
27-
:dev {:jvm-opts ["-Dcompojure.api.core.allow-dangerous-middleware=true"]
28-
:repl-options {:init-ns user}
29-
:plugins [[lein-clojars "0.9.1"]
30-
[lein-midje "3.2.1"]
31-
[lein-ring "0.12.0"]
32+
:dev {:plugins [[lein-clojars "0.9.1"]
33+
[lein-ring "0.12.5"]
3234
[funcool/codeina "0.5.0"]]
3335
:dependencies [[org.clojure/clojure "1.9.0"]
34-
;; bump
35-
[fipp "0.6.26"]
36-
[metosin/spec-tools "0.10.6"]
37-
[metosin/muuntaja "0.6.6"]
38-
[metosin/jsonista "0.2.5"]
39-
[com.fasterxml.jackson.datatype/jackson-datatype-joda "2.10.1"]
40-
[slingshot "0.12.2"]
41-
[peridot "0.5.1"]
42-
[javax.servlet/servlet-api "2.5"]
43-
[midje "1.9.9"]
36+
[org.clojure/core.unify "0.6.0"]
37+
[org.clojure/core.async "0.6.532"]
38+
[javax.servlet/javax.servlet-api "4.0.1"]
39+
[peridot "0.5.2"]
4440
[com.stuartsierra/component "0.4.0"]
41+
[expound "0.8.2"]
42+
[metosin/jsonista "0.2.5"]
4543
[reloaded.repl "0.2.4"]
44+
[metosin/muuntaja-msgpack "0.6.6"]
45+
[metosin/muuntaja-yaml "0.6.6"]
46+
[org.immutant/immutant "2.1.10"]
4647
[http-kit "2.3.0"]
4748
[criterium "0.4.5"]]
49+
:jvm-opts ["-Dcompojure.api.meta.static-context-coach={:default :assert :verbose true}"]
50+
:test-paths ["test19"]
4851
:ring {:handler examples.thingie/app
4952
:reload-paths ["src" "examples/thingie/src"]}
5053
:source-paths ["examples/thingie/src" "examples/thingie/dev-src"]
5154
:main examples.server}
5255
:perf {:jvm-opts ^:replace ["-server"
5356
"-Xmx4096m"
5457
"-Dclojure.compiler.direct-linking=true"]}
55-
:logging {:dependencies [[org.clojure/tools.logging "0.5.0"]]}
58+
:logging {:dependencies [[org.clojure/tools.logging "0.5.0"]
59+
[org.slf4j/jcl-over-slf4j "1.7.30"]
60+
[org.slf4j/jul-to-slf4j "1.7.30"]
61+
[org.slf4j/log4j-over-slf4j "1.7.30"]
62+
[ch.qos.logback/logback-classic "1.2.3" ]]}
5663
:1.10 {:dependencies [[org.clojure/clojure "1.10.1"]]}
5764
:1.11 {:dependencies [[org.clojure/clojure "1.11.3"]]}
58-
:1.12 {:dependencies [[org.clojure/clojure "1.12.0-alpha11"]]}}
65+
:1.12 {:dependencies [[org.clojure/clojure "1.12.0-alpha11"]]}
66+
:async {:jvm-opts ["-Dcompojure-api.test.async=true"]
67+
:dependencies [[manifold "0.1.8" :exclusions [org.clojure/tools.logging]]]}}
5968
:eastwood {:namespaces [:source-paths]
6069
:add-linters [:unused-namespaces]}
6170
:codeina {:sources ["src"]
@@ -79,10 +88,10 @@
7988
["change" "version" "leiningen.release/bump-version"]
8089
["vcs" "commit"]
8190
["vcs" "push"]]
82-
:aliases {"all" ["with-profile" "dev:dev,logging:dev,1.10:dev,1.11:dev,1.12"]
91+
:aliases {"all" ["with-profile" "dev:dev,async:dev,1.10:dev,1.11:dev,1.12"]
8392
"start-thingie" ["run"]
8493
"aot-uberjar" ["with-profile" "uberjar" "do" "clean," "ring" "uberjar"]
85-
"test-ancient" ["midje"]
94+
"test-ancient" ["test"]
8695
"perf" ["with-profile" "default,dev,perf"]
8796
"deploy!" ^{:doc "Recompile sources, then deploy if tests succeed."}
88-
["do" ["clean"] ["midje"] ["deploy" "clojars"]]})
97+
["do" ["clean"] ["test"] ["deploy" "clojars"]]})

‎src/compojure/api/api.clj

+42-17
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
(ns compojure.api.api
22
(:require [compojure.api.core :as c]
33
[compojure.api.swagger :as swagger]
4-
[compojure.api.middleware :as middleware]
4+
[compojure.api.middleware :as mw]
5+
[compojure.api.request :as request]
56
[compojure.api.routes :as routes]
67
[compojure.api.common :as common]
7-
[compojure.api.coerce :as coerce]
8+
[compojure.api.request :as request]
89
[ring.swagger.common :as rsc]
910
[ring.swagger.middleware :as rsm]))
1011

1112
(def api-defaults
1213
(merge
13-
middleware/api-middleware-defaults
14+
mw/api-middleware-defaults
1415
{:api {:invalid-routes-fn routes/log-invalid-child-routes
1516
:disable-api-middleware? false}
1617
:swagger {:ui nil, :spec nil}}))
@@ -23,8 +24,7 @@
2324
options map as the first parameter:
2425
2526
(api
26-
{:formats [:json-kw :edn :transit-msgpack :transit-json]
27-
:exceptions {:handlers {:compojure.api.exception/default my-logging-handler}}
27+
{:exceptions {:handlers {:compojure.api.exception/default my-logging-handler}}
2828
:api {:invalid-routes-fn (constantly nil)}
2929
:swagger {:spec \"/swagger.json\"
3030
:ui \"/api-docs\"
@@ -47,30 +47,55 @@
4747
4848
### api-middleware options
4949
50+
See `compojure.api.middleware/api-middleware` for more available options.
51+
5052
" (:doc (meta #'compojure.api.middleware/api-middleware)))}
5153
api
5254
[& body]
5355
(let [[options handlers] (common/extract-parameters body false)
56+
_ (assert (not (contains? options :format))
57+
(str "ERROR: Option [:format] is not used with 2.* version.\n"
58+
"Compojure-api uses now Muuntaja insted of ring-middleware-format,\n"
59+
"the new formatting options for it should be under [:formats]. See\n"
60+
"[[api-middleware]] documentation for more details.\n"))
61+
_ (when (and (not (:formatter options))
62+
(not (contains? options :formats))
63+
(not (System/getProperty "compojure.api.middleware.global-default-formatter")))
64+
(throw (ex-info (str "ERROR: Please set `:formatter :muuntaja` in the options map of `api`.\n"
65+
"e.g., (api {:formatter :muuntaja} routes...)\n"
66+
"To prepare for backwards compatibility with compojure-api 1.x, the formatting library must be\n"
67+
"explicitly chosen if not configured by `:format` (ring-middleware-format) or \n"
68+
"`:formats` (muuntaja). Once 2.x is stable, the default will be `:formatter :ring-middleware-format`.\n"
69+
"To globally override the formatter, use -Dcompojure.api.middleware.global-default-formatter=:muuntaja")
70+
{})))
5471
options (rsc/deep-merge api-defaults options)
5572
handler (apply c/routes (concat [(swagger/swagger-routes (:swagger options))] handlers))
56-
routes (routes/get-routes handler (:api options))
73+
partial-api-route (routes/map->Route
74+
{:childs [handler]
75+
:info {:coercion (:coercion options)}})
76+
routes (routes/get-routes partial-api-route (:api options))
5777
paths (-> routes routes/ring-swagger-paths swagger/transform-operations)
5878
lookup (routes/route-lookup-table routes)
5979
swagger-data (get-in options [:swagger :data])
6080
enable-api-middleware? (not (get-in options [:api :disable-api-middleware?]))
61-
api-handler (cond-> handler
62-
swagger-data (rsm/wrap-swagger-data swagger-data)
63-
enable-api-middleware? (middleware/api-middleware
64-
(dissoc options :api :swagger))
65-
true (middleware/wrap-options
66-
{:paths paths
67-
:coercer (coerce/memoized-coercer)
68-
:lookup lookup}))]
69-
(routes/create nil nil {} [handler] api-handler)))
81+
api-middleware-options (dissoc (mw/api-middleware-options (assoc (dissoc options :api :swagger) ::via-api true))
82+
::mw/api-middleware-defaults)
83+
api-handler (-> handler
84+
(cond-> swagger-data (rsm/wrap-swagger-data swagger-data))
85+
(cond-> enable-api-middleware? (mw/api-middleware
86+
api-middleware-options))
87+
(mw/wrap-inject-data
88+
{::request/paths paths
89+
::request/lookup lookup}))]
90+
(assoc partial-api-route :handler api-handler)))
7091

7192
(defmacro
72-
^{:doc (str
73-
"Defines an api.
93+
^{:superseded-by "api"
94+
:deprecated "2.0.0"
95+
:doc (str
96+
"Deprecated: please use (def name (api ...body..))
97+
98+
Defines an api.
7499
75100
API middleware options:
76101

‎src/compojure/api/coerce.clj

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
;; 1.1.x
12
(ns compojure.api.coerce
23
(:require [schema.coerce :as sc]
34
[compojure.api.middleware :as mw]

‎src/compojure/api/core.clj

+12-12
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,16 @@
5454
:deprecated "1.1.14"
5555
:superseded-by "route-middleware"}
5656
[middleware & body]
57-
(when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning"))
58-
(println (str "compojure.api.core.middleware is deprecated because of security issues. "
59-
"Please use route-middleware instead. middleware will be disabled in a future release."
60-
"Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning.")))
57+
(assert (= "true" (System/getProperty "compojure.api.core.allow-dangerous-middleware"))
58+
(str "compojure.api.core.middleware is deprecated because of security issues. "
59+
"Please use route-middleware instead. "
60+
"Set compojure.api.core.allow-dangerous-middleware=true to keep using middleware."))
6161
`(let [body# (routes ~@body)
6262
wrap-mw# (mw/compose-middleware ~middleware)]
6363
(routes/create nil nil {} [body#] (wrap-mw# body#))))
6464

6565
(defn route-middleware
66-
"Wraps routes with given middleware using thread-first macro."
66+
"Wraps routes with given middlewares using thread-first macro."
6767
{:style/indent 1
6868
:supercedes "middleware"}
6969
[middleware & body]
@@ -76,11 +76,11 @@
7676

7777
(defmacro context {:style/indent 2} [& args] (meta/restructure nil args {:context? true :&form &form :&env &env}))
7878

79-
(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args nil))
80-
(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args nil))
81-
(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args nil))
82-
(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args nil))
83-
(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args nil))
79+
(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args nil))
80+
(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args nil))
81+
(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args nil))
82+
(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args nil))
83+
(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args nil))
8484
(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args nil))
85-
(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args nil))
86-
(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args nil))
85+
(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args nil))
86+
(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args nil))

‎src/compojure/api/exception.clj

+49-30
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
(:require [ring.util.http-response :as response]
33
[clojure.walk :as walk]
44
[compojure.api.impl.logging :as logging]
5-
[schema.utils :as su])
6-
(:import [schema.utils ValidationError NamedError]
7-
[com.fasterxml.jackson.core JsonParseException]
8-
[org.yaml.snakeyaml.parser ParserException]))
5+
[compojure.api.coercion.core :as cc]
6+
[compojure.api.coercion.schema]))
97

108
;;
119
;; Default exception handlers
@@ -21,42 +19,62 @@
2119
(response/internal-server-error {:type "unknown-exception"
2220
:class (.getName (.getClass e))}))
2321

24-
(defn stringify-error
25-
"Stringifies symbols and validation errors in Schema error, keeping the structure intact."
26-
[error]
27-
(walk/postwalk
28-
(fn [x]
29-
(cond
30-
(instance? ValidationError x) (str (su/validation-error-explain x))
31-
(instance? NamedError x) (str (su/named-error-explain x))
32-
:else x))
33-
error))
34-
22+
;; TODO: coercion should handle how to publish data
3523
(defn response-validation-handler
36-
"Creates error response based on Schema error."
24+
"Creates error response based on a response error. The following keys are available:
25+
26+
:type type of the exception (::response-validation)
27+
:coercion coercion instance used
28+
:in location of the value ([:response :body])
29+
:schema schema to be validated against
30+
:error schema error
31+
:request raw request
32+
:response raw response"
3733
[e data req]
38-
(response/internal-server-error {:errors (stringify-error (su/error-val data))}))
34+
(response/internal-server-error
35+
(-> data
36+
(dissoc :request :response)
37+
(update :coercion cc/get-name)
38+
(assoc :value (-> data :response :body))
39+
(->> (cc/encode-error (:coercion data))))))
3940

41+
;; TODO: coercion should handle how to publish data
4042
(defn request-validation-handler
41-
"Creates error response based on Schema error."
43+
"Creates error response based on Schema error. The following keys are available:
44+
45+
:type type of the exception (::request-validation)
46+
:coercion coercion instance used
47+
:value value that was validated
48+
:in location of the value (e.g. [:request :query-params])
49+
:schema schema to be validated against
50+
:error schema error
51+
:request raw request"
4252
[e data req]
43-
(response/bad-request {:errors (stringify-error (su/error-val data))}))
53+
(response/bad-request
54+
(-> data
55+
(dissoc :request)
56+
(update :coercion cc/get-name)
57+
(->> (cc/encode-error (:coercion data))))))
58+
59+
(defn http-response-handler
60+
"reads response from ex-data :response"
61+
[_ {:keys [response]} _]
62+
response)
4463

4564
(defn schema-error-handler
4665
"Creates error response based on Schema error."
4766
[e data req]
48-
; FIXME: Why error is not wrapped to ErrorContainer here?
49-
(response/bad-request {:errors (stringify-error (:error data))}))
67+
(response/bad-request
68+
{:errors (compojure.api.coercion.schema/stringify (:error data))}))
5069

5170
(defn request-parsing-handler
5271
[^Exception ex data req]
53-
(let [cause (.getCause ex)]
54-
(response/bad-request {:type (cond
55-
(instance? JsonParseException cause) "json-parse-exception"
56-
(instance? ParserException cause) "yaml-parse-exception"
57-
:else "parse-exception")
58-
:message (.getMessage cause)})))
59-
72+
(let [cause (.getCause ex)
73+
original (.getCause cause)]
74+
(response/bad-request
75+
(merge (select-keys data [:type :format :charset])
76+
(if original {:original (.getMessage original)})
77+
{:message (.getMessage cause)}))))
6078
;;
6179
;; Logging
6280
;;
@@ -75,5 +93,6 @@
7593
;; Mappings from other Exception types to our base types
7694
;;
7795

78-
(def legacy-exception-types
79-
{:ring.swagger.schema/validation ::request-validation})
96+
(def mapped-exception-types
97+
{:ring.swagger.schema/validation ::request-validation
98+
:muuntaja/decode ::request-parsing})

‎src/compojure/api/impl/logging.clj

+11-10
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@
66
(declare log!)
77

88
;; use c.t.l logging if available, default to console logging
9-
(if (find-ns 'clojure.tools.logging)
9+
(try
1010
(eval
1111
`(do
1212
(require 'clojure.tools.logging)
1313
(defmacro ~'log! [& ~'args]
14-
`(do
15-
(clojure.tools.logging/log ~@~'args)))))
16-
(let [log (fn [level more] (println (.toUpperCase (name level)) (str/join " " more)))]
17-
(defn log! [level x & more]
18-
(if (instance? Throwable x)
19-
(do
20-
(log level more)
21-
(.printStackTrace ^Throwable x))
22-
(log level (into [x] more))))))
14+
`(clojure.tools.logging/log ~@~'args))))
15+
(catch Exception _
16+
(let [log (fn [level more] (println (.toUpperCase (name level)) (str/join " " more)))]
17+
(defn log! [level x & more]
18+
(if (instance? Throwable x)
19+
(do
20+
(log level more)
21+
(.printStackTrace ^Throwable x))
22+
(log level (into [x] more))))
23+
(log! :warn "clojure.tools.logging not found on classpath, compojure.api logging to console."))))

‎src/compojure/api/meta.clj

+914-143
Large diffs are not rendered by default.

‎src/compojure/api/middleware.clj

+268-148
Large diffs are not rendered by default.

‎src/compojure/api/resource.clj

+118-43
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
(ns compojure.api.resource
22
(:require [compojure.api.routes :as routes]
3-
[compojure.api.coerce :as coerce]
3+
[compojure.api.coercion :as coercion]
4+
[compojure.api.methods :as methods]
45
[ring.swagger.common :as rsc]
56
[schema.core :as s]
67
[plumbing.core :as p]
7-
[compojure.api.middleware :as mw]))
8+
[compojure.api.async]
9+
[compojure.api.middleware :as mw]
10+
[compojure.api.coercion.core :as cc]))
811

912
(def ^:private +mappings+
10-
{:methods #{:get :head :patch :delete :options :post :put}
13+
{:methods methods/all-methods
1114
:parameters {:query-params [:query-params :query :string true]
1215
:body-params [:body-params :body :body false]
1316
:form-params [:form-params :formData :string true]
@@ -25,51 +28,112 @@
2528
(:parameters +mappings+))
2629
(dissoc info :handler)))
2730

31+
(defn- inject-coercion [request info]
32+
(if (contains? info :coercion)
33+
(coercion/set-request-coercion request (:coercion info))
34+
request))
35+
2836
(defn- coerce-request [request info ks]
2937
(reduce-kv
3038
(fn [request ring-key [compojure-key _ type open?]]
31-
(if-let [schema (get-in info (concat ks [:parameters ring-key]))]
32-
(let [schema (if open? (assoc schema s/Keyword s/Any) schema)]
33-
(update request ring-key merge (coerce/coerce! schema compojure-key type request)))
39+
(if-let [model (get-in info (concat ks [:parameters ring-key]))]
40+
(let [coerced (coercion/coerce-request!
41+
model compojure-key type (not= :body type) open? request)]
42+
(if open?
43+
(update request ring-key merge coerced)
44+
(assoc request ring-key coerced)))
3445
request))
35-
request
46+
(inject-coercion request info)
3647
(:parameters +mappings+)))
3748

3849
(defn- coerce-response [response info request ks]
39-
(coerce/coerce-response! request response (get-in info (concat ks [:responses]))))
40-
41-
(defn- resolve-handler [info request-method]
42-
(or
43-
(get-in info [request-method :handler])
44-
(get-in info [:handler])))
50+
(coercion/coerce-response! request response (get-in info (concat ks [:responses]))))
51+
52+
(defn- maybe-async [async? x]
53+
(if (and async? x) [x true]))
54+
55+
(defn- maybe-sync [x]
56+
(if x [x false]))
57+
58+
(defn- resolve-handler [info path-info route request-method async?]
59+
(and
60+
(or
61+
;; directly under a context
62+
(= path-info "/")
63+
;; under an compojure endpoint
64+
route
65+
;; vanilla ring
66+
(nil? path-info))
67+
(let [[handler async] (or
68+
(maybe-async async? (get-in info [request-method :async-handler]))
69+
(maybe-sync (get-in info [request-method :handler]))
70+
(maybe-async async? (get-in info [:async-handler]))
71+
(maybe-sync (get-in info [:handler])))]
72+
(if handler
73+
[handler async]))))
74+
75+
(defn- middleware-chain [info request-method handler]
76+
(let [direct-mw (:middleware info)
77+
method-mw (:middleware (get info request-method))
78+
middleware (mw/compose-middleware (concat direct-mw method-mw))]
79+
(middleware handler)))
4580

4681
(defn- create-childs [info]
4782
(map
4883
(fn [[method info]]
49-
(routes/create "/" method (swaggerize info) nil nil))
84+
(routes/map->Route
85+
{:path "/"
86+
:method method
87+
:info {:public (swaggerize info)}}))
5088
(select-keys info (:methods +mappings+))))
5189

52-
(defn- create-handler [info {:keys [coercion]}]
53-
(fn [{:keys [request-method] :as request}]
54-
(let [request (if coercion (assoc-in request mw/coercion-request-ks coercion) request)
55-
ks (if (contains? info request-method) [request-method] [])]
56-
(if-let [handler (resolve-handler info request-method)]
57-
(-> (coerce-request request info ks)
58-
handler
59-
(coerce-response info request ks))))))
90+
(defn- handle-sync [info {:keys [request-method path-info :compojure/route] :as request}]
91+
(when-let [[raw-handler] (resolve-handler info path-info route request-method false)]
92+
(let [ks (if (contains? info request-method) [request-method] [])
93+
handler (middleware-chain info request-method raw-handler)]
94+
(-> (coerce-request request info ks)
95+
(handler)
96+
(compojure.response/render request)
97+
(coerce-response info request ks)))))
98+
99+
(defn- handle-async [info {:keys [request-method path-info :compojure/route] :as request} respond raise]
100+
(if-let [[raw-handler async?] (resolve-handler info path-info route request-method true)]
101+
(let [ks (if (contains? info request-method) [request-method] [])
102+
respond-coerced (fn [response]
103+
(respond
104+
(try (coerce-response response info request ks)
105+
(catch Throwable e (raise e)))))
106+
handler (middleware-chain info request-method raw-handler)]
107+
(try
108+
(as-> (coerce-request request info ks) $
109+
(if async?
110+
(handler $ #(compojure.response/send % $ respond-coerced raise) raise)
111+
(compojure.response/send (handler $) $ respond-coerced raise)))
112+
(catch Throwable e
113+
(raise e))))
114+
(respond nil)))
115+
116+
(defn- create-handler [info]
117+
(fn
118+
([request]
119+
(handle-sync info request))
120+
([request respond raise]
121+
(handle-async info request respond raise))))
60122

61123
(defn- merge-parameters-and-responses [info]
62124
(let [methods (select-keys info (:methods +mappings+))]
63125
(-> info
64126
(merge
65-
(p/for-map [[method method-info] methods]
66-
method (-> method-info
67-
(->> (rsc/deep-merge (select-keys info [:parameters])))
68-
(update :responses (fn [responses] (merge (:responses info) responses)))))))))
69-
70-
(defn- root-info [info]
127+
(p/for-map [[method method-info] methods
128+
:let [responses (merge
129+
(:responses info)
130+
(:responses method-info))]]
131+
method (cond-> (->> method-info (rsc/deep-merge (select-keys info [:parameters])))
132+
(seq responses) (assoc :responses responses)))))))
133+
134+
(defn- public-root-info [info]
71135
(-> (reduce dissoc info (:methods +mappings+))
72-
(dissoc :parameters :responses)))
136+
(dissoc :parameters :responses :coercion)))
73137

74138
;;
75139
;; Public api
@@ -81,17 +145,21 @@
81145
; TODO: validate input against ring-swagger schema, fail for missing handlers
82146
; TODO: extract parameter schemas from handler fnks?
83147
(defn resource
84-
"Creates a nested compojure-api Route from enchanced ring-swagger operations map and options.
148+
"Creates a nested compojure-api Route from enchanced ring-swagger operations map.
85149
By default, applies both request- and response-coercion based on those definitions.
86150
87-
Options:
151+
Extra keys:
152+
153+
- **:middleware** Middleware in duct-format either at top-level or under methods.
154+
Top-level mw are applied first if route matches, method-level
155+
mw are applied next if method matches
88156
89157
- **:coercion** A function from request->type->coercion-matcher, used
90158
in resource coercion for :body, :string and :response.
91159
Setting value to `(constantly nil)` disables both request- &
92160
response coercion. See tests and wiki for details.
93161
94-
Enchancements to ring-swagger operations map:
162+
Enhancements to ring-swagger operations map:
95163
96164
1) :parameters use ring request keys (query-params, path-params, ...) instead of
97165
swagger-params (query, path, ...). This keeps things simple as ring keys are used in
@@ -104,9 +172,14 @@
104172
2.2) :responses are merged into operation :responses (operation can fully override them)
105173
2.3) all others (:produces, :consumes, :summary,...) are deep-merged by compojure-api
106174
107-
3) special key `:handler` either under operations or at top-level. Value should be a
108-
ring-handler function, responsible for the actual request processing. Handler lookup
109-
order is the following: operations-level, top-level.
175+
3) special keys `:handler` and/or `:async-handler` either under operations or at top-level.
176+
They should be 1-ary and 3-ary Ring handler functions, respectively, that are responsible
177+
for the actual request processing. Handler lookup order is the following:
178+
179+
3.1) If called asynchronously, operations-level :async-handler
180+
3.2) Operations-level :handler
181+
3.3) If called asynchronously, top-level :async-handler
182+
3.4) Top-level :handler
110183
111184
4) request-coercion is applied once, using deep-merged parameters for a given
112185
operation or resource-level if only resource-level handler is defined.
@@ -130,11 +203,13 @@
130203
:post {}
131204
:handler (constantly
132205
(internal-server-error {:reason \"not implemented\"}))})"
133-
([info]
134-
(resource info {}))
135-
([info options]
136-
(let [info (merge-parameters-and-responses info)
137-
root-info (swaggerize (root-info info))
138-
childs (create-childs info)
139-
handler (create-handler info options)]
140-
(routes/create nil nil root-info childs handler))))
206+
[data]
207+
(let [data (merge-parameters-and-responses data)
208+
public-info (swaggerize (public-root-info data))
209+
info (merge {:public public-info} (select-keys data [:coercion]))
210+
childs (create-childs data)
211+
handler (create-handler data)]
212+
(routes/map->Route
213+
{:info info
214+
:childs childs
215+
:handler handler})))

‎src/compojure/api/routes.clj

+73-35
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
(ns compojure.api.routes
22
(:require [compojure.core :refer :all]
33
[clojure.string :as string]
4-
[cheshire.core :as json]
5-
[compojure.api.middleware :as mw]
4+
[compojure.api.methods :as methods]
5+
[compojure.api.request :as request]
66
[compojure.api.impl.logging :as logging]
7+
[compojure.api.impl.json :as json]
78
[compojure.api.common :as common]
9+
[muuntaja.core :as m]
810
[ring.swagger.common :as rsc]
911
[clojure.string :as str]
1012
[linked.core :as linked]
1113
[compojure.response]
12-
[schema.core :as s])
13-
(:import [clojure.lang AFn IFn Var]))
14+
[schema.core :as s]
15+
[compojure.api.coercion :as coercion])
16+
(:import (clojure.lang AFn IFn Var IDeref)
17+
(java.io Writer)))
1418

1519
;;
1620
;; Route records
@@ -47,37 +51,61 @@
4751
(update-in route [0] (fn [uri] (if (str/blank? uri) "/" uri))))
4852
(-get-routes handler options))))
4953

54+
(defn get-static-context-routes
55+
([handler]
56+
(get-static-context-routes handler nil))
57+
([handler options]
58+
(filter (fn [[_ _ info]] (get info :static-context?))
59+
(get-routes handler options))))
60+
61+
(defn- realize-childs [route]
62+
(update route :childs #(if (instance? IDeref %) @% %)))
63+
64+
(defn- filter-childs [route]
65+
(update route :childs (partial filter (partial satisfies? Routing))))
66+
5067
(defrecord Route [path method info childs handler]
5168
Routing
5269
(-get-routes [this options]
53-
(let [valid-childs (filter-routes this options)]
54-
(if (seq childs)
70+
(let [this (-> this realize-childs)
71+
valid-childs (filter-routes this options)
72+
make-method-path-fn (fn [m] [path m info])]
73+
(if (-> this filter-childs :childs seq)
5574
(vec
5675
(for [[p m i] (mapcat #(-get-routes % options) valid-childs)]
5776
[(->paths path p) m (rsc/deep-merge info i)]))
58-
(into [] (if path [[path method info]])))))
77+
(into [] (cond
78+
(and path method) [(make-method-path-fn method)]
79+
path (mapv make-method-path-fn methods/all-methods))))))
5980

6081
compojure.response/Renderable
61-
(render [_ {:keys [uri request-method]}]
62-
(throw
63-
(ex-info
64-
(str "\ncompojure.api.routes/Route can't be returned from endpoint "
65-
(-> request-method name str/upper-case) " \"" uri "\". "
66-
"For nested routes, use `context` instead: (context \"path\" [] ...)\n")
67-
{:request-method request-method
68-
:path path
69-
:method method
70-
:uri uri})))
82+
(render [_ request]
83+
(handler request))
84+
85+
;; Sendable implementation in compojure.api.async
7186

7287
IFn
7388
(invoke [_ request]
7489
(handler request))
90+
(invoke [_ request respond raise]
91+
(handler request respond raise))
92+
7593
(applyTo [this args]
7694
(AFn/applyToHelper this args)))
7795

7896
(defn create [path method info childs handler]
7997
(->Route path method info childs handler))
8098

99+
(defmethod print-method Route
100+
[this ^Writer w]
101+
(let [childs (some-> this realize-childs filter-childs :childs seq vec)]
102+
(.write w (str "#Route"
103+
(cond-> (dissoc this :handler :childs)
104+
(not (:path this)) (dissoc :path)
105+
(not (seq (:info this))) (dissoc :info)
106+
(not (:method this)) (dissoc :method)
107+
childs (assoc :childs childs))))))
108+
81109
;;
82110
;; Invalid route handlers
83111
;;
@@ -117,11 +145,16 @@
117145
{:paths
118146
(reduce
119147
(fn [acc [path method info]]
120-
(update-in
121-
acc [path method]
122-
(fn [old-info]
123-
(let [info (or old-info info)]
124-
(ensure-path-parameters path info)))))
148+
(if-not (:no-doc info)
149+
(if-let [public-info (->> (get info :public {})
150+
(coercion/get-apidocs (:coercion info) "swagger"))]
151+
(update-in
152+
acc [path method]
153+
(fn [old-info]
154+
(let [public-info (or old-info public-info)]
155+
(ensure-path-parameters path public-info))))
156+
acc)
157+
acc))
125158
(linked/map)
126159
routes)})
127160

@@ -133,8 +166,19 @@
133166
(for [[id freq] (frequencies seq)
134167
:when (> freq 1)] id))
135168

169+
(defn all-paths [routes]
170+
(reduce
171+
(fn [acc [path method info]]
172+
(let [public-info (get info :public {})]
173+
(update-in acc [path method]
174+
(fn [old-info]
175+
(let [public-info (or old-info public-info)]
176+
(ensure-path-parameters path public-info))))))
177+
(linked/map)
178+
routes))
179+
136180
(defn route-lookup-table [routes]
137-
(let [entries (for [[path endpoints] (-> routes ring-swagger-paths :paths)
181+
(let [entries (for [[path endpoints] (all-paths routes)
138182
[method {:keys [x-name parameters]}] endpoints
139183
:let [params (:path parameters)]
140184
:when x-name]
@@ -155,12 +199,6 @@
155199
;; Endpoint Trasformers
156200
;;
157201

158-
(defn strip-no-doc-endpoints
159-
"Endpoint transformer, strips all endpoints that have :x-no-doc true."
160-
[endpoint]
161-
(if-not (some-> endpoint :x-no-doc true?)
162-
endpoint))
163-
164202
(defn non-nil-routes [endpoint]
165203
(or endpoint {}))
166204

@@ -171,7 +209,7 @@
171209
(defn- un-quote [s]
172210
(str/replace s #"^\"(.+(?=\"$))\"$" "$1"))
173211

174-
(defn- path-string [s params]
212+
(defn- path-string [m s params]
175213
(-> s
176214
(str/replace #":([^/]+)" " :$1 ")
177215
(str/split #" ")
@@ -181,7 +219,7 @@
181219
(let [key (keyword (subs token 1))
182220
value (key params)]
183221
(if value
184-
(un-quote (json/generate-string value))
222+
(un-quote (slurp (m/encode m "application/json" value)))
185223
(throw
186224
(IllegalArgumentException.
187225
(str "Missing path-parameter " key " for path " s)))))
@@ -191,14 +229,14 @@
191229
(defn path-for*
192230
"Extracts the lookup-table from request and finds a route by name."
193231
[route-name request & [params]]
194-
(let [[path details] (some-> request
195-
mw/get-options
196-
:lookup
232+
(let [m (or (::request/muuntaja request) json/muuntaja)
233+
[path details] (some-> request
234+
::request/lookup
197235
route-name
198236
first)
199237
path-params (:params details)]
200238
(if (seq path-params)
201-
(path-string path params)
239+
(path-string m path params)
202240
path)))
203241

204242
(defmacro path-for

‎src/compojure/api/swagger.clj

+16-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
(ns compojure.api.swagger
22
(:require [compojure.api.core :as c]
3-
[compojure.api.common :as common]
43
[compojure.api.middleware :as mw]
4+
[compojure.api.request :as request]
55
[ring.util.http-response :refer [ok]]
66
[ring.swagger.common :as rsc]
77
[ring.swagger.middleware :as rsm]
88
[ring.swagger.core :as swagger]
99
[ring.swagger.swagger-ui :as swagger-ui]
1010
[ring.swagger.swagger2 :as swagger2]
11-
[compojure.api.routes :as routes]))
11+
[compojure.api.routes :as routes]
12+
[spec-tools.swagger.core]))
1213

1314
(defn base-path [request]
1415
(let [context (swagger/context request)]
@@ -24,9 +25,7 @@
2425
first))
2526

2627
(defn transform-operations [swagger]
27-
(->> swagger
28-
(swagger2/transform-operations routes/non-nil-routes)
29-
(swagger2/transform-operations routes/strip-no-doc-endpoints)))
28+
(swagger2/transform-operations routes/non-nil-routes swagger))
3029

3130
(defn swagger-ui [options]
3231
(assert (map? options) "Since 1.1.11, compojure.api.swagger/swagger-ui takes just one map as argument, with `:path` for the path.")
@@ -39,12 +38,13 @@
3938
(c/GET path request
4039
:no-doc true
4140
:name ::swagger
42-
(let [runtime-info (rsm/get-swagger-data request)
41+
(let [runtime-info1 (mw/get-swagger-data request)
42+
runtime-info2 (rsm/get-swagger-data request)
4343
base-path {:basePath (base-path request)}
44-
options (:ring-swagger (mw/get-options request))
45-
paths (:paths (mw/get-options request))
46-
swagger (apply rsc/deep-merge (keep identity [base-path paths extra-info runtime-info]))
47-
spec (swagger2/swagger-json swagger options)]
44+
options (::request/ring-swagger request)
45+
paths (::request/paths request)
46+
swagger (apply rsc/deep-merge (keep identity [base-path paths extra-info runtime-info1 runtime-info2]))
47+
spec (spec-tools.swagger.core/swagger-spec (swagger2/swagger-json swagger options))]
4848
(ok spec)))))
4949

5050
;;
@@ -56,15 +56,21 @@
5656
(defn swagger-routes
5757
"Returns routes for swagger-articats (ui & spec). Accepts an options map, with the
5858
following options:
59+
5960
**:ui** Path for the swagger-ui (defaults to \"/\").
6061
Setting the value to nil will cause the swagger-ui not to be mounted
62+
6163
**:spec** Path for the swagger-spec (defaults to \"/swagger.json\")
6264
Setting the value to nil will cause the swagger-ui not to be mounted
65+
6366
**:data** Swagger data in the Ring-Swagger format.
67+
6468
**:options**
6569
**:ui** Options to configure the ui
6670
**:spec** Options to configure the spec. Nada at the moment.
71+
6772
Example options:
73+
6874
{:ui \"/api-docs\"
6975
:spec \"/swagger.json\"
7076
:options {:ui {:jsonEditor true}

‎src/compojure/api/sweet.clj

+6-6
Large diffs are not rendered by default.

‎src/compojure/api/upload.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
;; NOTE: This namespace is generated by compojure.api.dev.gen
22
(ns compojure.api.upload (:require ring.middleware.multipart-params ring.swagger.upload))
3-
(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)
3+
(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)
44
(def ^{:doc "Schema for file param created by ring.middleware.multipart-params.temp-file store."} TempFileUpload ring.swagger.upload/TempFileUpload)
55
(def ^{:doc "Schema for file param created by ring.middleware.multipart-params.byte-array store."} ByteArrayUpload ring.swagger.upload/ByteArrayUpload)

0 commit comments

Comments
 (0)
Please sign in to comment.