Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: compojure1 compat #461

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
test:
strategy:
matrix:
jdk: [8, 11, 17, 21]
jdk: [8, 11, 17, 21, 22]

name: Java ${{ matrix.jdk }}

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
See also: [compojure-api 1.1.x changelog](./CHANGELOG-1.1.x.md)

## Next
* 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))

## 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
Expand Down
8 changes: 5 additions & 3 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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"]
Expand All @@ -61,6 +60,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]
Expand All @@ -86,7 +88,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"]
Expand Down
67 changes: 67 additions & 0 deletions src/compojure/api/coerce.clj
Original file line number Diff line number Diff line change
@@ -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)))
5 changes: 3 additions & 2 deletions src/compojure/api/coercion.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions src/compojure/api/coercion/register_schema.clj
Original file line number Diff line number Diff line change
@@ -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)))))
8 changes: 8 additions & 0 deletions src/compojure/api/coercion/register_spec.clj
Original file line number Diff line number Diff line change
@@ -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)))))
6 changes: 3 additions & 3 deletions src/compojure/api/coercion/schema.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down Expand Up @@ -84,5 +86,3 @@
(->SchemaCoercion :schema options))

(def default-coercion (create-coercion default-options))

(defmethod cc/named-coercion :schema [_] default-coercion)
6 changes: 3 additions & 3 deletions src/compojure/api/coercion/spec.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -149,5 +151,3 @@
(->SpecCoercion :spec options))

(def default-coercion (create-coercion default-options))

(defmethod cc/named-coercion :spec [_] default-coercion)
21 changes: 21 additions & 0 deletions src/compojure/api/middleware.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand All @@ -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
;;
Expand Down
1 change: 1 addition & 0 deletions test-suites/compojure1/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
Empty file.
93 changes: 93 additions & 0 deletions test-suites/compojure1/project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
(defproject metosin/compojure-api "1.1.14-SNAPSHOT"
:description "Compojure Api"
:url "https://github.com/metosin/compojure-api"
:license {:name "Eclipse Public License"
: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"}
:source-paths ["../../src"]
: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" ]
[org.clojure/core.memoize "0.8.2"]
[clj-commons/clj-yaml "0.7.0"]
[org.yaml/snakeyaml "1.24"]
[ring-middleware-format "0.7.4"]
[metosin/spec-tools "0.10.0"]
[metosin/ring-http-response "0.9.1"]
[metosin/ring-swagger-ui "3.24.3"]
[metosin/ring-swagger "0.26.2"]

;; Fix dependency conflicts
[clj-time "0.15.2"]
[joda-time "2.10.5"]
[riddley "0.2.0"]]
:profiles {:uberjar {:aot :all
:ring {:handler examples.thingie/app}
:source-paths ["examples/thingie/src"]
:dependencies [[org.clojure/clojure "1.10.1"]
[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"]
[lein-midje "3.2.1"]
[lein-ring "0.12.0"]
[funcool/codeina "0.5.0"]]
:dependencies [[org.clojure/clojure "1.10.1"]
[slingshot "0.12.2"]
[peridot "0.5.1"]
[javax.servlet/servlet-api "2.5"]
[midje "1.9.9"]
[com.stuartsierra/component "0.4.0"]
[reloaded.repl "0.2.4"]
[http-kit "2.3.0"]
[criterium "0.4.5"]]
:ring {:handler examples.thingie/app
:reload-paths ["src" "examples/thingie/src"]}
:source-paths ["examples/thingie/src" "examples/thingie/dev-src"]
:main examples.server}
:perf {:jvm-opts ^:replace ["-server"
"-Xmx4096m"
"-Dclojure.compiler.direct-linking=true"]}
:logging {:dependencies [[org.clojure/tools.logging "0.5.0"]]}
:1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}
:1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]}
:1.10 {:dependencies [[org.clojure/clojure "1.10.1"]]}}
:eastwood {:namespaces [:source-paths]
:add-linters [:unused-namespaces]}
:codeina {:sources ["src"]
:target "gh-pages/doc"
:src-uri "http://github.com/metosin/compojure-api/blob/master/"
:src-uri-prefix "#L"}
:deploy-repositories [["snapshot" {:url "https://clojars.org/repo"
:username [:gpg :env/clojars_user]
:password [:gpg :env/clojars_token]
:sign-releases false}]
["releases" {:url "https://clojars.org/repo"
:username [:gpg :env/clojars_user]
:password [:gpg :env/clojars_token]
:sign-releases false}]]
:release-tasks [["clean"]
["vcs" "assert-committed"]
["change" "version" "leiningen.release/bump-version" "release"]
["vcs" "commit"]
["vcs" "tag" "--no-sign"]
["deploy" "release"]
["change" "version" "leiningen.release/bump-version"]
["vcs" "commit"]
["vcs" "push"]]
:aliases {"all" ["with-profile" "dev:dev,logging:dev,1.10"]
"start-thingie" ["run"]
"aot-uberjar" ["with-profile" "uberjar" "do" "clean," "ring" "uberjar"]
"test-ancient" ["midje"]
"perf" ["with-profile" "default,dev,perf"]
"deploy!" ^{:doc "Recompile sources, then deploy if tests succeed."}
["do" ["clean"] ["midje"] ["deploy" "clojars"]]})
Loading
Loading