metosin / malli

High-performance data-driven data specification library for Clojure/Script.
Eclipse Public License 2.0
1.44k stars 205 forks source link

Simplify content-dependent schema creation #866

Closed ikitommi closed 1 year ago

ikitommi commented 1 year ago

Both m/-simple-schema and m/-collection-schema support "content-dependent schema creation", which mean you can use schema properties and children to create the actual Schema instance. Old mechanism to do this was using a arity2 callback function children properties -> props instead of actual property map. This is a bad because:

(-> (m/default-schemas) :> (m/-type))
; => null

This MR introduces a :compile options, which is a arity3 function children properties options -> props. Original properties (without the :compile) are merged to the result of the :compile function, so one can define static properties ahead of time an this works now:

(-> (m/default-schemas) :> (m/-type))
; => :>

It's a breaking change for the extender API, marked as m/-deprecated so it's easy to migrate to using this.

Bump into this while working on https://github.com/metosin/malli/issues/264.

Usage example:

(def Between
  (m/-simple-schema
   {:type `Between
    :compile (fn [_properties [min max] _options]
               (when-not (and (int? min) (int? max))
                 (m/-fail! ::invalid-children {:min min, :max max}))
               {:pred #(and (int? %) (>= min % max))
                :min 2 ;; at least 1 child
                :max 2 ;; at most 1 child
                :type-properties {:error/fn (fn [error _] (str "should be betweeb " min " and " max ", was " (:value error)))
                                  :decode/string mt/-string->long
                                  :json-schema {:type "integer"
                                                :format "int64"
                                                :minimum min
                                                :maximum max}
                                  :gen/gen (gen/large-integer* {:min (inc min), :max max})}})}))

(m/form [Between 10 20])
; => [user/Between 10 20]

(-> [Between 10 20]
    (m/explain 8)
    (me/humanize))
; => ["should be betweeb 10 and 20, was 8"]

(mg/sample [Between -10 10])
; => (-1 0 -2 -4 -4 0 -2 7 1 0)
bsless commented 1 year ago

Is it possible to not make the change breaking at the moment, but accept both and emit a warning?

ikitommi commented 1 year ago

thanks @bsless, it is. Changed so that the old syntax works too, just with extra DEPRECATED printing on the console.