Anyone have any novel ways of organizing their schema registry worth sharing? We all know the traditional clojure.spec model, where you co-locate the specs with the business logic, and then register schema underneath fully qualified keywords, ::alias/foo. Later on clojure added require :as-alias which let you use potentially synthetic namespaces. Malli adds a lot of degrees of freedom, you don't have to use keywords necessarily in the registry, etc, etc.
One thing i've done, that i found very useful / practical, is I have all classes resolve in the registry, so you can write (m/validate java.lang.String "foo")
https://github.com/metosin/malli/issues/1007 for a recent discussion of class schemas
How do you have all classes resolve in the registry?
;; -----------------------------------------------------------------------------------------------------------
;; Malli Protocol Extensions
;;
;; We would like to extend m/IntoSchema with support for java.lang.Class, so that
;; you can treat any java Class like its a schema/specification, for example....
;; (m/validate java.lang.String "foo") ;;=> true
;; (m/validate java.lang.String 3) ;;=> false
;; (m/validate datomic.db.Db @*db*) ;;=> true
;;
;;
;; Unfortunately malli's implementation of into-schema? calls instance? instead of satisfies?
;; so the polymorphism is closed, instead of open, not what you'd expect for a protocol.
;; They did this for performance reasons because satisfies? is slow (see )
;;
;; So we fix all this with a new protocol,
;; See if you want to understand this
(defprotocol SatisfiesIntoSchema?
(into-schema?
[this]
"Returns true iff. you can call m/into-schema on `this` otherwise false."))
(alter-var-root #'m/into-schema? (constantly #(or (into-schema? %)
(instance? malli.core.IntoSchema %))))
(extend-protocol SatisfiesIntoSchema? ;
nil
(into-schema? [this] false)
Object
(into-schema? [this] false)
malli.core.IntoSchema
(into-schema? [this] true))
;; Now we finally have an an extensible system / open polymorphism.... extend it
(extend-protocol m/IntoSchema
java.lang.Class
(-type [^Class this] (symbol (.getName this)))
(-type-properties [this])
(-properties-schema [this options])
(-children-schema [this options])
(-into-schema [^Class this properties children options]
(m/schema [:fn (merge {:error/message (str "should be an instance of " this)} properties)
#(clojure.core/instance? this %)]
options)))
(extend-protocol SatisfiesIntoSchema? ; this must always follow .... what's above ^
java.lang.Class
(into-schema? [this] true)) maybe @ikitommi would be onboard with upstreaming the alter-var-root part of it, if you like the concept.
Using a protocol ontop of a protocol, so you don't have to call satisfies? which is dead-slow
You can use classes as Schemas using custom registry, had this stashed. Would this be a good add-on to malli itself?
(ns malli.class-registry
(:require [malli.registry :as mr]
[malli.core :as m]))
(defn -class-schema [c]
(m/-simple-schema
{:type c
:pred #(instance? c %)
:type-properties {:error/message (str "not an instance of class " c)}}))
(defn class-registry
([] (class-registry nil))
([_options]
(reify
mr/Registry
(-schema [_ type] (when (class? type) (-class-schema type)))
(-schemas [_]))))
…
(mr/set-default-registry!
(mr/composite-registry
(m/default-schemas)
(malli.class-registry/class-registry)))
(m/validate String "123") ; => true
(-> (m/explain String 123) (me/humanize))
; => ["not an instance of class class java.lang.String"]1. would be optional
2. Classes should be mapped to malli types to enable generators and transformers
3. should do some caching to avoid garbage (e.g. all instances of String turned into separate Schema instances)
Yah this is definitely worth including in malli, (i still wouldnt mind ALSO having open polymorphism / extend protocol and the approach i went but thats just me , i think theres certain behavior you can only implement this way )
Classes resolving are super useful in plumatic style experimental defn syntax
In that case, you don’t need transformers, generators yes?
i dont
I'd worry about the humanized error message, maybe it should only print the class base name?