-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch '1.1.x' of github.com:metosin/compojure-api into 1.1.x
- Loading branch information
Showing
19 changed files
with
630 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
(ns compojure.api.async | ||
(:require [compojure.response :as response] | ||
[compojure.api.common :as common] | ||
compojure.api.routes)) | ||
|
||
(common/when-ns 'manifold.deferred | ||
;; Compojure is smart enough to get the success value out of deferred by | ||
;; itself, but we want to catch the exceptions as well. | ||
(extend-protocol compojure.response/Sendable | ||
manifold.deferred.IDeferred | ||
(send* [deferred request respond raise] | ||
(manifold.deferred/on-realized deferred #(response/send % request respond raise) raise)))) | ||
|
||
(common/when-ns 'clojure.core.async | ||
(extend-protocol compojure.response/Sendable | ||
clojure.core.async.impl.channels.ManyToManyChannel | ||
(send* [channel request respond raise] | ||
(clojure.core.async/go | ||
(let [message (clojure.core.async/<! channel)] | ||
(if (instance? Throwable message) | ||
(raise message) | ||
(response/send message request respond raise))))))) | ||
|
||
(extend-protocol compojure.response/Sendable | ||
compojure.api.routes.Route | ||
(send* [this request respond raise] | ||
((.handler this) request #(response/send % request respond raise) raise))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
(ns compojure.api.coercion | ||
(:require [clojure.walk :as walk] | ||
[compojure.api.exception :as ex] | ||
[compojure.api.request :as request] | ||
[compojure.api.coercion.core :as cc] | ||
;; side effects | ||
compojure.api.coercion.register-schema | ||
compojure.api.coercion.register-spec) | ||
(:import (compojure.api.coercion.core CoercionError))) | ||
|
||
(def default-coercion :schema) | ||
|
||
(defn set-request-coercion [request coercion] | ||
(assoc request ::request/coercion coercion)) | ||
|
||
(defn get-request-coercion [request] | ||
(if-let [entry (find request ::request/coercion)] | ||
(val entry) | ||
default-coercion)) | ||
|
||
(defn resolve-coercion [coercion] | ||
(cond | ||
(nil? coercion) nil | ||
(keyword? coercion) (cc/named-coercion coercion) | ||
(satisfies? cc/Coercion coercion) coercion | ||
:else (throw (ex-info (str "invalid coercion " coercion) {:coercion coercion})))) | ||
|
||
(defn get-apidocs [maybe-coercion spec info] | ||
(if-let [coercion (resolve-coercion maybe-coercion)] | ||
(cc/get-apidocs coercion spec info))) | ||
|
||
(defn coerce-request! [model in type keywordize? open? request] | ||
(let [transform (if keywordize? walk/keywordize-keys identity) | ||
value (transform (in request))] | ||
(if-let [coercion (-> request | ||
(get-request-coercion) | ||
(resolve-coercion))] | ||
(let [model (if open? (cc/make-open coercion model) model) | ||
format (some-> request :muuntaja/request :format) | ||
result (cc/coerce-request coercion model value type format request)] | ||
(if (instance? CoercionError result) | ||
(throw (ex-info | ||
(str "Request validation failed: " (pr-str result)) | ||
(merge | ||
(into {} result) | ||
{:type ::ex/request-validation | ||
:coercion coercion | ||
:value value | ||
:in [:request in] | ||
:request request}))) | ||
result)) | ||
value))) | ||
|
||
(defn coerce-response! [request {:keys [status body] :as response} responses] | ||
(if-let [model (or (:schema (get responses status)) | ||
(:schema (get responses :default)))] | ||
(if-let [coercion (-> request | ||
(get-request-coercion) | ||
(resolve-coercion))] | ||
(let [format (or (-> response :muuntaja/content-type) | ||
(some-> request :muuntaja/response :format)) | ||
accept? (cc/accept-response? coercion model)] | ||
(if accept? | ||
(let [result (cc/coerce-response coercion model body :response format response)] | ||
(if (instance? CoercionError result) | ||
(throw (ex-info | ||
(str "Response validation failed: " (pr-str result)) | ||
(merge | ||
(into {} result) | ||
{:type ::ex/response-validation | ||
:coercion coercion | ||
:value body | ||
:in [:response :body] | ||
:request request | ||
:response response}))) | ||
(assoc response | ||
:compojure.api.meta/serializable? true | ||
:body result))) | ||
response)) | ||
response) | ||
response)) | ||
|
||
;; | ||
;; middleware | ||
;; | ||
|
||
(defn wrap-coerce-response [handler responses] | ||
(fn | ||
([request] | ||
(coerce-response! request (handler request) responses)) | ||
([request respond raise] | ||
(handler | ||
request | ||
(fn [response] | ||
(try | ||
(respond (coerce-response! request response responses)) | ||
(catch Exception e | ||
(raise e)))) | ||
raise)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
(ns compojure.api.coercion.core) | ||
|
||
(defprotocol Coercion | ||
(get-name [this]) | ||
(get-apidocs [this model data]) | ||
(make-open [this model]) | ||
(encode-error [this error]) | ||
(coerce-request [this model value type format request]) | ||
(accept-response? [this model]) | ||
(coerce-response [this model value type format request])) | ||
|
||
(defrecord CoercionError []) | ||
|
||
(defmulti named-coercion identity :default ::default) | ||
|
||
(defmethod named-coercion ::default [x] | ||
(let [message (if (= :spec x) | ||
(str "spec-coercion is not enabled. " | ||
"you most likely are missing the " | ||
"required deps: org.clojure/clojure 1.9+ " | ||
"and metosin/spec-tools.") | ||
(str "cant find named-coercion for " x))] | ||
(throw (ex-info message {:name x})))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
(ns compojure.api.coercion.schema | ||
(:require [schema.coerce :as sc] | ||
[schema.utils :as su] | ||
[ring.swagger.coerce :as coerce] | ||
[compojure.api.coercion.core :as cc] | ||
[clojure.walk :as walk] | ||
[schema.core :as s] | ||
[compojure.api.common :as common] | ||
;; side effects | ||
compojure.api.coercion.register-schema) | ||
(:import (java.io File) | ||
(schema.core OptionalKey RequiredKey) | ||
(schema.utils ValidationError NamedError))) | ||
|
||
(def string-coercion-matcher coerce/query-schema-coercion-matcher) | ||
(def json-coercion-matcher coerce/json-schema-coercion-matcher) | ||
|
||
(defn stringify | ||
"Stringifies Schema records recursively." | ||
[error] | ||
(walk/prewalk | ||
(fn [x] | ||
(cond | ||
(class? x) (.getName ^Class x) | ||
(instance? OptionalKey x) (pr-str (list 'opt (:k x))) | ||
(instance? RequiredKey x) (pr-str (list 'req (:k x))) | ||
(and (satisfies? s/Schema x) (record? x)) (try (pr-str (s/explain x)) (catch Exception _ x)) | ||
(instance? ValidationError x) (str (su/validation-error-explain x)) | ||
(instance? NamedError x) (str (su/named-error-explain x)) | ||
:else x)) | ||
error)) | ||
|
||
(def memoized-coercer | ||
(common/fifo-memoize sc/coercer 1000)) | ||
|
||
;; don't use coercion for certain types | ||
(defmulti coerce-response? identity :default ::default) | ||
(defmethod coerce-response? ::default [_] true) | ||
(defmethod coerce-response? File [_] false) | ||
|
||
(defrecord SchemaCoercion [name options] | ||
cc/Coercion | ||
(get-name [_] name) | ||
|
||
(get-apidocs [_ _ data] data) | ||
|
||
(make-open [_ schema] | ||
(if (map? schema) | ||
(assoc schema s/Keyword s/Any) | ||
schema)) | ||
|
||
(encode-error [_ error] | ||
(-> error | ||
(update :schema pr-str) | ||
(update :errors stringify))) | ||
|
||
(coerce-request [_ schema value type format request] | ||
(let [type-options (options type)] | ||
(if-let [matcher (or (get (get type-options :formats) format) | ||
(get type-options :default))] | ||
(let [coerce (memoized-coercer schema matcher) | ||
coerced (coerce value)] | ||
(if (su/error? coerced) | ||
(let [errors (su/error-val coerced)] | ||
(cc/map->CoercionError | ||
{:schema schema | ||
:errors errors})) | ||
coerced)) | ||
value))) | ||
|
||
(accept-response? [_ model] | ||
(coerce-response? model)) | ||
|
||
(coerce-response [this schema value type format request] | ||
(cc/coerce-request this schema value type format request))) | ||
|
||
(def default-options | ||
{:body {:default (constantly nil) | ||
:formats {"application/json" json-coercion-matcher | ||
"application/msgpack" json-coercion-matcher | ||
"application/x-yaml" json-coercion-matcher}} | ||
:string {:default string-coercion-matcher} | ||
:response {:default (constantly nil)}}) | ||
|
||
(defn create-coercion [options] | ||
(->SchemaCoercion :schema options)) | ||
|
||
(def default-coercion (create-coercion default-options)) |
Oops, something went wrong.