-
Notifications
You must be signed in to change notification settings - Fork 256
/
complete.clj
102 lines (91 loc) · 3.65 KB
/
complete.clj
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
(ns schema.experimental.complete
"(Extremely) experimental support for 'completing' partial datums to match
a schema. To use it, you must provide your own test.check dependency."
{:deprecated "1.1.0"}
(:require
[clojure.test.check.generators :as check-generators]
[schema.spec.core :as spec]
schema.spec.collection
schema.spec.leaf
schema.spec.variant
[schema.coerce :as coerce]
[schema.core :as s]
[schema.macros :as macros]
[schema.utils :as utils]
[schema.experimental.generators :as generators]))
(def +missing+ ::missing)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Private helpers
(defprotocol Completer
(completer* [spec s sub-checker generator-opts]
"A function applied to a datum as part of coercion to complete missing fields."))
(defn sample [g]
(check-generators/generate g 10))
(extend-protocol Completer
schema.spec.variant.VariantSpec
(completer* [spec s sub-checker generator-opts]
(let [g (apply generators/generator s generator-opts)]
(if (and (class? s) (isa? s clojure.lang.IRecord) (utils/class-schema s))
(fn record-completer [x]
(sub-checker (into (sample g) x)))
(fn variant-completer [x]
(if (= +missing+ x)
(sample g)
(sub-checker x))))))
schema.spec.collection.CollectionSpec
(completer* [spec s sub-checker generator-opts]
(if (instance? clojure.lang.APersistentMap s) ;; todo: pluggable
(let [g (apply generators/generator s generator-opts)]
(fn map-completer [x]
(if (= +missing+ x)
(sample g)
;; for now, just do required keys when user provides input.
(let [ks (distinct (concat (keys x)
(->> s
keys
(filter s/required-key?)
(map s/explicit-schema-key))))]
(sub-checker
(into {} (for [k ks] [k (get x k +missing+)])))))))
(let [g (apply generators/generator s generator-opts)]
(fn coll-completer [x]
(if (= +missing+ x)
(sample g)
(sub-checker x))))))
schema.spec.leaf.LeafSpec
(completer* [spec s sub-checker generator-opts]
(let [g (apply generators/generator s generator-opts)]
(fn leaf-completer [x]
(if (= +missing+ x)
(sample g)
x)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Public
(s/defn completer
"Produce a function that simultaneously coerces, completes, and validates a datum."
([schema] (completer schema {}))
([schema coercion-matcher] (completer schema coercion-matcher {}))
([schema coercion-matcher leaf-generators]
(completer schema coercion-matcher leaf-generators {}))
([schema
coercion-matcher :- coerce/CoercionMatcher
leaf-generators :- generators/LeafGenerators
wrappers :- generators/GeneratorWrappers]
(spec/run-checker
(fn [s params]
(let [c (spec/checker (s/spec s) params)
coercer (or (coercion-matcher s) identity)
completr (completer* (s/spec s) s c [leaf-generators wrappers])]
(fn [x]
(macros/try-catchall
(let [v (coercer x)]
(if (utils/error? v)
v
(completr v)))
(catch t (macros/validation-error s x t))))))
true
schema)))
(defn complete
"Fill in partial-datum to make it validate schema."
[partial-datum & completer-args]
((apply completer completer-args) partial-datum))