From 8ca7e862499b26297b36daa792f7be1344afd75e Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Mon, 13 May 2024 15:20:53 -0500 Subject: [PATCH 01/12] meta fixes --- src/compojure/api/meta.clj | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/compojure/api/meta.clj b/src/compojure/api/meta.clj index 11e17fd0..4a2a4e38 100644 --- a/src/compojure/api/meta.clj +++ b/src/compojure/api/meta.clj @@ -693,6 +693,12 @@ (defn- route-args? [arg] (not= arg [])) +(defn- resolve-var [&env sym] + (when (symbol? sym) + (let [v (resolve &env sym)] + (when (var? v) + v)))) + (def endpoint-vars (into #{} (mapcat (fn [n] (map #(symbol (name %) (name n)) @@ -759,14 +765,11 @@ (defn- static-middleware? [&env body] (and (seq? body) (boolean - (let [sym (first body)] - (when (symbol? sym) - (when-some [v (resolve &env sym)] - (when (var? v) - (when (middleware-vars (var->sym v)) - (let [[_ path route-arg & args] body - [options body] (extract-parameters args true)] - (static-body? &env body)))))))))) + (when-some [v (resolve-var &env (first body))] + (when (middleware-vars (var->sym v)) + (let [[_ mid & body] body] + (and (static-form? &env mid) + (static-body? &env body)))))))) (def route-middleware-vars (into #{} (mapcat (fn [n] @@ -803,12 +806,6 @@ (= sym 'if)) (static-body? &env (next form))))))))) -(defn- resolve-var [&env sym] - (when (symbol? sym) - (let [v (resolve &env sym)] - (when (var? v) - v)))) - (defn- static-resolved-form? [&env form] (boolean (or (and (seq? form) @@ -1007,7 +1004,7 @@ (let [coach (some-> (System/getProperty "compojure.api.meta.static-context-coach") edn/read-string)] (if-not coach - (when (ffirst (reset-vals! warned-non-static? true)) + (when (first (reset-vals! warned-non-static? true)) (println (str (format "WARNING: Performance issue detected with compojure-api usage in %s.\n" (ns-name *ns*)) "To fix this warning, set: -Dcompojure.api.meta.static-context-coach={:default :print}.\n" From 965aff2df4342905c63950cceaad291ce639be36 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 13:27:59 -0500 Subject: [PATCH 02/12] add back c.api.coerce --- src/compojure/api/coerce.clj | 67 ++++++++++++++++++++++++++++++++ src/compojure/api/middleware.clj | 21 ++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/compojure/api/coerce.clj diff --git a/src/compojure/api/coerce.clj b/src/compojure/api/coerce.clj new file mode 100644 index 00000000..5a147a14 --- /dev/null +++ b/src/compojure/api/coerce.clj @@ -0,0 +1,67 @@ +;; 1.1.x +(ns compojure.api.coerce + (:require [schema.coerce :as sc] + [compojure.api.middleware :as mw] + [compojure.api.exception :as ex] + [clojure.walk :as walk] + [schema.utils :as su] + [linked.core :as linked])) + +(defn memoized-coercer + "Returns a memoized version of a referentially transparent coercer fn. The + memoized version of the function keeps a cache of the mapping from arguments + to results and, when calls with the same arguments are repeated often, has + higher performance at the expense of higher memory use. FIFO with 10000 entries. + Cache will be filled if anonymous coercers are used (does not match the cache)" + [] + (let [cache (atom (linked/map)) + cache-size 10000] + (fn [& args] + (or (@cache args) + (let [coercer (apply sc/coercer args)] + (swap! cache (fn [mem] + (let [mem (assoc mem args coercer)] + (if (>= (count mem) cache-size) + (dissoc mem (-> mem first first)) + mem)))) + coercer))))) + +(defn cached-coercer [request] + (or (-> request mw/get-options :coercer) sc/coercer)) + +(defn coerce-response! [request {:keys [status] :as response} responses] + (-> (when-let [schema (or (:schema (get responses status)) + (:schema (get responses :default)))] + (when-let [matchers (mw/coercion-matchers request)] + (when-let [matcher (matchers :response)] + (let [coercer (cached-coercer request) + coerce (coercer schema matcher) + body (coerce (:body response))] + (if (su/error? body) + (throw (ex-info + (str "Response validation failed: " (su/error-val body)) + (assoc body :type ::ex/response-validation + :response response))) + (assoc response + :compojure.api.meta/serializable? true + :body body)))))) + (or response))) + +(defn body-coercer-middleware [handler responses] + (fn [request] + (coerce-response! request (handler request) responses))) + +(defn coerce! [schema key type request] + (let [value (walk/keywordize-keys (key request))] + (if-let [matchers (mw/coercion-matchers request)] + (if-let [matcher (matchers type)] + (let [coercer (cached-coercer request) + coerce (coercer schema matcher) + result (coerce value)] + (if (su/error? result) + (throw (ex-info + (str "Request validation failed: " (su/error-val result)) + (assoc result :type ::ex/request-validation))) + result)) + value) + value))) diff --git a/src/compojure/api/middleware.clj b/src/compojure/api/middleware.clj index ebc603b2..3d6ba644 100644 --- a/src/compojure/api/middleware.clj +++ b/src/compojure/api/middleware.clj @@ -8,6 +8,7 @@ [ring.middleware.keyword-params :refer [wrap-keyword-params]] [ring.middleware.nested-params :refer [wrap-nested-params]] [ring.middleware.params :refer [wrap-params]] + [ring.swagger.coerce :as coerce] [muuntaja.middleware] [muuntaja.core :as m] @@ -88,6 +89,12 @@ ;; Options ;; +;; 1.1.x +(defn get-options + "Extracts compojure-api options from the request." + [request] + (::options request)) + (defn wrap-inject-data "Injects data into the request." [handler data] @@ -108,6 +115,20 @@ ([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 + :response coerce/json-schema-coercion-matcher}) + +;; 1.1.x +(defn coercion-matchers [request] + (let [options (get-options request)] + (if (contains? options :coercion) + (if-let [provider (:coercion options)] + (provider request)) + default-coercion-matchers))) + ;; ;; Muuntaja ;; From fb72503203741bc8757ebb147e3374b6aba05d6e Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 14:06:59 -0500 Subject: [PATCH 03/12] lazily load coercion extensions --- CHANGELOG.md | 3 +++ src/compojure/api/coercion.clj | 5 +++-- src/compojure/api/coercion/register_schema.clj | 8 ++++++++ src/compojure/api/coercion/register_spec.clj | 8 ++++++++ src/compojure/api/coercion/schema.clj | 2 -- src/compojure/api/coercion/spec.clj | 2 -- 6 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 src/compojure/api/coercion/register_schema.clj create mode 100644 src/compojure/api/coercion/register_spec.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index e93d4be7..4e275e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ See also: [compojure-api 1.1.x changelog](./CHANGELOG-1.1.x.md) +## Next +* Lazily load spec and schema coercion support to preserve Clojure 1.8 support for 1.1.x + ## 2.0.0-alpha34-SNAPSHOT * **BREAKING CHANGE**: `:formatter :muuntaja` sometimes required for `api{-middleware}` options * to prepare for 1.x compatibility, :muuntaja must be explicitly configured diff --git a/src/compojure/api/coercion.clj b/src/compojure/api/coercion.clj index a83a7082..0dd26b04 100644 --- a/src/compojure/api/coercion.clj +++ b/src/compojure/api/coercion.clj @@ -3,8 +3,9 @@ [compojure.api.exception :as ex] [compojure.api.request :as request] [compojure.api.coercion.core :as cc] - [compojure.api.coercion.schema] - [compojure.api.coercion.spec]) + ;; side effects + compojure.api.coercion.register-schema + compojure.api.coercion.register-spec) (:import (compojure.api.coercion.core CoercionError))) (def default-coercion :schema) diff --git a/src/compojure/api/coercion/register_schema.clj b/src/compojure/api/coercion/register_schema.clj new file mode 100644 index 00000000..e1e8f993 --- /dev/null +++ b/src/compojure/api/coercion/register_schema.clj @@ -0,0 +1,8 @@ +(ns compojure.api.coercion.register-schema + (:require [compojure.api.coercion.core :as cc])) + +(defmethod cc/named-coercion :schema [_] + (deref + (or (resolve 'compojure.api.coercion.schema/default-coercion) + (do (require 'compojure.api.coercion.schema) + (resolve 'compojure.api.coercion.schema/default-coercion))))) diff --git a/src/compojure/api/coercion/register_spec.clj b/src/compojure/api/coercion/register_spec.clj new file mode 100644 index 00000000..143320fb --- /dev/null +++ b/src/compojure/api/coercion/register_spec.clj @@ -0,0 +1,8 @@ +(ns compojure.api.coercion.register-spec + (:require [compojure.api.coercion.core :as cc])) + +(defmethod cc/named-coercion :spec [_] + (deref + (or (resolve 'compojure.api.coercion.spec/default-coercion) + (do (require 'compojure.api.coercion.spec) + (resolve 'compojure.api.coercion.spec/default-coercion))))) diff --git a/src/compojure/api/coercion/schema.clj b/src/compojure/api/coercion/schema.clj index b308d0c2..b310fc18 100644 --- a/src/compojure/api/coercion/schema.clj +++ b/src/compojure/api/coercion/schema.clj @@ -84,5 +84,3 @@ (->SchemaCoercion :schema options)) (def default-coercion (create-coercion default-options)) - -(defmethod cc/named-coercion :schema [_] default-coercion) diff --git a/src/compojure/api/coercion/spec.clj b/src/compojure/api/coercion/spec.clj index 9b20481a..ea8cf4b6 100644 --- a/src/compojure/api/coercion/spec.clj +++ b/src/compojure/api/coercion/spec.clj @@ -149,5 +149,3 @@ (->SpecCoercion :spec options)) (def default-coercion (create-coercion default-options)) - -(defmethod cc/named-coercion :spec [_] default-coercion) From 27aa29aa4c0cbe08b63aad9930b3d1e700802721 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 14:12:58 -0500 Subject: [PATCH 04/12] preserve side effect --- src/compojure/api/coercion/schema.clj | 4 +++- src/compojure/api/coercion/spec.clj | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/compojure/api/coercion/schema.clj b/src/compojure/api/coercion/schema.clj index b310fc18..9a7e01b0 100644 --- a/src/compojure/api/coercion/schema.clj +++ b/src/compojure/api/coercion/schema.clj @@ -5,7 +5,9 @@ [compojure.api.coercion.core :as cc] [clojure.walk :as walk] [schema.core :as s] - [compojure.api.common :as common]) + [compojure.api.common :as common] + ;; side effects + compojure.api.coercion.register-schema) (:import (java.io File) (schema.core OptionalKey RequiredKey) (schema.utils ValidationError NamedError))) diff --git a/src/compojure/api/coercion/spec.clj b/src/compojure/api/coercion/spec.clj index ea8cf4b6..b5d6ad31 100644 --- a/src/compojure/api/coercion/spec.clj +++ b/src/compojure/api/coercion/spec.clj @@ -6,7 +6,9 @@ [clojure.walk :as walk] [compojure.api.coercion.core :as cc] [spec-tools.swagger.core :as swagger] - [compojure.api.common :as common]) + [compojure.api.common :as common] + ;; side effects + compojure.api.coercion.register-spec) (:import (clojure.lang IPersistentMap) (schema.core RequiredKey OptionalKey) (spec_tools.core Spec) From 8e39651b01a762480169f0ef49278dc5b91ab47f Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 14:28:09 -0500 Subject: [PATCH 05/12] test future --- project.clj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/project.clj b/project.clj index e77fe64e..61bb8c15 100644 --- a/project.clj +++ b/project.clj @@ -61,6 +61,9 @@ [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"]]} :async {:jvm-opts ["-Dcompojure-api.test.async=true"] :dependencies [[manifold "0.1.8" :exclusions [org.clojure/tools.logging]]]}} :eastwood {:namespaces [:source-paths] @@ -86,7 +89,7 @@ ["change" "version" "leiningen.release/bump-version"] ["vcs" "commit"] ["vcs" "push"]] - :aliases {"all" ["with-profile" "dev:dev,async"] + :aliases {"all" ["with-profile" "dev:dev,async:dev,1.10:dev,1.11:dev,1.12"] "start-thingie" ["run"] "aot-uberjar" ["with-profile" "uberjar" "do" "clean," "ring" "uberjar"] "test-ancient" ["test"] From ef3e4d30807229a9fa2b165ea868580e1d8e46a2 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 14:37:13 -0500 Subject: [PATCH 06/12] fix pedantic --- project.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 61bb8c15..d8ed380e 100644 --- a/project.clj +++ b/project.clj @@ -63,7 +63,8 @@ [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"] + [org.clojure/spec.alpha "0.3.218"]]} :async {:jvm-opts ["-Dcompojure-api.test.async=true"] :dependencies [[manifold "0.1.8" :exclusions [org.clojure/tools.logging]]]}} :eastwood {:namespaces [:source-paths] From e455e6caa94252021a8f430599f05788f81c5450 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 14:38:12 -0500 Subject: [PATCH 07/12] +jdk22 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 946cce48..4665c66a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: test: strategy: matrix: - jdk: [8, 11, 17, 21] + jdk: [8, 11, 17, 21, 22] name: Java ${{ matrix.jdk }} From ec6d42d8cacba25c3b68ecb7b8383c3a04cf828e Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 14:46:18 -0500 Subject: [PATCH 08/12] bump spec-tools --- project.clj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/project.clj b/project.clj index d8ed380e..e08a7716 100644 --- a/project.clj +++ b/project.clj @@ -12,7 +12,7 @@ [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.0"] + [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"] @@ -37,7 +37,6 @@ [org.clojure/core.async "0.6.532"] [javax.servlet/javax.servlet-api "4.0.1"] [peridot "0.5.2"] - [com.rpl/specter "1.1.3"] [com.stuartsierra/component "0.4.0"] [expound "0.8.2"] [metosin/jsonista "0.2.5"] @@ -63,8 +62,7 @@ [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"] - [org.clojure/spec.alpha "0.3.218"]]} + :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] From f160d60885b8c5a66b90958d0b97e093ea51e03f Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 14:46:34 -0500 Subject: [PATCH 09/12] [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e275e5c..a0f4f818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ See also: [compojure-api 1.1.x changelog](./CHANGELOG-1.1.x.md) ## Next * Lazily load spec and schema coercion support to preserve Clojure 1.8 support for 1.1.x +* bump spec-tools to 0.10.6 ## 2.0.0-alpha34-SNAPSHOT * **BREAKING CHANGE**: `:formatter :muuntaja` sometimes required for `api{-middleware}` options From 6b5b7de5aec9a3bb186a56498d545cd84c4cc226 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 14:51:43 -0500 Subject: [PATCH 10/12] ci From 2577f7454608ddca22083bf742d69a960a12c674 Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 15:17:55 -0500 Subject: [PATCH 11/12] update for spec-tools 0.10.6 --- CHANGELOG.md | 1 + test19/compojure/api/coercion/spec_coercion_test.clj | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f4f818..9b6c78c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ See also: [compojure-api 1.1.x changelog](./CHANGELOG-1.1.x.md) ## Next * Lazily load spec and schema coercion support to preserve Clojure 1.8 support for 1.1.x * bump spec-tools to 0.10.6 + * notable changes: swagger `:name` defaults to `"body"` instead of `""` ([diff](https://github.com/metosin/spec-tools/compare/0.10.2...0.10.3)) ## 2.0.0-alpha34-SNAPSHOT * **BREAKING CHANGE**: `:formatter :muuntaja` sometimes required for `api{-middleware}` options diff --git a/test19/compojure/api/coercion/spec_coercion_test.clj b/test19/compojure/api/coercion/spec_coercion_test.clj index 53412eac..b19c0762 100644 --- a/test19/compojure/api/coercion/spec_coercion_test.clj +++ b/test19/compojure/api/coercion/spec_coercion_test.clj @@ -393,7 +393,7 @@ :responses {:default {:description ""}}}} "/body-map" {:post {:parameters [{:description "" :in "body" - :name "" + :name "body" :required true :schema {:properties {:x {:format "int64" :type "integer"} @@ -404,7 +404,7 @@ :responses {:default {:description ""}}}} "/body-params" {:post {:parameters [{:description "" :in "body" - :name "" + :name "body" :required true :schema {:properties {:x {:format "int64" :type "integer"} @@ -415,7 +415,7 @@ :responses {:default {:description ""}}}} "/body-string" {:post {:parameters [{:description "" :in "body" - :name "" + :name "body" :required true :schema {:type "string"}}] :responses {:default {:description ""}}}} @@ -476,7 +476,7 @@ :default {:description ""}}} :post {:parameters [{:description "" :in "body" - :name "" + :name "body" :required true :schema {:properties {:x {:format "int64" :type "integer"} From c1bd124e11b7513fe58b0de7f04cdb081836eb5e Mon Sep 17 00:00:00 2001 From: Ambrose Bonnaire-Sergeant Date: Tue, 14 May 2024 15:27:24 -0500 Subject: [PATCH 12/12] [skip ci] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b6c78c3..bfd87589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ See also: [compojure-api 1.1.x changelog](./CHANGELOG-1.1.x.md) ## Next -* Lazily load spec and schema coercion support to preserve Clojure 1.8 support for 1.1.x +* Lazily load spec and schema coercion * bump spec-tools to 0.10.6 * notable changes: swagger `:name` defaults to `"body"` instead of `""` ([diff](https://github.com/metosin/spec-tools/compare/0.10.2...0.10.3))