(ns schema.spec.variant (:require #?(:clj [schema.macros :as macros]) [schema.utils :as utils] [schema.spec.core :as spec]) #?(:cljs (:require-macros [schema.macros :as macros]))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Variant Specs (defn- option-step [o params else] (let [g (:guard o) c (spec/sub-checker o params) step (if g (fn [x] (let [guard-result (macros/try-catchall (g x) (catch e# ::exception))] (cond (= ::exception guard-result) (macros/validation-error (:schema o) x (list (symbol (utils/fn-name g)) (utils/value-name x)) 'throws?) guard-result (c x) :else (else x)))) c)] (if-let [wrap-error (:wrap-error o)] (fn [x] (let [res (step x)] (if-let [e (utils/error-val res)] (utils/error (wrap-error e)) res))) step))) (defrecord VariantSpec [pre options err-f post] spec/CoreSpec (subschemas [this] (map :schema options)) (checker [this params] (let [t (reduce (fn [f o] (option-step o params f)) (fn [x] (macros/validation-error this x (err-f (utils/value-name x)))) (reverse options))] (if post (fn [x] (or (pre x) (let [v (t x)] (if (utils/error? v) v (or (post (if (:return-walked? params) v x)) v))))) (fn [x] (or (pre x) (t x))))))) (defn variant-spec "A variant spec represents a choice between a set of alternative subschemas, e.g., a tagged union. It has an overall precondition, set of options, and error function. The semantics of `options` is that the options are processed in order. During checking, the datum must match the schema for the first option for which `guard` passes. During generation, any datum generated from an option will pass the corresponding `guard`. err-f is a function to produce an error message if none of the guards match (and must be passed unless the last option has no guard)." ([pre options] (variant-spec pre options nil)) ([pre options err-f] (variant-spec pre options err-f nil)) ([pre ;- spec/Precondition options ;- [{:schema (s/protocol Schema) ;; (s/optional-key :guard) (s/pred fn?) ;; (s/optional-key :error-wrap) (s/pred fn?)}] err-f ;- (s/pred fn?) post ;- (s/maybe spec/Precondition) ] (macros/assert! (or err-f (nil? (:guard (last options)))) "when last option has a guard, err-f must be provided") (->VariantSpec pre options err-f post)))