too hot, quick poke on instrumenting functions. moving functioin wrapping code from malli.generate
into malli.core
(just one fn) ->`m/-instrument`, can be used to enforce function schmas. First user will be malli.instrument
utilities, but could be used directly in client code too:
(def pow2
(m/-instrument
{:schema [:=> [:cat :int] [:int {:max 6}]]}
(fn [x] (* x x))))
(pow2 2)
; => 4
(pow2 "2")
; =throws=> :malli.core/invalid-input {:input [:cat :int], :args ["2"], :schema [:=> [:cat :int] [:int {:max 6}]]}
(pow2 4)
; =throws=> :malli.core/invalid-output {:output [:int {:max 6}], :value 16, :args [4], :schema [:=> [:cat :int] [:int {:max 6}]]}
(pow2 4 2)
; =throws=> :malli.core/invalid-arity {:arity 2, :arities #{{:min 1, :max 1}}, :args [4 2], :input [:cat :int], :schema [:=> [:cat :int] [:int {:max 6}]]}
like everything else, can be configured to behave differently. e.g. just printing out the errors (or to tap ’em):
(def multi-arity-pow
(m/-instrument
{:schema [:function
[:=> [:cat :int] [:int {:max 6}]]
[:=> [:cat :int :int] [:int {:max 6}]]]
:wrap #{:input, :output}
:report (fn [error props]
(println "\n" error "\n")
(clojure.pprint/pprint props))}
(fn
([x] (* x x))
([x y] (* x y)))))
(multi-arity-pow 4)
;:malli.core/invalid-output
;
;{:output [:int {:max 6}],
; :value 16,
; :args [4],
; :schema [:=> [:cat :int] [:int {:max 6}]]}
; => 16
(multi-arity-pow 5 0.1)
;:malli.core/invalid-input
;
;{:input [:cat :int :int],
; :args [5 0.1],
; :schema [:=> [:cat :int :int] [:int {:max 6}]]}
;
;:malli.core/invalid-output
;
;{:output [:int {:max 6}],
; :value 0.5,
; :args [5 0.1],
; :schema [:=> [:cat :int :int] [:int {:max 6}]]}
;=> 0.5
oh, and the generators use this too, generator for :=>
looks dead simple now:
(defn -=>-gen [schema options]
(let [output-generator (generator (:output (m/-function-info schema)) options)]
(gen/return (m/-instrument {:schema schema} (fn [& _] (generate output-generator options))))))
dunno if there is a way to emit clj-kondo annotations from schematized fns, which are not vars :thinking_face:
Say I've got a :orn
schema that I'm matching multiple values against:
[:orn [:map1 [:map {:closed true
:description "map1"}
[:a :string]]]
[:map2 [:map {:description "map2"}
[:a :string]
[:b {:optional true} :string]]]]
is there a way to see which of these subschemas were responsible for a given value matching the top-level form - something like m/explain
but for values that do conform to the schema?@afoltzm try m/parse
@ikitommi thanks, I knew there was likely something I was missing. my earlier examples didn't use :orn
- it seems like with an unnamed :or
schema only the conforming value is returned by m/parse
. is there a way to return the matching schema for unnamed :or
subschemas? the use case I'm thinking of here is matching values against some collection of schemas that may not necessarily be known in advance, and so can't easily be named with :orn
.
it seems like I may be able to implement something in terms of m/walk
if it's not built in?
currrently, no. Could add an property to :or
to hint that parse should return the index of the branch or an option to parse like :malli.core/parse-indexes true
. Or, you can just create :or
with number-indexed branches:
(m/parse
[:orn
[1 :int]
[2 :boolean]]
true)
; => [2 true]
ah, gotcha. I think numbered branches is likely the easiest workaround for the time being.
(defn or->orn [s]
(m/into-schema :orn (m/properties s) (map-indexed vector (m/children s))))
(or->orn [:or :int :boolean])
; => [:orn [0 :int] [1 :boolean]]