Skip to content

Commit

Permalink
squashed
Browse files Browse the repository at this point in the history
  • Loading branch information
frenchy64 committed Jun 17, 2024
1 parent a8cf4d6 commit afc1a93
Show file tree
Hide file tree
Showing 12 changed files with 1,511 additions and 455 deletions.
59 changes: 42 additions & 17 deletions src/compojure/api/api.clj
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
(ns compojure.api.api
(:require [compojure.api.core :as c]
[compojure.api.swagger :as swagger]
[compojure.api.middleware :as middleware]
[compojure.api.middleware :as mw]
[compojure.api.request :as request]
[compojure.api.routes :as routes]
[compojure.api.common :as common]
[compojure.api.coerce :as coerce]
[compojure.api.request :as request]
[ring.swagger.common :as rsc]
[ring.swagger.middleware :as rsm]))

(def api-defaults
(merge
middleware/api-middleware-defaults
mw/api-middleware-defaults
{:api {:invalid-routes-fn routes/log-invalid-child-routes
:disable-api-middleware? false}
:swagger {:ui nil, :spec nil}}))
Expand All @@ -23,8 +24,7 @@
options map as the first parameter:
(api
{:formats [:json-kw :edn :transit-msgpack :transit-json]
:exceptions {:handlers {:compojure.api.exception/default my-logging-handler}}
{:exceptions {:handlers {:compojure.api.exception/default my-logging-handler}}
:api {:invalid-routes-fn (constantly nil)}
:swagger {:spec \"/swagger.json\"
:ui \"/api-docs\"
Expand All @@ -47,30 +47,55 @@
### api-middleware options
See `compojure.api.middleware/api-middleware` for more available options.
" (:doc (meta #'compojure.api.middleware/api-middleware)))}
api
[& body]
(let [[options handlers] (common/extract-parameters body false)
_ (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"))
_ (when (and (not (:formatter options))
(not (contains? options :formats))
(not (System/getProperty "compojure.api.middleware.global-default-formatter")))
(throw (ex-info (str "ERROR: Please set `:formatter :muuntaja` in the options map of `api`.\n"
"e.g., (api {:formatter :muuntaja} routes...)\n"
"To prepare for backwards compatibility with compojure-api 1.x, the formatting library must be\n"
"explicitly chosen if not configured by `:format` (ring-middleware-format) or \n"
"`:formats` (muuntaja). Once 2.x is stable, the default will be `:formatter :ring-middleware-format`.\n"
"To globally override the formatter, use -Dcompojure.api.middleware.global-default-formatter=:muuntaja")
{})))
options (rsc/deep-merge api-defaults options)
handler (apply c/routes (concat [(swagger/swagger-routes (:swagger options))] handlers))
routes (routes/get-routes handler (:api options))
partial-api-route (routes/map->Route
{:childs [handler]
:info {:coercion (:coercion options)}})
routes (routes/get-routes partial-api-route (:api options))
paths (-> routes routes/ring-swagger-paths swagger/transform-operations)
lookup (routes/route-lookup-table routes)
swagger-data (get-in options [:swagger :data])
enable-api-middleware? (not (get-in options [:api :disable-api-middleware?]))
api-handler (cond-> handler
swagger-data (rsm/wrap-swagger-data swagger-data)
enable-api-middleware? (middleware/api-middleware
(dissoc options :api :swagger))
true (middleware/wrap-options
{:paths paths
:coercer (coerce/memoized-coercer)
:lookup lookup}))]
(routes/create nil nil {} [handler] api-handler)))
api-middleware-options (dissoc (mw/api-middleware-options (assoc (dissoc options :api :swagger) ::via-api true))
::mw/api-middleware-defaults)
api-handler (-> handler
(cond-> swagger-data (rsm/wrap-swagger-data swagger-data))
(cond-> enable-api-middleware? (mw/api-middleware
api-middleware-options))
(mw/wrap-inject-data
{::request/paths paths
::request/lookup lookup}))]
(assoc partial-api-route :handler api-handler)))

(defmacro
^{:doc (str
"Defines an api.
^{:superseded-by "api"
:deprecated "2.0.0"
:doc (str
"Deprecated: please use (def name (api ...body..))
Defines an api.
API middleware options:
Expand Down
1 change: 1 addition & 0 deletions src/compojure/api/coerce.clj
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
;; 1.1.x
(ns compojure.api.coerce
(:require [schema.coerce :as sc]
[compojure.api.middleware :as mw]
Expand Down
24 changes: 12 additions & 12 deletions src/compojure/api/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,16 @@
:deprecated "1.1.14"
:superseded-by "route-middleware"}
[middleware & body]
(when (not= "true" (System/getProperty "compojure.api.core.suppress-middleware-warning"))
(println (str "compojure.api.core.middleware is deprecated because of security issues. "
"Please use route-middleware instead. middleware will be disabled in a future release."
"Set -dcompojure.api.core.suppress-middleware-warning=true to suppress this warning.")))
(assert (= "true" (System/getProperty "compojure.api.core.allow-dangerous-middleware"))
(str "compojure.api.core.middleware is deprecated because of security issues. "
"Please use route-middleware instead. "
"Set compojure.api.core.allow-dangerous-middleware=true to keep using middleware."))
`(let [body# (routes ~@body)
wrap-mw# (mw/compose-middleware ~middleware)]
(routes/create nil nil {} [body#] (wrap-mw# body#))))

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

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

(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args nil))
(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args nil))
(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args nil))
(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args nil))
(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args nil))
(defmacro GET {:style/indent 2} [& args] (meta/restructure :get args nil))
(defmacro ANY {:style/indent 2} [& args] (meta/restructure nil args nil))
(defmacro HEAD {:style/indent 2} [& args] (meta/restructure :head args nil))
(defmacro PATCH {:style/indent 2} [& args] (meta/restructure :patch args nil))
(defmacro DELETE {:style/indent 2} [& args] (meta/restructure :delete args nil))
(defmacro OPTIONS {:style/indent 2} [& args] (meta/restructure :options args nil))
(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args nil))
(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args nil))
(defmacro POST {:style/indent 2} [& args] (meta/restructure :post args nil))
(defmacro PUT {:style/indent 2} [& args] (meta/restructure :put args nil))
79 changes: 49 additions & 30 deletions src/compojure/api/exception.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
(:require [ring.util.http-response :as response]
[clojure.walk :as walk]
[compojure.api.impl.logging :as logging]
[schema.utils :as su])
(:import [schema.utils ValidationError NamedError]
[com.fasterxml.jackson.core JsonParseException]
[org.yaml.snakeyaml.parser ParserException]))
[compojure.api.coercion.core :as cc]
[compojure.api.coercion.schema]))

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

(defn stringify-error
"Stringifies symbols and validation errors in Schema error, keeping the structure intact."
[error]
(walk/postwalk
(fn [x]
(cond
(instance? ValidationError x) (str (su/validation-error-explain x))
(instance? NamedError x) (str (su/named-error-explain x))
:else x))
error))

;; TODO: coercion should handle how to publish data
(defn response-validation-handler
"Creates error response based on Schema error."
"Creates error response based on a response error. The following keys are available:
:type type of the exception (::response-validation)
:coercion coercion instance used
:in location of the value ([:response :body])
:schema schema to be validated against
:error schema error
:request raw request
:response raw response"
[e data req]
(response/internal-server-error {:errors (stringify-error (su/error-val data))}))
(response/internal-server-error
(-> data
(dissoc :request :response)
(update :coercion cc/get-name)
(assoc :value (-> data :response :body))
(->> (cc/encode-error (:coercion data))))))

;; TODO: coercion should handle how to publish data
(defn request-validation-handler
"Creates error response based on Schema error."
"Creates error response based on Schema error. The following keys are available:
:type type of the exception (::request-validation)
:coercion coercion instance used
:value value that was validated
:in location of the value (e.g. [:request :query-params])
:schema schema to be validated against
:error schema error
:request raw request"
[e data req]
(response/bad-request {:errors (stringify-error (su/error-val data))}))
(response/bad-request
(-> data
(dissoc :request)
(update :coercion cc/get-name)
(->> (cc/encode-error (:coercion 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]
; FIXME: Why error is not wrapped to ErrorContainer here?
(response/bad-request {:errors (stringify-error (:error data))}))
(response/bad-request
{:errors (compojure.api.coercion.schema/stringify (:error data))}))

(defn request-parsing-handler
[^Exception ex data req]
(let [cause (.getCause ex)]
(response/bad-request {:type (cond
(instance? JsonParseException cause) "json-parse-exception"
(instance? ParserException cause) "yaml-parse-exception"
:else "parse-exception")
:message (.getMessage cause)})))

(let [cause (.getCause ex)
original (.getCause cause)]
(response/bad-request
(merge (select-keys data [:type :format :charset])
(if original {:original (.getMessage original)})
{:message (.getMessage cause)}))))
;;
;; Logging
;;
Expand All @@ -75,5 +93,6 @@
;; Mappings from other Exception types to our base types
;;

(def legacy-exception-types
{:ring.swagger.schema/validation ::request-validation})
(def mapped-exception-types
{:ring.swagger.schema/validation ::request-validation
:muuntaja/decode ::request-parsing})
21 changes: 11 additions & 10 deletions src/compojure/api/impl/logging.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@
(declare log!)

;; use c.t.l logging if available, default to console logging
(if (find-ns 'clojure.tools.logging)
(try
(eval
`(do
(require 'clojure.tools.logging)
(defmacro ~'log! [& ~'args]
`(do
(clojure.tools.logging/log ~@~'args)))))
(let [log (fn [level more] (println (.toUpperCase (name level)) (str/join " " more)))]
(defn log! [level x & more]
(if (instance? Throwable x)
(do
(log level more)
(.printStackTrace ^Throwable x))
(log level (into [x] more))))))
`(clojure.tools.logging/log ~@~'args))))
(catch Exception _
(let [log (fn [level more] (println (.toUpperCase (name level)) (str/join " " more)))]
(defn log! [level x & more]
(if (instance? Throwable x)
(do
(log level more)
(.printStackTrace ^Throwable x))
(log level (into [x] more))))
(log! :warn "clojure.tools.logging not found on classpath, compojure.api logging to console."))))
Loading

0 comments on commit afc1a93

Please sign in to comment.