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
Changes from 12 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
@@ -14,7 +14,7 @@ jobs:
test:
strategy:
matrix:
jdk: [8, 11, 17, 21]
jdk: [8, 11, 17, 21, 22]

name: Java ${{ matrix.jdk }}

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
12 changes: 5 additions & 7 deletions project.clj
Original file line number Diff line number Diff line change
@@ -12,11 +12,7 @@
[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/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"]
@@ -41,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"]
@@ -65,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]
@@ -90,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"]
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
@@ -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)
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
@@ -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)))
@@ -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
@@ -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)
@@ -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
@@ -13,6 +13,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]
@@ -103,6 +104,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]
@@ -123,6 +130,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
;;
8 changes: 4 additions & 4 deletions test19/compojure/api/coercion/spec_coercion_test.clj
Original file line number Diff line number Diff line change
@@ -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"}