Hi, I'm getting started with malli and this migh be a noob question, but why is this an invalid schema error? (I took the source of string?
in cljs and changed it to str2?
Weirdly string? is valid and the alias isn't
(defn ^boolean str2?
"Returns true if x is a JavaScript string."
[x]
(goog/isString x))
(def Test
[:map
[:url str2?]
[:det map?]
]
)
(-> Test
(m/explain {:url "sa" :det {:a 5}})
(me/humanize)
)
> good point. maybe for some people serializing a schema is not important? to be frank I think quite some (if not most) people don't need that ...
Why can't I define a boolean predicate function there?
@strasser.ms Any reason you're not using string?
@strasser.ms try [:fn str2]
the default shemas are listed here: https://github.com/metosin/malli/blob/master/src/malli/core.cljc#L1223-L1269
FWIW string?
on CLJS is:
(defn ^boolean string?
"Returns true if x is a JavaScript string."
[x]
(goog/isString x))
thanks for the quick repsonse @borkdude, str2? was just for figuring out if my function was the issue
ah ok
@wxitb2017 thanks that works actually, but error messages are not defined then
example:
(defn twitter-url? [s] (re-find #"http[s]?://twitter.*\d+" s))
(def Test
[:map
[:url [:fn twitter-url?]]
[:det map?]
]
)
(-> Test
(m/explain {:url "<https://twitter.com/dennybritz/status/1323580850433269760>" :det {:a 5}})
(me/humanize)
)
(-> Test
(m/explain {:url "<https://notwitterurl>" :det {:a 5}})
(me/humanize)
)
{:url ["unknown error"]} then
@strasser.ms Have you tried https://github.com/metosin/malli#custom-error-messages?
Maybe malli expects the function to return a boolean instead of nil? don't know
Thanks for the pointer
nils should be ok, also regexs:
[:map
[:x #"http[s]?://twitter.*\d+"]
[:y [:fn {:error/message "invalid"}Â (constantly false)]]
Ah interesting, so regex work, but I can't have something like:
[:x #(re-find #"http[s]?://twitter.*\d+" %)]
Probably have to add to the default schema registry then, to have it work naturally like, [:x twitter-url?] I guess?I'm going to read the docs for the third time; seems my mental model is not there yet!
(defn twitter-url? [s] (re-find #"http[s]?://twitter.*\d+" s))
(def -twitter-url? [:fn {:error/message "invalid twitter url"} twitter-url?])
(def Test
[:map
[:url -twitter-url?]
[:det map?]
]
)
(-> Test
(m/explain {:url "<https://twitter.com/dennybritz/status/1323580850433269760>" :det {:a 5}})
(me/humanize)
)
So this is how I could do it; only backdraw is that I have two functions now; one for normal use, the other specifically for validationcould make the predicate schemas implement IFn
, so you could:
(def twitter-url?
(m/schema
[:re {:error/message "invalid twitter url"}
#"http[s]?://twitter.*\d+"]))
(twitter-url? "<https://twitter.com/dennybritz/status/1323580850433269760>")
; => true
(m/validat twitter-url? "<https://twitter.com/dennybritz/status/1323580850433269760>")
; => true
Looks promising. For me personally (and I might be totally off) it could be nice for malli to also accept predicate functions directly inside the spec-vector
(defn twitter-url? [s] (re-find #"http[s]?://twitter.*\d+" s))
(def Test
[:map
[:url twitter-url?]
]
)
For now I'll read up on the registry and how to add it thereWhat's the reason it requires [:fn ..]
right now @ikitommi?
is there something off with generators and the [:and]
clause ?
(def schema [:and map?
[:map
[:foo string?]]])
(mg/generate schema)
throws an error ("Couldn't satisfy such-that predicate after 100 tries.")
this, however, works:
(def schema [:and
[:map
[:foo string?]]
map?])
(mg/generate schema)
my hunch is that it's unable to "deduce" that it should first try to generate the :map
, and starts by generating a completely random map which of course never qualifies the [:map [:foo string?]]
schema?yes, the order matters
still seems like there's something off with :and
:thinking_face:
(def base [:map
[:foo [:enum :a :b]]])
(def x [:and base
[:multi {:dispatch :foo}
[:a [:map [:i string?]]]
[:b [:map [:j pos-int?]]]]])
seems like mg/generate is unable to generate values for this schema as well(defmethod -schema-generator :and [schema options] (gen/such-that (m/validator schema options) (-> schema (m/children options) first (generator options)) 100))
^:-- the first one is used for the generator.
aha!
but that means there isn't really a way for me to work around it except writing a custom generator, right ?
(which is what i just did)
you could merge the base schemas, but merge might not work with :multi
. but, there are options, like:
(mg/generate
[:and
[:multi {:dispatch :foo}
[:a [:merge ::base [:map [:i string?]]]]
[:b [:merge ::base [:map [:j pos-int?]]]]]]
{:registry (merge
(m/default-schemas)
(mu/schemas)
{::base [:map [:foo [:enum :a :b]]]})})
i see
merge doesn’t work with :multi
currently, this would be best way to do it (if it worked):
(mu/merge
[:map [:foo [:enum :a :b]]]
[:multi {:dispatch :foo}
[:a [:map [:i string?]]]
[:b [:map [:j pos-int?]]]])
;[:multi {:dispatch :foo}
; [:a [:map [:i string?]]]
; [:b [:map [:j pos-int?]]]]
i just wrote a helper function which just does this:
(defn merged
"Given a list of generators, returns a generator that applies `merge`
to all the generator results."
[& gens]
(gen/fmap (fn [args]
(reduce merge {} args))
(apply gen/tuple gens)))
(def schema [:and {:gen/gen (merged (mg/geneator a) (mg/generator b)} a b])
:multi
should do type-inferring, so that all map-like :multi
s act like a map.
yes
that's what i was going to say
:multi
is pretty much always a map anyway
unless it’s a tuple / sequence:
(m/validate
[:multi {:dispatch 'first}
[:sized [:tuple keyword? [:map [:size int?]]]]
[:human [:tuple keyword? [:map [:name string?] [:address [:map [:country keyword?]]]]]]]
[:human {:name "seppo", :address {:country :sweden}}])
; true
right
but, it’s easy to figure out if it looks like a map and could be merged.
could add an option to allow that shortcut (as there is regexs), but in short: it’s too easy to write schemas which do not serialize.
but if one is not looking for serialization, it would be easier for sure.
good point. maybe for some people serializing a schema is not important?
jinx
need to revisit these for 1.0.0. but will add optional support for plain functions.
also, the default registry being immutable. it’s currently easy to use immutable registries, but hard to get a global mutable registry. both could be easy.
Do you think malli will be able to do something like grasp?
as in, support the things that spec does to match s-expressions
I believe so, as soon as there are the sequence schemas.
Hi, I’ve just started with malli
, and I’m spiking converting our spec
definitions to malli-based. Among others, we’ve defined specs with generators and json decoding for java-time
(aka java8) types , like instant
, duration
, and interval
. It’s easy with spec-tools
. Now I’m trying to do the same with malli
and this is what I came up with:
(def Instant
(m/-simple-schema
{:pred t/instant?
:type-properties {:error/message "should be an instant"
:decode/json t/instant
:encode/json str
:gen/gen (gen/fmap (fn [[d h m s]] (t/plus (t/instant) (t/days d) (t/hours h) (t/minutes m) (t/seconds s)))
(gen/tuple gen/int gen/int gen/int gen/int))}}))
Does it makes sense? Would there be a better way? I’m aware of this https://github.com/metosin/malli/issues/49, but it’s still open.