This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-03-10
Channels
- # announcements (48)
- # asami (8)
- # babashka (186)
- # beginners (56)
- # calva (42)
- # clerk (84)
- # clj-kondo (75)
- # cljdoc (21)
- # clojure (121)
- # clojure-art (1)
- # clojure-australia (1)
- # clojure-china (1)
- # clojure-conj (2)
- # clojure-europe (10)
- # clojure-filipino (1)
- # clojure-hk (1)
- # clojure-indonesia (1)
- # clojure-japan (1)
- # clojure-korea (1)
- # clojure-my (1)
- # clojure-nl (2)
- # clojure-norway (9)
- # clojure-sg (1)
- # clojure-taiwan (1)
- # clojure-uk (2)
- # clojurescript (11)
- # cursive (30)
- # datalevin (20)
- # datomic (4)
- # fulcro (5)
- # gratitude (1)
- # hyperfiddle (89)
- # introduce-yourself (1)
- # java (5)
- # jobs-discuss (8)
- # lsp (89)
- # malli (57)
- # membrane (16)
- # off-topic (12)
- # pathom (38)
- # releases (5)
- # shadow-cljs (17)
- # tools-deps (18)
- # xtdb (62)
Figuring out best way to https://github.com/metosin/malli/issues/264. While doing, about to break “content-dependent schema creation” using a new :compile
prop instead of a top-level callback function. Look like this:
(def Between
(m/-simple-schema
{:type :user/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)
MR here for comments https://github.com/metosin/malli/pull/866
I have an annoying input to a value in my system. It is a vector of items. But if there is only a single item, I receive the one item, not wrapped in a vector. Is there a built in way with malli to handle this? I want the single item to be plopped in a vector. i.e.,
[:map [:items [:vector :string]]]
;; the input I receive is
{:items ["a" "b" ...]}
;; or, sometimes, when there is only one item
{:items "a"}
;; my desired output from m/decode is that :items should always be a vector
{:items ["a"]}
You can use :tuple
instead of :vector
The docs say: A :tuple
describes a fixed length Clojure vector of heterogeneous elements
Ups, I didn't read the example properly 😅
Some sort of transformer might work, but I don't want to apply this to every instance of string/:vector in a schema
I would:
(def vectorify-transformer
(let [coders {:vector #(cond-> % (not (vector? %)) (vector))}]
(mt/transformer
{:decoders coders
:encoders coders})))
(m/decode [:map [:items [:vector :string]]] {:items ["a"]} vectorify-transformer)
; => {:items ["a"]}
(m/decode [:map [:items [:vector :string]]] {:items "a"} vectorify-transformer)
; => {:items ["a"]}
there is mt/collection-transformer
which ensures the correct type, but doesn’t create collections of non-collection items. There could be a similar built-in for this too.
… or you could tag the keys and read those in the transformer:
[:map [:items [:vector {:vectorize true} :string]]]
or
[:map [:items {:vectorize true} [:vector :string]]]
non need for simple-schema here, if instance properties are good enough. or a new property for more declarative transformation
transformers can read the schemas (and properties) at transfromer creation time, so it’s also super efficient, e.g. creating a decoder from this would mount the transforming function:
[:map [:items [:vector {:vectorize true} :string]]]
, but for this:
[:map [:items [:vector :string]]]
… the transformer known there is nothing to do and doesn’t emit any codesee the mt/default-value-transformer
which reads the :default
key from schema. there is a :compile
option to access the schema.
brilliant!
(def vectorize-transformer
(let [coders {:vector {:compile
(fn [schema _]
(when (some-> schema m/properties (find :vectorize))
#(cond-> % (not (vector? %)) (vector))))}}]
(mt/transformer
{:decoders coders
:encoders coders})))
many thanks @U055NJ5CC malli is great 🙂 sometimes i find it difficult to find the extension points like that. diving into the core code is a great tip
@U055NJ5CC given schema [:vector {:vectorize true} :string]
, (m/properties schema)
returns {:vectorize true}
. What is the equivelent malli.core function to get the :string
part? of which there may be more than one in the case of :tuple
or :enum
Usually I use (m/form schema)
to get the plain data and then things like (second form)
or (nth schema 2)
hello o/ I think I found an inconsistent behavior between clj and cljs, in cljs, the uuid is properly validated using regex, but on CLJ the validation is relied to UUID/fromString, it turns out that fromString accept incomplete UUID's as valid uuids.
I think a better approach would be to perform regex validation always, what do you think?
if I have
(def schema1
{::id int?
::country string?})
(def schema3
{::secondary-id ::DOES-NOT-EXIST
::age int?})
(def merged (merge
(malli/default-schemas)
schema1
schema3))
Is there any way to get an error because of trying to refer to the ::DOES-NOT-EXIST
recipe? I remember figuring this out before but I didn’t make a note at the time 😕Currently, the registries do not check the references eagerly to see if everything is linked correctly. m/schema
does check those eagerly:
(def schema1
{::id int?
::country string?})
(def schema3
{::secondary-id ::DOES-NOT-EXIST
::age int?})
(mr/set-default-registry!
(merge
(m/default-schemas)
schema1
schema3))
(m/schema [:map ::id ::age])
; => [:map :user/id :user/age]
(m/schema [:map ::id ::secondary-id])
; =throws=> {:type :malli.core/invalid-schema, :message :malli.core/invalid-schema, :data {:schema :user/DOES-NOT-EXIST}}
thanks, I did try this, but I think I’m using incorrectly because it doesn’t work with valid references:
(def schema1
{::id int?
::country string?})
(def schema2
{::secondary-id ::id
::age int?})
(malli/schema
(merge
(malli/default-schemas)
schema1
schema2))
I get an exceptionoh, so I’d have to check every schema one by one, right?
(m/schema
;; schema syntax
[:map ::id ::secondary-id]
;; options
{:registry (merge
(m/default-schemas)
schema1
schema2)})
; => [:map :user/id :user/secondary-id]
thanks!
.. but checks just what is in the schema syntax. to verify all schemas in registry, you have to pull the schemas (via malli.registry/schemas
) and check them 1 by 1, I guess.
my use case is to merge registries and check whether they have any broken links, let me see if I can come up with a solution
kinda works (will make less imperative):
(defn check-registry [registry]
(let [schemas (-> registry
mr/registry
mr/schemas)]
(doseq [[k _] schemas]
(when (or (string? k) (and (qualified-keyword? k)
(-> k namespace (str/starts-with? "malli") not)))
(malli/schema [:map k] {:registry registry})))))
you could check just your own custom registry, no need to crawl the default schemas I guess. btw, there is m/-reference?
to check if the key is a valid reference type (string or qualified kw).
both great tips, thank you!
I filtered out the malli schemas because the check fails when I pass it one of them
yes, there is a bunch of schemas that can’t be created without children, e.g. (m/schema :schema)
fails, it requires 1 child, (m/schema [:schema :int])
. Same with :map-of
, :enum
, :or
, :vector
etc.
there is ::m/schema
and ::m/val
as internal schema types, marking eager references and entry values, both require a child.
hmm simply merging my own schemas into a registry (and not the default malli schemas) and checking the result with my function doesn’t work, presumably because they use int?
and string?
which only exist in the default schemas
Yes, you have the full registry when validating (via options or global registry), but just need to loop the own ones
Idea: a mr/requiring-resolve-registry
that can be used to create var-schemas without needing to register those:
;; a simple custom Var-schema
(def Over
(m/-simple-schema
{:type `Over ;; here: use qualified symbol so we reconstruct the schema from form!
:compile (fn [_ [value] _]
{:pred #(and (int? %) (> % value))
:min 1
:max 1})}))
;; normal usage
(m/validate [Over 6] 7)
; => true
;; defauls schema + auto-resolve qualified symbols
(mr/set-default-registry!
(mr/composite-registry
(m/default-schemas)
(mr/requiring-resolve-registry)))
;; as symbol
(m/validate [`Over 6] 7)
; => true
;; as symbol
(m/validate ['user/Over 6] 7)
; => true
;; durable
(-> [Over 6]
(edn/write-string)
(edn/read-string)
(m/validate 7))
;=> true
… schema-like simplicity with durability 💪