-
Notifications
You must be signed in to change notification settings - Fork 250
/
spec.cljc
153 lines (134 loc) · 5.12 KB
/
spec.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
(ns reitit.coercion.spec
(:require [clojure.set :as set]
[clojure.spec.alpha :as s]
[meta-merge.core :as mm]
[reitit.coercion :as coercion]
[reitit.exception :as ex]
[spec-tools.core :as st #?@(:cljs [:refer [Spec]])]
[spec-tools.data-spec :as ds #?@(:cljs [:refer [Maybe]])]
[spec-tools.openapi.core :as openapi]
[spec-tools.swagger.core :as swagger])
#?(:clj
(:import (spec_tools.core Spec)
(spec_tools.data_spec Maybe))))
(def string-transformer
(st/type-transformer
st/strip-extra-keys-transformer
st/string-transformer))
(def json-transformer
(st/type-transformer
st/strip-extra-keys-transformer
st/json-transformer))
(def strip-extra-keys-transformer
st/strip-extra-keys-transformer)
(def no-op-transformer
(reify
st/Transformer
(-name [_] ::no-op)
(-encoder [_ _ _])
(-decoder [_ _ _])))
(defprotocol IntoSpec
(into-spec [this name]))
(defn- ensure-name [?name]
(or ?name (keyword "spec" (name (gensym "")))))
(extend-protocol IntoSpec
#?(:clj clojure.lang.PersistentArrayMap
:cljs cljs.core.PersistentArrayMap)
(into-spec [this name]
(dissoc (ds/spec (ensure-name name) this) :name))
#?(:clj clojure.lang.PersistentHashMap
:cljs cljs.core.PersistentHashMap)
(into-spec [this name]
(dissoc (ds/spec (ensure-name name) this) :name))
#?(:clj clojure.lang.PersistentVector
:cljs cljs.core.PersistentVector)
(into-spec [this name]
(dissoc (ds/spec (ensure-name name) this) :name))
Maybe
(into-spec [this name]
(ds/spec (ensure-name name) this))
Spec
(into-spec [this _] this)
#?(:clj Object
:cljs default)
(into-spec [this _]
(st/create-spec {:spec this}))
nil
(into-spec [_ _]))
(defn stringify-pred [pred]
(str (if (seq? pred) (seq pred) pred)))
(defmulti coerce-response? identity :default ::default)
(defmethod coerce-response? ::default [_] true)
(def default-options
{:coerce-response? coerce-response?
:transformers {:body {:default strip-extra-keys-transformer
:formats {"application/json" json-transformer}}
:string {:default string-transformer}
:response {:default no-op-transformer}}})
(defn create [{:keys [transformers coerce-response?] :as opts}]
^{:type ::coercion/coercion}
(reify coercion/Coercion
(-get-name [_] :spec)
(-get-options [_] opts)
(-get-model-apidocs [_ specification model options]
(case specification
:openapi (openapi/transform model (merge opts options))
(throw
(ex-info
(str "Can't produce Spec apidocs for " specification)
{:type specification, :coercion :spec}))))
(-get-apidocs [_ specification {:keys [request parameters responses content-types]
:or {content-types ["application/json"]}}]
(case specification
:swagger (swagger/swagger-spec
(merge
(if parameters
{::swagger/parameters parameters})
(if responses
{::swagger/responses
(into
(empty responses)
(for [[k response] responses]
[k (as-> response $
(dissoc $ :content)
(set/rename-keys $ {:body :schema}))]))})))
;; :openapi handled in reitit.openapi/-get-apidocs-openapi
(throw
(ex-info
(str "Can't produce Spec apidocs for " specification)
{:specification specification, :coercion :spec}))))
(-compile-model [_ model name]
(into-spec
(cond
;; we are safe!
(= (count model) 1) (first model)
;; here be dragons, best effort
(every? map? model) (apply mm/meta-merge model)
;; fail fast
:else (ex/fail! ::model-error {:message "Can't merge nested clojure specs", :spec model}))
name))
(-open-model [_ spec] spec)
(-encode-error [_ error]
(let [problems (-> error :problems ::s/problems)]
(-> error
(update :spec (comp str s/form))
(assoc :problems (mapv #(update % :pred stringify-pred) problems)))))
(-request-coercer [_ type spec]
(let [{:keys [formats default]} (transformers type)]
(fn [value format]
(if-let [transformer (or (get formats format) default)]
(let [coerced (st/coerce spec value transformer)]
(if (s/valid? spec coerced)
coerced
(let [transformed (st/conform spec coerced transformer)]
(if (s/invalid? transformed)
(let [problems (st/explain-data spec coerced transformer)]
(coercion/map->CoercionError
{:spec spec
:problems problems}))
(s/unform spec transformed)))))
value))))
(-response-coercer [this spec]
(if (coerce-response? spec)
(coercion/-request-coercer this :response spec)))))
(def coercion (create default-options))